agentic-workflow-manager 9.11.12 → 9.12.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/dist/src/commands/doctor.js +23 -1
- package/dist/src/commands/hooks/claude.js +61 -8
- package/dist/src/commands/hooks/resync.js +1 -1
- package/dist/src/commands/job/index.js +39 -4
- package/dist/src/commands/model-policy/index.js +253 -0
- package/dist/src/commands/plan/index.js +53 -9
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +62 -4
- package/dist/src/commands/watch/supervisor.js +63 -3
- package/dist/src/core/admission/index.js +20 -6
- package/dist/src/core/journal/types.js +21 -5
- package/dist/src/core/model-policy/capabilities-v2.js +152 -0
- package/dist/src/core/model-policy/capabilities.js +7 -1
- package/dist/src/core/model-policy/claude-hook-events.js +120 -0
- package/dist/src/core/model-policy/event-store.js +221 -0
- package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
- package/dist/src/core/model-policy/journal.js +113 -18
- package/dist/src/core/model-policy/local-claude-scope.js +41 -0
- package/dist/src/core/model-policy/local-codex-scope.js +33 -0
- package/dist/src/core/model-policy/local-event-scope.js +13 -0
- package/dist/src/core/model-policy/machine-key.js +56 -0
- package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
- package/dist/src/core/model-policy/native-claude-config.js +107 -0
- package/dist/src/core/model-policy/native-claude-machine.js +58 -0
- package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
- package/dist/src/core/model-policy/native-claude.js +41 -0
- package/dist/src/core/model-policy/native-codex-machine.js +116 -0
- package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
- package/dist/src/core/model-policy/native-codex.js +302 -0
- package/dist/src/core/model-policy/native-routing-observe.js +106 -0
- package/dist/src/core/model-policy/native-routing-proof.js +52 -0
- package/dist/src/core/model-policy/native-runtime.js +86 -0
- package/dist/src/core/model-policy/resolve.js +96 -6
- package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
- package/dist/src/core/model-policy/setup-readiness.js +77 -0
- package/dist/src/providers/index.js +3 -1
- package/dist/tests/commands/doctor-routing.test.js +22 -0
- package/dist/tests/commands/doctor.test.js +5 -0
- package/dist/tests/commands/hooks/install.test.js +6 -0
- package/dist/tests/commands/hooks/resync.test.js +3 -0
- package/dist/tests/commands/hooks/status.test.js +12 -1
- package/dist/tests/commands/job/routing-file.test.js +32 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
- package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
- package/dist/tests/commands/model-policy/index.test.js +179 -0
- package/dist/tests/commands/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +84 -0
- package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
- package/dist/tests/core/admission/index.test.js +21 -0
- package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
- package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
- package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
- package/dist/tests/core/model-policy/event-store.test.js +151 -0
- package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
- package/dist/tests/core/model-policy/journal.test.js +112 -1
- package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
- package/dist/tests/core/model-policy/machine-key.test.js +38 -0
- package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
- package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
- package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
- package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
- package/dist/tests/core/model-policy/native-claude.test.js +21 -0
- package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
- package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
- package/dist/tests/core/model-policy/native-codex.test.js +133 -0
- package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
- package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
- package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
- package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
- package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
- package/dist/tests/providers/index.test.js +1 -1
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -21,6 +21,26 @@ function canonical(value) { if (Array.isArray(value))
|
|
|
21
21
|
function digest(value) { return crypto_1.default.createHash('sha256').update(canonical(value)).digest('hex'); }
|
|
22
22
|
function clone(state) { const next = structuredClone(state); if (!Array.isArray(next.routingAttempts))
|
|
23
23
|
next.routingAttempts = []; return next; }
|
|
24
|
+
function noteRoutingIncident(state, target, selection, reasonCode, at, fallback, sameObligationAlreadyCounted = false) {
|
|
25
|
+
if (!/^[A-Z0-9_]{3,64}$/.test(reasonCode))
|
|
26
|
+
throw new Error('routing incident reason is invalid');
|
|
27
|
+
state.routingIncidents ??= [];
|
|
28
|
+
// One incident/alert per selected route in a run even if later failures
|
|
29
|
+
// refine the cause; retain the first actionable reason.
|
|
30
|
+
const id = crypto_1.default.createHash('sha256').update(`${state.journalId}\0${state.cycle.startedAt}\0${target}\0${canonical(selection)}`).digest('hex').slice(0, 24);
|
|
31
|
+
const prior = state.routingIncidents.find(item => item.id === id);
|
|
32
|
+
if (prior) {
|
|
33
|
+
prior.affectedObligations += Number(!sameObligationAlreadyCounted);
|
|
34
|
+
prior.fallbackCount += Number(fallback);
|
|
35
|
+
prior.blockedCount += Number(!fallback);
|
|
36
|
+
prior.lastAt = at;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (state.routingIncidents.length >= 4096)
|
|
40
|
+
throw new Error('routing incident limit exhausted');
|
|
41
|
+
state.routingIncidents.push({ schema: 'routing-incident/v1', id, runStartedAt: state.cycle.startedAt, target, selection: structuredClone(selection), reasonCode,
|
|
42
|
+
firstAt: at, lastAt: at, affectedObligations: 1, fallbackCount: Number(fallback), blockedCount: Number(!fallback), alertState: 'pending' });
|
|
43
|
+
}
|
|
24
44
|
function reserveRoutingAttempt(state, input, now) {
|
|
25
45
|
assertTimestamp(now);
|
|
26
46
|
if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.obligationId !== 'string' || input.obligationId.length === 0 || input.obligationId.length > 128 || typeof input.lineageId !== 'string' || input.lineageId.length === 0 || input.lineageId.length > 128 || !(0, types_1.isRoutingEnvelope)(input.envelope) || !/^[a-f0-9]{64}$/.test(input.fingerprint))
|
|
@@ -35,34 +55,62 @@ function reserveRoutingAttempt(state, input, now) {
|
|
|
35
55
|
next.implementationLineages ??= [];
|
|
36
56
|
const priorLineage = next.implementationLineages.find(lineage => lineage.id === input.lineageId);
|
|
37
57
|
if (input.envelope.role === 'implementer' && priorLineage === undefined)
|
|
38
|
-
next.implementationLineages.push({ id: input.lineageId, obligationId: input.obligationId, sliceId: input.envelope.sliceId, planDigest: input.envelope.planDigest, executionDigest: input.envelope.executionDigest, initialProfile: input.envelope.requestedProfile, initialEffort: input.envelope.resolved.effort.kind === '
|
|
58
|
+
next.implementationLineages.push({ id: input.lineageId, obligationId: input.obligationId, sliceId: input.envelope.sliceId, planDigest: input.envelope.planDigest, executionDigest: input.envelope.executionDigest, initialProfile: input.envelope.requestedProfile, initialEffort: input.envelope.resolved.effort.kind === 'runtime-default' ? 'runtime-default' : input.envelope.resolved.effort.value === 'high' ? 'high' : 'medium', attempts: 0 });
|
|
39
59
|
if (priorLineage !== undefined && (priorLineage.obligationId !== input.obligationId || priorLineage.planDigest !== input.envelope.planDigest || priorLineage.executionDigest !== input.envelope.executionDigest || priorLineage.sliceId !== input.envelope.sliceId || priorLineage.initialProfile !== input.envelope.requestedProfile))
|
|
40
60
|
throw new Error('routing lineage binding mismatch');
|
|
41
61
|
const attempts = next.routingAttempts.filter(item => item.lineageId === input.lineageId && item.envelope.role === 'implementer');
|
|
42
62
|
if (input.envelope.role === 'implementer' && attempts.length >= 3)
|
|
43
63
|
throw new Error('routing implementation budget exhausted');
|
|
64
|
+
const fullFallback = input.envelope.role === 'implementer' && input.envelope.effectiveProfile === 'full';
|
|
65
|
+
if (fullFallback && attempts.some(item => item.envelope.effectiveProfile === 'full'))
|
|
66
|
+
throw new Error('routing full fallback cannot recursively retry a lineage');
|
|
67
|
+
if (fullFallback && attempts.length > 0) {
|
|
68
|
+
const previous = attempts[attempts.length - 1];
|
|
69
|
+
if (previous.state !== 'blocked' || previous.verdict !== 'inconclusive'
|
|
70
|
+
|| !['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(previous.reasonCode ?? ''))
|
|
71
|
+
throw new Error('routing full fallback requires a terminal native routing failure');
|
|
72
|
+
}
|
|
44
73
|
if (input.envelope.role === 'implementer') {
|
|
45
74
|
const initial = input.envelope.requestedProfile;
|
|
46
75
|
if (initial === 'full')
|
|
47
76
|
throw new Error('routing implementer profile is invalid');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
if (!fullFallback) {
|
|
78
|
+
const expected = resolveLineageEscalation(next, input.lineageId, initial);
|
|
79
|
+
const actualEffort = input.envelope.resolved.effort.kind === 'explicit' ? input.envelope.resolved.effort.value : 'runtime-default';
|
|
80
|
+
const judgmentHighAfterIntegration = expected.profile === 'judgment' && expected.effort === 'medium' && actualEffort === 'high' && attempts.length > 0 && attempts[attempts.length - 1].envelope.effectiveProfile === 'integration';
|
|
81
|
+
if (input.envelope.effectiveProfile !== expected.profile || (actualEffort !== expected.effort && !judgmentHighAfterIntegration))
|
|
82
|
+
throw new Error('routing effective escalation mismatch');
|
|
83
|
+
}
|
|
53
84
|
}
|
|
54
85
|
const approval = input.approval?.();
|
|
55
86
|
if (!approval)
|
|
56
87
|
throw new Error('routing reservation requires current approval');
|
|
57
|
-
|
|
88
|
+
if ((approval.eventReceipt || approval.eventScope) && (!approval.eventReceipt || !approval.eventScope || canonical(approval.eventScope.runtime) !== canonical(input.envelope.runtime)))
|
|
89
|
+
throw new Error('routing reservation event scope mismatch');
|
|
90
|
+
const selectionInput = { role: input.envelope.role, requestedProfile: input.envelope.role === 'implementer' ? input.envelope.effectiveProfile : 'full', runtime: (0, capabilities_1.validateRuntimeKey)(input.envelope.runtime), policy: approval.policy, capabilities: approval.capabilities, eventReceipt: approval.eventReceipt, eventScope: approval.eventScope, circuitIncidents: next.routingIncidents, now: approval.checkedAt };
|
|
91
|
+
const mapping = approval.policy?.content.mappings.find(item => item.target === input.envelope.runtime.target && item.runtimeKind === input.envelope.runtime.kind);
|
|
92
|
+
const optimizedSelection = mapping && input.envelope.role === 'implementer' && input.envelope.requestedProfile !== 'full' ? mapping.profiles[input.envelope.requestedProfile] : undefined;
|
|
93
|
+
const circuitReason = optimizedSelection && next.routingIncidents?.find(item => item.target === input.envelope.runtime.target && canonical(item.selection) === canonical(optimizedSelection)
|
|
94
|
+
&& ['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(item.reasonCode))?.reasonCode;
|
|
58
95
|
const resolved = input.envelope.role === 'implementer' && input.envelope.effectiveProfile === 'judgment' && input.envelope.resolved.effort.kind === 'explicit' && input.envelope.resolved.effort.value === 'high'
|
|
59
96
|
? (0, resolve_1.resolveEscalatedSelection)({ ...selectionInput, role: 'implementer', requestedProfile: 'judgment', expectedEffort: 'high' })
|
|
60
|
-
:
|
|
61
|
-
|
|
97
|
+
: approval.eventReceipt && approval.eventScope
|
|
98
|
+
? (0, resolve_1.resolveEventWithFallback)({ role: selectionInput.role, requestedProfile: input.envelope.role === 'implementer' ? fullFallback ? input.envelope.requestedProfile : input.envelope.effectiveProfile : 'full', policy: approval.policy, receipt: approval.eventReceipt, scope: approval.eventScope, now: approval.checkedAt, circuitReason })
|
|
99
|
+
: (0, resolve_1.resolveSelection)(selectionInput);
|
|
100
|
+
if (fullFallback && (resolved.state !== 'resolved' || !resolved.fallbackReason))
|
|
101
|
+
throw new Error('routing full fallback requires a current optimized-route failure');
|
|
102
|
+
if (resolved.state !== 'resolved' || resolved.effectiveProfile !== input.envelope.effectiveProfile || canonical(resolved.selection) !== canonical(input.envelope.resolved) || resolved.policyDigest !== input.envelope.policyDigest || resolved.capabilityDigest !== input.envelope.capabilityDigest || resolved.outcome !== input.envelope.outcome || canonical(resolved.unavailableEvidence) !== canonical(input.envelope.unavailableEvidence) || resolved.nativeAgentType !== input.envelope.nativeAgentType)
|
|
62
103
|
throw new Error('routing reservation approval mismatch');
|
|
63
104
|
const attempt = attempts.length + 1;
|
|
64
105
|
const id = `route-${crypto_1.default.createHash('sha256').update(`${input.lineageId}\0${attempt}\0${envelopeDigest}\0${input.fingerprint}`).digest('hex').slice(0, 24)}`;
|
|
65
|
-
next.routingAttempts.push({ schema: 'routing-attempt/v1', id, obligationId: input.obligationId, lineageId: input.lineageId, attempt, envelope: structuredClone(input.envelope), envelopeDigest, fingerprint: input.fingerprint, state: 'reserved' });
|
|
106
|
+
next.routingAttempts.push({ schema: 'routing-attempt/v1', id, obligationId: input.obligationId, lineageId: input.lineageId, attempt, envelope: structuredClone(input.envelope), envelopeDigest, fingerprint: input.fingerprint, state: 'reserved', reservedAt: now, ...(approval.eventReceipt ? { nativeEvidenceRequired: true } : {}), ...(resolved.state === 'resolved' && resolved.fallbackReason ? { reasonCode: resolved.fallbackReason } : {}) });
|
|
107
|
+
if (resolved.state === 'resolved' && resolved.fallbackReason) {
|
|
108
|
+
const mapping = approval.policy?.content.mappings.find(item => item.target === input.envelope.runtime.target && item.runtimeKind === input.envelope.runtime.kind);
|
|
109
|
+
if (!mapping || input.envelope.role !== 'implementer' || input.envelope.requestedProfile === 'full')
|
|
110
|
+
throw new Error('routing fallback lacks approved optimized selection');
|
|
111
|
+
const sameObligationAlreadyCounted = attempts.some(item => item.obligationId === input.obligationId && item.state === 'blocked' && item.reasonCode === resolved.fallbackReason);
|
|
112
|
+
noteRoutingIncident(next, input.envelope.runtime.target, mapping.profiles[input.envelope.requestedProfile], resolved.fallbackReason, now, true, sameObligationAlreadyCounted);
|
|
113
|
+
}
|
|
66
114
|
const lineage = next.implementationLineages.find(candidate => candidate.id === input.lineageId);
|
|
67
115
|
if (lineage !== undefined)
|
|
68
116
|
lineage.attempts = attempt;
|
|
@@ -70,25 +118,53 @@ function reserveRoutingAttempt(state, input, now) {
|
|
|
70
118
|
}
|
|
71
119
|
function observeRoutingAttempt(state, input, now) {
|
|
72
120
|
assertTimestamp(now);
|
|
73
|
-
if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.attemptId !== 'string' || input.attemptId.length === 0 || typeof input.nativeAgentId !== 'string' || input.nativeAgentId.length === 0 || input.nativeAgentId.length > 128 || (input.observed !== undefined && !(0, types_1.isRoutingSelection)(input.observed)) || (input.unavailableReason !== undefined && (typeof input.unavailableReason !== 'string' || input.unavailableReason.length === 0 || input.unavailableReason.length > 128)))
|
|
121
|
+
if (!(0, types_1.isWellFormedState)(state) || !input || typeof input.attemptId !== 'string' || input.attemptId.length === 0 || typeof input.nativeAgentId !== 'string' || input.nativeAgentId.length === 0 || input.nativeAgentId.length > 128 || (input.observed !== undefined && !(0, types_1.isRoutingSelection)(input.observed)) || (input.observedBackendModel !== undefined && (typeof input.observedBackendModel !== 'string' || input.observedBackendModel.length === 0 || input.observedBackendModel.length > 128 || input.nativeEventDigest === undefined)) || (input.unavailableReason !== undefined && (typeof input.unavailableReason !== 'string' || input.unavailableReason.length === 0 || input.unavailableReason.length > 128)))
|
|
74
122
|
throw new Error('routing observation is invalid');
|
|
75
123
|
const next = clone(state);
|
|
76
124
|
const attempt = next.routingAttempts.find(item => item.id === input.attemptId);
|
|
77
125
|
if (!attempt)
|
|
78
126
|
throw new Error('routing attempt is unknown');
|
|
79
127
|
if (attempt.state === 'active') {
|
|
80
|
-
if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed))
|
|
128
|
+
if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed) && attempt.observedBackendModel === input.observedBackendModel)
|
|
129
|
+
return next;
|
|
130
|
+
throw new Error('routing observation is immutable');
|
|
131
|
+
}
|
|
132
|
+
if (attempt.state === 'blocked') {
|
|
133
|
+
const mismatch = input.observed !== undefined && canonical(input.observed) !== canonical(attempt.envelope.resolved);
|
|
134
|
+
const reason = mismatch ? 'SELECTION_MISMATCH' : ['PROVIDER_REJECTED', 'PROVENANCE_MISSING'].includes(input.unavailableReason ?? '') ? input.unavailableReason : undefined;
|
|
135
|
+
if (attempt.nativeAgentId === input.nativeAgentId && canonical(attempt.observed) === canonical(input.observed) && reason === attempt.reasonCode)
|
|
81
136
|
return next;
|
|
82
137
|
throw new Error('routing observation is immutable');
|
|
83
138
|
}
|
|
84
139
|
if (attempt.state !== 'reserved')
|
|
85
140
|
throw new Error('routing attempt cannot be observed');
|
|
86
|
-
|
|
141
|
+
if (input.nativeEventDigest !== undefined) {
|
|
142
|
+
if (!/^[a-f0-9]{64}$/.test(input.nativeEventDigest) || next.routingAttempts.some(item => item.id !== attempt.id
|
|
143
|
+
&& (item.nativeEventDigest === input.nativeEventDigest || (item.nativeEvidenceRequired && item.nativeAgentId === input.nativeAgentId))))
|
|
144
|
+
throw new Error('native routing event was already consumed');
|
|
145
|
+
attempt.nativeEventDigest = input.nativeEventDigest;
|
|
146
|
+
}
|
|
87
147
|
attempt.nativeAgentId = input.nativeAgentId;
|
|
88
148
|
if (input.observed)
|
|
89
149
|
attempt.observed = structuredClone(input.observed);
|
|
90
|
-
if (input.
|
|
91
|
-
attempt.
|
|
150
|
+
if (input.observedBackendModel)
|
|
151
|
+
attempt.observedBackendModel = input.observedBackendModel;
|
|
152
|
+
const mismatch = input.observed !== undefined && canonical(input.observed) !== canonical(attempt.envelope.resolved);
|
|
153
|
+
const unavailable = input.unavailableReason === 'PROVIDER_REJECTED' || input.unavailableReason === 'PROVENANCE_MISSING';
|
|
154
|
+
if (mismatch || unavailable) {
|
|
155
|
+
const reason = mismatch ? 'SELECTION_MISMATCH' : input.unavailableReason;
|
|
156
|
+
attempt.state = 'blocked';
|
|
157
|
+
attempt.verdict = 'inconclusive';
|
|
158
|
+
attempt.reasonCode = reason;
|
|
159
|
+
// Agent-supplied negative evidence may conservatively trip a breaker,
|
|
160
|
+
// but never mints or renews native capability evidence.
|
|
161
|
+
noteRoutingIncident(next, attempt.envelope.runtime.target, attempt.envelope.resolved, reason, now, false);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
attempt.state = 'active';
|
|
165
|
+
if (input.unavailableReason)
|
|
166
|
+
attempt.reasonCode = input.unavailableReason;
|
|
167
|
+
}
|
|
92
168
|
return next;
|
|
93
169
|
}
|
|
94
170
|
/** Computes, but never persists, the only permitted implementation retry.
|
|
@@ -105,12 +181,19 @@ function resolveLineageEscalation(state, lineageId, initial) {
|
|
|
105
181
|
return { profile: initial, effort: lineage?.initialEffort ?? 'medium' };
|
|
106
182
|
}
|
|
107
183
|
const last = attempts[attempts.length - 1];
|
|
184
|
+
if (last.envelope.effectiveProfile === 'full')
|
|
185
|
+
throw new Error('routing full fallback is exhausted');
|
|
186
|
+
if (last.verdict === 'inconclusive' && ['PROVIDER_REJECTED', 'SELECTION_MISMATCH', 'PROVENANCE_MISSING'].includes(last.reasonCode ?? '')) {
|
|
187
|
+
const lineage = state.implementationLineages?.find(candidate => candidate.id === lineageId);
|
|
188
|
+
return { profile: initial, effort: lineage?.initialEffort ?? 'medium' };
|
|
189
|
+
}
|
|
108
190
|
if (last.verdict !== 'fail')
|
|
109
191
|
throw new Error('routing lineage advances only after a terminal failed verdict');
|
|
192
|
+
const routeEffort = last.envelope.resolved.effort.kind === 'runtime-default' ? 'runtime-default' : 'medium';
|
|
110
193
|
if (last.envelope.effectiveProfile === 'mechanical')
|
|
111
|
-
return { profile: 'integration', effort:
|
|
194
|
+
return { profile: 'integration', effort: routeEffort };
|
|
112
195
|
if (last.envelope.effectiveProfile === 'integration')
|
|
113
|
-
return { profile: 'judgment', effort:
|
|
196
|
+
return { profile: 'judgment', effort: routeEffort };
|
|
114
197
|
if (last.envelope.effectiveProfile === 'judgment' && last.envelope.resolved.effort.kind === 'explicit' && last.envelope.resolved.effort.value === 'medium')
|
|
115
198
|
return { profile: 'judgment', effort: 'high' };
|
|
116
199
|
throw new Error('routing lineage escalation exhausted');
|
|
@@ -121,6 +204,7 @@ function routingReport(state) {
|
|
|
121
204
|
const attempts = state.routingAttempts ?? [];
|
|
122
205
|
const plannedByRole = {};
|
|
123
206
|
const actualByRole = {};
|
|
207
|
+
const nativeReconciledByRole = {};
|
|
124
208
|
const byState = {};
|
|
125
209
|
const unavailable = {};
|
|
126
210
|
const verdicts = {};
|
|
@@ -128,11 +212,22 @@ function routingReport(state) {
|
|
|
128
212
|
plannedByRole[attempt.envelope.role] = (plannedByRole[attempt.envelope.role] ?? 0) + 1;
|
|
129
213
|
if (attempt.nativeAgentId !== undefined)
|
|
130
214
|
actualByRole[attempt.envelope.role] = (actualByRole[attempt.envelope.role] ?? 0) + 1;
|
|
215
|
+
if (attempt.nativeEventDigest !== undefined && ['active', 'complete'].includes(attempt.state))
|
|
216
|
+
nativeReconciledByRole[attempt.envelope.role] = (nativeReconciledByRole[attempt.envelope.role] ?? 0) + 1;
|
|
131
217
|
byState[attempt.state] = (byState[attempt.state] ?? 0) + 1;
|
|
132
218
|
for (const reason of attempt.envelope.unavailableEvidence)
|
|
133
219
|
unavailable[reason] = (unavailable[reason] ?? 0) + 1;
|
|
220
|
+
if (attempt.envelope.role === 'implementer' && attempt.envelope.effectiveProfile === 'full' && attempt.reasonCode)
|
|
221
|
+
unavailable[attempt.reasonCode] = (unavailable[attempt.reasonCode] ?? 0) + 1;
|
|
134
222
|
if (attempt.verdict !== undefined)
|
|
135
223
|
verdicts[attempt.verdict] = (verdicts[attempt.verdict] ?? 0) + 1;
|
|
136
224
|
}
|
|
137
|
-
|
|
225
|
+
const incidents = (state.routingIncidents ?? []).map(({ reasonCode, selection, affectedObligations, fallbackCount, blockedCount, alertState }) => ({ reasonCode, selection, affectedObligations, fallbackCount, blockedCount, alertState }));
|
|
226
|
+
const reconciled = Object.values(nativeReconciledByRole).reduce((total, count) => total + count, 0);
|
|
227
|
+
const selections = attempts.map(attempt => ({ role: attempt.envelope.role, configured: attempt.envelope.resolved,
|
|
228
|
+
accepted: attempt.nativeEventDigest && ['active', 'complete'].includes(attempt.state) ? attempt.observed ?? 'unknown' : 'unknown',
|
|
229
|
+
backendModel: attempt.nativeEventDigest && ['active', 'complete'].includes(attempt.state) ? attempt.observedBackendModel ?? 'unknown' : 'unknown' }));
|
|
230
|
+
return { schema: 'routing-report/v1', attempts: attempts.length, plannedByRole, actualByRole, selections,
|
|
231
|
+
...(reconciled ? { nativeReconciledByRole } : {}), byState, retries: attempts.filter((attempt) => attempt.attempt > 1).length, fallbacks: attempts.filter((attempt) => attempt.envelope.role === 'implementer' && attempt.envelope.effectiveProfile === 'full' && attempt.envelope.requestedProfile !== 'full').length, unavailable, verdicts, administrativeRepairs: attempts.filter((attempt) => attempt.administrativeRepair === true).length,
|
|
232
|
+
measurement: { configuredSelection: 'envelope-recorded', acceptedSelection: reconciled === 0 ? 'not-natively-reconciled' : reconciled === attempts.length ? 'natively-reconciled' : 'partially-reconciled', backendModel: selections.some(item => item.backendModel !== 'unknown') ? 'provider-reported' : 'unknown', tokenUsage: 'unknown', savings: 'unverified' }, ...(incidents.length ? { incidents } : {}) };
|
|
138
233
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queryLocalClaudeScope = queryLocalClaudeScope;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const paths_1 = require("../paths");
|
|
10
|
+
const machine_key_1 = require("./machine-key");
|
|
11
|
+
const native_claude_machine_1 = require("./native-claude-machine");
|
|
12
|
+
const native_claude_config_1 = require("./native-claude-config");
|
|
13
|
+
const native_runtime_1 = require("./native-runtime");
|
|
14
|
+
const hook_diagnostics_1 = require("./hook-diagnostics");
|
|
15
|
+
/** Read-only local facts; no model inference or machine-key creation. */
|
|
16
|
+
async function queryLocalClaudeScope(cwd, runtimeKind, deps = {}) {
|
|
17
|
+
if (typeof cwd !== 'string' || !path_1.default.isAbsolute(cwd) || cwd.includes('\0') || typeof runtimeKind !== 'string'
|
|
18
|
+
|| !/^[a-z0-9][a-z0-9_-]{0,127}$/.test(runtimeKind))
|
|
19
|
+
throw new Error('local Claude scope input is invalid');
|
|
20
|
+
const hooks = (deps.readRoutingHookStatus ?? hook_diagnostics_1.readRoutingHookStatus)();
|
|
21
|
+
if (deps.requireHookHealthy !== false && (hooks.state === 'failed' || hooks.state === 'invalid'))
|
|
22
|
+
return { state: 'unverified', reason: 'HOOK_CAPTURE_FAILED' };
|
|
23
|
+
const key = (deps.readMachineKey ?? machine_key_1.readMachineKey)((0, paths_1.awmHome)());
|
|
24
|
+
if (!key)
|
|
25
|
+
return { state: 'unverified', reason: 'MACHINE_KEY_MISSING' };
|
|
26
|
+
try {
|
|
27
|
+
const [auth, runtime] = await Promise.all([
|
|
28
|
+
(deps.queryClaudeAuthStatus ?? native_claude_machine_1.queryClaudeAuthStatus)({ command: 'claude', args: ['auth', 'status'], timeoutMs: 10000, key }),
|
|
29
|
+
(deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'claude', args: ['--version'], timeoutMs: 10000 }),
|
|
30
|
+
]);
|
|
31
|
+
if (auth.accountState !== 'identified' || !auth.accountScopeDigest)
|
|
32
|
+
return { state: 'unverified', reason: 'ACCOUNT_UNVERIFIED' };
|
|
33
|
+
const configDigest = (deps.readClaudeModelConfigDigest ?? native_claude_config_1.readClaudeModelConfigDigest)({ cwd, configDir: path_1.default.resolve(process.env.CLAUDE_CONFIG_DIR ?? path_1.default.join(os_1.default.homedir(), '.claude')),
|
|
34
|
+
key, environment: process.env });
|
|
35
|
+
return { state: 'current', scope: { runtime: { target: 'claude-code', kind: runtimeKind, version: runtime.version,
|
|
36
|
+
accountScopeDigest: auth.accountScopeDigest }, binaryDigest: runtime.binaryDigest, configDigest } };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { state: 'unverified', reason: 'NATIVE_QUERY_FAILED' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queryLocalCodexScope = queryLocalCodexScope;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const paths_1 = require("../paths");
|
|
9
|
+
const machine_key_1 = require("./machine-key");
|
|
10
|
+
const native_codex_machine_1 = require("./native-codex-machine");
|
|
11
|
+
const native_runtime_1 = require("./native-runtime");
|
|
12
|
+
/** Read-only, no-token local facts for the same cwd as the routed work. */
|
|
13
|
+
async function queryLocalCodexScope(cwd, runtimeKind, deps = {}) {
|
|
14
|
+
if (typeof cwd !== 'string' || !path_1.default.isAbsolute(cwd) || cwd.includes('\0') || typeof runtimeKind !== 'string'
|
|
15
|
+
|| !/^[a-z0-9][a-z0-9_-]{0,127}$/.test(runtimeKind))
|
|
16
|
+
throw new Error('local Codex scope input is invalid');
|
|
17
|
+
const key = (deps.readMachineKey ?? machine_key_1.readMachineKey)((0, paths_1.awmHome)());
|
|
18
|
+
if (!key)
|
|
19
|
+
return { state: 'unverified', reason: 'MACHINE_KEY_MISSING' };
|
|
20
|
+
try {
|
|
21
|
+
const [machine, executable] = await Promise.all([
|
|
22
|
+
(deps.queryCodexMachineFacts ?? native_codex_machine_1.queryCodexMachineFacts)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000, cwd, key }),
|
|
23
|
+
(deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'codex', args: ['--version'], timeoutMs: 10000 }),
|
|
24
|
+
]);
|
|
25
|
+
if (machine.accountState !== 'identified' || !machine.accountScopeDigest)
|
|
26
|
+
return { state: 'unverified', reason: 'ACCOUNT_UNVERIFIED' };
|
|
27
|
+
return { state: 'current', scope: { runtime: { target: 'codex', kind: runtimeKind, version: executable.version,
|
|
28
|
+
accountScopeDigest: machine.accountScopeDigest }, binaryDigest: executable.binaryDigest, configDigest: machine.configDigest } };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { state: 'unverified', reason: 'NATIVE_QUERY_FAILED' };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryLocalEventScope = queryLocalEventScope;
|
|
4
|
+
const local_claude_scope_1 = require("./local-claude-scope");
|
|
5
|
+
const local_codex_scope_1 = require("./local-codex-scope");
|
|
6
|
+
/** Provider-specific, read-only scope discovery for dispatch-time drift checks. */
|
|
7
|
+
async function queryLocalEventScope(cwd, target, runtimeKind) {
|
|
8
|
+
if (target === 'codex')
|
|
9
|
+
return (0, local_codex_scope_1.queryLocalCodexScope)(cwd, runtimeKind);
|
|
10
|
+
if (target === 'claude-code')
|
|
11
|
+
return (0, local_claude_scope_1.queryLocalClaudeScope)(cwd, runtimeKind);
|
|
12
|
+
return { state: 'unverified', reason: 'PROVIDER_UNSUPPORTED' };
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readMachineKey = readMachineKey;
|
|
7
|
+
exports.ensureMachineKey = ensureMachineKey;
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const native_bridge_1 = require("../secure-fs/native-bridge");
|
|
12
|
+
const KEY_NAME = 'routing-machine.key';
|
|
13
|
+
function keyPath(root) {
|
|
14
|
+
if (typeof root !== 'string' || !path_1.default.isAbsolute(root) || path_1.default.normalize(root) !== root || root.includes('\0'))
|
|
15
|
+
throw new Error('routing machine root must be an absolute normalized path');
|
|
16
|
+
for (let dir = root;; dir = path_1.default.dirname(dir)) {
|
|
17
|
+
const stat = fs_1.default.lstatSync(dir);
|
|
18
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
19
|
+
throw new Error('routing machine root has unsafe or symlinked ancestor');
|
|
20
|
+
if (dir === path_1.default.dirname(dir))
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
return path_1.default.join(root, KEY_NAME);
|
|
24
|
+
}
|
|
25
|
+
/** Read-only status path; never initializes machine state during diagnostics. */
|
|
26
|
+
function readMachineKey(root) {
|
|
27
|
+
const file = keyPath(root);
|
|
28
|
+
try {
|
|
29
|
+
const stat = fs_1.default.lstatSync(file);
|
|
30
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
31
|
+
throw new Error('routing machine key is unsafe or symlinked');
|
|
32
|
+
if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)
|
|
33
|
+
throw new Error('routing machine key permissions are unsafe');
|
|
34
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 32).bytes;
|
|
35
|
+
if (bytes.length !== 32)
|
|
36
|
+
throw new Error('routing machine key length is invalid');
|
|
37
|
+
return Buffer.from(bytes);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT')
|
|
41
|
+
return null;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Only explicit setup may call this producer. No secret bytes are reported. */
|
|
46
|
+
function ensureMachineKey(root) {
|
|
47
|
+
keyPath(root);
|
|
48
|
+
return native_bridge_1.secureFs.withProjectLease(root, () => {
|
|
49
|
+
const existing = readMachineKey(root);
|
|
50
|
+
if (existing)
|
|
51
|
+
return existing;
|
|
52
|
+
const bytes = (0, crypto_1.randomBytes)(32);
|
|
53
|
+
native_bridge_1.secureFs.writeProjectTransaction(root, KEY_NAME, bytes, { mode: 'create', createParents: false });
|
|
54
|
+
return bytes;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isNativeClaudeAgentDeclaration = isNativeClaudeAgentDeclaration;
|
|
7
|
+
exports.readClaudeAgentDeclaredModel = readClaudeAgentDeclaredModel;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
const native_bridge_1 = require("../secure-fs/native-bridge");
|
|
12
|
+
const observedDeclarations = new WeakMap();
|
|
13
|
+
function isNativeClaudeAgentDeclaration(value) {
|
|
14
|
+
return !!value && typeof value === 'object' && observedDeclarations.get(value) === JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
function safeDirectory(dir) {
|
|
17
|
+
for (let current = dir;; current = path_1.default.dirname(current)) {
|
|
18
|
+
const stat = fs_1.default.lstatSync(current);
|
|
19
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
20
|
+
throw new Error('Claude agent definition has an unsafe ancestor');
|
|
21
|
+
if (current === path_1.default.dirname(current))
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** An explicit custom-agent model is necessary, but the effective model must
|
|
26
|
+
* still be independently observed in the native child transcript. Built-in
|
|
27
|
+
* agents, aliases and an inherited/default model cannot enroll a selection. */
|
|
28
|
+
function readClaudeAgentDeclaredModel(input) {
|
|
29
|
+
if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
|
|
30
|
+
|| path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
|
|
31
|
+
|| input.cwd.includes('\0') || input.configDir.includes('\0')
|
|
32
|
+
|| typeof input.agentType !== 'string' || !/^awm-[A-Za-z0-9][A-Za-z0-9_-]{0,123}$/.test(input.agentType))
|
|
33
|
+
throw new Error('Claude agent definition identity is invalid');
|
|
34
|
+
const dirs = [path_1.default.join(input.configDir, 'agents'), path_1.default.join(input.cwd, '.claude', 'agents')];
|
|
35
|
+
const declared = [];
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
try {
|
|
38
|
+
safeDirectory(dir);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT')
|
|
42
|
+
continue;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md'));
|
|
46
|
+
if (names.length > 256)
|
|
47
|
+
throw new Error('Claude agent definitions exceed limit');
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
const file = path_1.default.join(dir, name);
|
|
50
|
+
const stat = fs_1.default.lstatSync(file);
|
|
51
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
52
|
+
throw new Error('Claude agent definition is unsafe');
|
|
53
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
54
|
+
const content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
55
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
56
|
+
if (!match)
|
|
57
|
+
continue;
|
|
58
|
+
let frontmatter;
|
|
59
|
+
try {
|
|
60
|
+
frontmatter = js_yaml_1.default.load(match[1]);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error('Claude agent frontmatter is invalid');
|
|
64
|
+
}
|
|
65
|
+
if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter))
|
|
66
|
+
continue;
|
|
67
|
+
const row = frontmatter;
|
|
68
|
+
if (row.name !== input.agentType)
|
|
69
|
+
continue;
|
|
70
|
+
if (typeof row.description !== 'string' || !row.description.trim())
|
|
71
|
+
throw new Error('Claude agent description is missing');
|
|
72
|
+
if (typeof row.model !== 'string' || !/^claude-[a-z0-9][a-z0-9.-]{1,126}$/.test(row.model))
|
|
73
|
+
throw new Error('Claude agent requires an explicit full model ID; aliases and inherit are unverified');
|
|
74
|
+
declared.push(row.model);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (declared.length !== 1)
|
|
78
|
+
throw new Error(declared.length ? 'Claude agent definition is ambiguous' : 'Claude agent has no explicit custom definition');
|
|
79
|
+
const result = { agentType: input.agentType, modelId: declared[0] };
|
|
80
|
+
observedDeclarations.set(result, JSON.stringify(result));
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readClaudeModelConfigDigest = readClaudeModelConfigDigest;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const native_bridge_1 = require("../secure-fs/native-bridge");
|
|
11
|
+
const json_1 = require("../plan/json");
|
|
12
|
+
const MODEL_KEYS = ['model', 'effortLevel', 'availableModels', 'enforceAvailableModels', 'modelOverrides'];
|
|
13
|
+
const ENV_KEYS = ['ANTHROPIC_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL_FORCE'];
|
|
14
|
+
function safeDirectory(dir) {
|
|
15
|
+
for (let current = dir;; current = path_1.default.dirname(current)) {
|
|
16
|
+
const stat = fs_1.default.lstatSync(current);
|
|
17
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
18
|
+
throw new Error('Claude settings have an unsafe or symlinked ancestor');
|
|
19
|
+
if (current === path_1.default.dirname(current))
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function settings(file) {
|
|
24
|
+
let stat;
|
|
25
|
+
try {
|
|
26
|
+
stat = fs_1.default.lstatSync(file);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT')
|
|
30
|
+
return null;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
safeDirectory(path_1.default.dirname(file));
|
|
34
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
35
|
+
throw new Error('Claude settings file is unsafe or symlinked');
|
|
36
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
37
|
+
const value = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
38
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
39
|
+
throw new Error('Claude settings must be an object');
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function agentDefinitions(dir) {
|
|
43
|
+
let stat;
|
|
44
|
+
try {
|
|
45
|
+
stat = fs_1.default.lstatSync(dir);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code === 'ENOENT')
|
|
49
|
+
return [];
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
safeDirectory(path_1.default.dirname(dir));
|
|
53
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
54
|
+
throw new Error('Claude agents directory is unsafe or symlinked');
|
|
55
|
+
const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md')).sort();
|
|
56
|
+
if (names.length > 256)
|
|
57
|
+
throw new Error('Claude agent definitions exceed limit');
|
|
58
|
+
return names.map(name => {
|
|
59
|
+
const file = path_1.default.join(dir, name);
|
|
60
|
+
const entry = fs_1.default.lstatSync(file);
|
|
61
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
62
|
+
throw new Error('Claude agent definition is unsafe or symlinked');
|
|
63
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
64
|
+
return [name, (0, crypto_1.createHash)('sha256').update(bytes).digest('hex')];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Fingerprints only locally visible model controls. Managed/server-side policy
|
|
68
|
+
* is not represented here; a native child observation remains mandatory. */
|
|
69
|
+
function readClaudeModelConfigDigest(input) {
|
|
70
|
+
if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
|
|
71
|
+
|| path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
|
|
72
|
+
|| input.cwd.includes('\0') || input.configDir.includes('\0') || !Buffer.isBuffer(input.key) || input.key.length !== 32
|
|
73
|
+
|| !input.environment || typeof input.environment !== 'object' || Array.isArray(input.environment))
|
|
74
|
+
throw new Error('Claude model config input is invalid');
|
|
75
|
+
safeDirectory(input.cwd);
|
|
76
|
+
safeDirectory(input.configDir);
|
|
77
|
+
const files = [path_1.default.join(input.configDir, 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.local.json')];
|
|
78
|
+
const projection = files.map(file => {
|
|
79
|
+
const value = settings(file);
|
|
80
|
+
if (!value)
|
|
81
|
+
return null;
|
|
82
|
+
const model = {};
|
|
83
|
+
for (const key of MODEL_KEYS)
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
85
|
+
model[key] = value[key];
|
|
86
|
+
if (value.env && typeof value.env === 'object' && !Array.isArray(value.env)) {
|
|
87
|
+
const env = {};
|
|
88
|
+
for (const key of ENV_KEYS)
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(value.env, key))
|
|
90
|
+
env[key] = value.env[key];
|
|
91
|
+
if (Object.keys(env).length)
|
|
92
|
+
model.env = env;
|
|
93
|
+
}
|
|
94
|
+
return model;
|
|
95
|
+
});
|
|
96
|
+
const environment = {};
|
|
97
|
+
for (const key of ENV_KEYS) {
|
|
98
|
+
const value = input.environment[key];
|
|
99
|
+
if (value !== undefined) {
|
|
100
|
+
if (typeof value !== 'string' || value.length > 4096)
|
|
101
|
+
throw new Error('Claude model environment is invalid');
|
|
102
|
+
environment[key] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const agents = [agentDefinitions(path_1.default.join(input.configDir, 'agents')), agentDefinitions(path_1.default.join(input.cwd, '.claude', 'agents'))];
|
|
106
|
+
return (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify({ projection, agents, environment })).digest('hex');
|
|
107
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryClaudeAuthStatus = queryClaudeAuthStatus;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
/** `claude auth status` is documented as a JSON, no-inference command. The
|
|
7
|
+
* account shape varies by auth method; incomplete identity stays unverified. */
|
|
8
|
+
function queryClaudeAuthStatus(input) {
|
|
9
|
+
if (!input || typeof input !== 'object' || typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
|
|
10
|
+
|| !Array.isArray(input.args) || input.args.length > 16 || input.args.some(value => typeof value !== 'string' || value.length > 65536)
|
|
11
|
+
|| !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000
|
|
12
|
+
|| !Buffer.isBuffer(input.key) || input.key.length !== 32)
|
|
13
|
+
throw new Error('Claude auth query is invalid');
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['ignore', 'pipe', 'pipe'], shell: false });
|
|
16
|
+
let settled = false;
|
|
17
|
+
let buffer = '';
|
|
18
|
+
const finish = (error, facts) => {
|
|
19
|
+
if (settled)
|
|
20
|
+
return;
|
|
21
|
+
settled = true;
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
if (error) {
|
|
24
|
+
child.kill();
|
|
25
|
+
reject(error);
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
resolve(facts);
|
|
29
|
+
};
|
|
30
|
+
const timer = setTimeout(() => finish(new Error('Claude auth status timed out')), input.timeoutMs);
|
|
31
|
+
child.on('error', () => finish(new Error('Claude CLI could not be started')));
|
|
32
|
+
child.stdout.setEncoding('utf8');
|
|
33
|
+
child.stdout.on('data', (chunk) => { buffer += chunk; if (buffer.length > 64 * 1024)
|
|
34
|
+
finish(new Error('Claude auth status exceeds 64 KiB')); });
|
|
35
|
+
child.on('exit', code => {
|
|
36
|
+
if (code !== 0)
|
|
37
|
+
return finish(new Error('Claude auth status failed or is logged out'));
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(buffer);
|
|
40
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
41
|
+
throw new Error('shape');
|
|
42
|
+
const value = parsed;
|
|
43
|
+
if (value.loggedIn !== true)
|
|
44
|
+
throw new Error('login');
|
|
45
|
+
const fields = ['authMethod', 'apiProvider', 'email', 'orgId'];
|
|
46
|
+
const identified = fields.every(field => typeof value[field] === 'string' && value[field].length > 0 && value[field].length <= 320);
|
|
47
|
+
const subscriptionType = value.subscriptionType;
|
|
48
|
+
if (subscriptionType !== null && subscriptionType !== undefined && (typeof subscriptionType !== 'string' || subscriptionType.length > 128))
|
|
49
|
+
throw new Error('subscription');
|
|
50
|
+
const accountScopeDigest = identified ? (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify(fields.map(field => value[field]).concat([subscriptionType ?? null]))).digest('hex') : null;
|
|
51
|
+
finish(undefined, { provenance: 'claude-auth-status', accountState: accountScopeDigest ? 'identified' : 'unverified', accountScopeDigest, inferenceDispatched: false });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
finish(new Error('Claude auth status response is invalid'));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|