onto-mcp 0.4.12 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +137 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +575 -24
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +75 -3
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +282 -29
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +135 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +188 -47
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
- package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
- package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
- package/dist/core-runtime/reconstruct/record.js +42 -0
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
- package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
- package/dist/core-runtime/reconstruct/run.js +5198 -536
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
- package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +217 -5
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +12 -5
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +18 -3
- package/settings.example.json +77 -16
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { VERSION as ANTHROPIC_SDK_VERSION } from "@anthropic-ai/sdk/version";
|
|
3
|
+
import { VERSION as OPENAI_SDK_VERSION } from "openai/version";
|
|
4
|
+
import { normalizeLlmModelSwitcher, } from "./model-switcher.js";
|
|
5
|
+
import { resolveDispatchFallbackAdapterCapabilities, } from "./dispatch-fallback-adapter-capabilities.js";
|
|
6
|
+
import { StructuredDispatchError, } from "./structured-dispatch-error.js";
|
|
7
|
+
export class SemanticMapDispatchAccounting {
|
|
8
|
+
#entries = [];
|
|
9
|
+
record(entry) {
|
|
10
|
+
if (!Number.isSafeInteger(entry.actual_adapter_request_count) ||
|
|
11
|
+
entry.actual_adapter_request_count < 0) {
|
|
12
|
+
throw new Error("semantic-map dispatch accounting requires a non-negative adapter request count.");
|
|
13
|
+
}
|
|
14
|
+
const existing = this.#entries.find((candidate) => candidate.logical_dispatch_id === entry.logical_dispatch_id);
|
|
15
|
+
if (existing) {
|
|
16
|
+
if (existing.observation_id !== entry.observation_id ||
|
|
17
|
+
existing.execution_source !== entry.execution_source ||
|
|
18
|
+
existing.operation !== entry.operation ||
|
|
19
|
+
existing.descriptor_id !== entry.descriptor_id ||
|
|
20
|
+
existing.capability_instance_id !== entry.capability_instance_id) {
|
|
21
|
+
throw new Error(`semantic-map dispatch accounting logical_dispatch_id ${entry.logical_dispatch_id} changed identity.`);
|
|
22
|
+
}
|
|
23
|
+
existing.actual_adapter_request_count += entry.actual_adapter_request_count;
|
|
24
|
+
existing.disposition = entry.disposition;
|
|
25
|
+
existing.failure_class = entry.failure_class;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.#entries.push(structuredClone(entry));
|
|
29
|
+
}
|
|
30
|
+
entries() {
|
|
31
|
+
return this.#entries.map((entry) => structuredClone(entry));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const OFFICIAL_BASE_URLS = {
|
|
35
|
+
openai: "https://api.openai.com/v1",
|
|
36
|
+
anthropic: "https://api.anthropic.com",
|
|
37
|
+
};
|
|
38
|
+
const ROUTE_AFFECTING_ENV = {
|
|
39
|
+
openai: [
|
|
40
|
+
"OPENAI_ADMIN_KEY",
|
|
41
|
+
"OPENAI_API_VERSION",
|
|
42
|
+
"OPENAI_BASE_URL",
|
|
43
|
+
"OPENAI_CUSTOM_HEADERS",
|
|
44
|
+
"OPENAI_ENDPOINT",
|
|
45
|
+
"OPENAI_LOG",
|
|
46
|
+
"OPENAI_ORG_ID",
|
|
47
|
+
"OPENAI_PROJECT_ID",
|
|
48
|
+
"OPENAI_WEBHOOK_SECRET",
|
|
49
|
+
],
|
|
50
|
+
anthropic: [
|
|
51
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
52
|
+
"ANTHROPIC_BASE_URL",
|
|
53
|
+
"ANTHROPIC_CONFIG_DIR",
|
|
54
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
55
|
+
"ANTHROPIC_ENVIRONMENT_ID",
|
|
56
|
+
"ANTHROPIC_ENVIRONMENT_KEY",
|
|
57
|
+
"ANTHROPIC_FEDERATION_RULE_ID",
|
|
58
|
+
"ANTHROPIC_IDENTITY_TOKEN",
|
|
59
|
+
"ANTHROPIC_IDENTITY_TOKEN_FILE",
|
|
60
|
+
"ANTHROPIC_LOG",
|
|
61
|
+
"ANTHROPIC_ORGANIZATION_ID",
|
|
62
|
+
"ANTHROPIC_PROFILE",
|
|
63
|
+
"ANTHROPIC_SCOPE",
|
|
64
|
+
"ANTHROPIC_SERVICE_ACCOUNT_ID",
|
|
65
|
+
"ANTHROPIC_SESSION_ID",
|
|
66
|
+
"ANTHROPIC_WEBHOOK_SIGNING_KEY",
|
|
67
|
+
"ANTHROPIC_WORKSPACE_ID",
|
|
68
|
+
"ANTHROPIC_WORK_ID",
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
const ACCEPTED_REASONING_EFFORTS = {
|
|
72
|
+
openai: new Set(["none", "minimal", "low", "medium", "high", "xhigh"]),
|
|
73
|
+
anthropic: new Set(["low", "medium", "high", "xhigh", "max"]),
|
|
74
|
+
};
|
|
75
|
+
function canonicalJson(value) {
|
|
76
|
+
if (value === null || typeof value !== "object")
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
if (Array.isArray(value))
|
|
79
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
80
|
+
const object = value;
|
|
81
|
+
return `{${Object.keys(object)
|
|
82
|
+
.sort()
|
|
83
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`)
|
|
84
|
+
.join(",")}}`;
|
|
85
|
+
}
|
|
86
|
+
export function dispatchDescriptorProjection(preimage) {
|
|
87
|
+
return {
|
|
88
|
+
...preimage,
|
|
89
|
+
descriptor_id: crypto.createHash("sha256").update(canonicalJson(preimage)).digest("hex"),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function assertSealedEnvironment(provider) {
|
|
93
|
+
const present = ROUTE_AFFECTING_ENV[provider].filter((name) => process.env[name] !== undefined);
|
|
94
|
+
if (present.length > 0) {
|
|
95
|
+
throw new Error(`dispatch fallback sealed ${provider} route rejects route-affecting ambient environment: ${present.join(", ")}.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function readCapturedCredential(envName) {
|
|
99
|
+
const credential = process.env[envName];
|
|
100
|
+
if (typeof credential !== "string" || credential.trim().length === 0) {
|
|
101
|
+
throw new Error(`dispatch fallback credential environment ${envName} is missing or empty.`);
|
|
102
|
+
}
|
|
103
|
+
return credential;
|
|
104
|
+
}
|
|
105
|
+
function errorMetadata(error) {
|
|
106
|
+
const record = error !== null && typeof error === "object"
|
|
107
|
+
? error
|
|
108
|
+
: {};
|
|
109
|
+
const nestedCode = record.error?.code ?? record.error?.type;
|
|
110
|
+
return {
|
|
111
|
+
status: typeof record.status === "number" ? record.status : null,
|
|
112
|
+
code: typeof record.code === "string"
|
|
113
|
+
? record.code
|
|
114
|
+
: typeof nestedCode === "string" ? nestedCode : null,
|
|
115
|
+
name: typeof record.constructor?.name === "string" &&
|
|
116
|
+
record.constructor.name !== "Error"
|
|
117
|
+
? record.constructor.name
|
|
118
|
+
: typeof record.name === "string"
|
|
119
|
+
? record.name
|
|
120
|
+
: "UnknownError",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function structuredEvidence(args) {
|
|
124
|
+
const common = {
|
|
125
|
+
descriptor_id: args.descriptorId,
|
|
126
|
+
capability_instance_id: args.capabilityInstanceId,
|
|
127
|
+
logical_dispatch_id: args.logicalDispatchId,
|
|
128
|
+
actual_adapter_request_count: args.adapterRequestCount,
|
|
129
|
+
};
|
|
130
|
+
const metadata = errorMetadata(args.error);
|
|
131
|
+
if (metadata.status === 429) {
|
|
132
|
+
return { ...common, failure_class: "rate_limit", failure_code: "http_429", source: "sdk_http_status" };
|
|
133
|
+
}
|
|
134
|
+
if (metadata.code === "rate_limit_error" || metadata.code === "rate_limit_exceeded") {
|
|
135
|
+
return { ...common, failure_class: "rate_limit", failure_code: "provider_rate_limit_code", source: "sdk_error_code" };
|
|
136
|
+
}
|
|
137
|
+
if (metadata.status === 401 || metadata.status === 403) {
|
|
138
|
+
return {
|
|
139
|
+
...common,
|
|
140
|
+
failure_class: "auth",
|
|
141
|
+
failure_code: metadata.status === 401 ? "http_401" : "http_403",
|
|
142
|
+
source: "sdk_http_status",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (metadata.code === "authentication_error" || metadata.code === "invalid_api_key") {
|
|
146
|
+
return { ...common, failure_class: "auth", failure_code: "provider_auth_code", source: "sdk_error_code" };
|
|
147
|
+
}
|
|
148
|
+
if (metadata.status !== null && metadata.status >= 500) {
|
|
149
|
+
return { ...common, failure_class: "transport", failure_code: "http_5xx", source: "sdk_http_status" };
|
|
150
|
+
}
|
|
151
|
+
if (metadata.name.includes("Timeout")) {
|
|
152
|
+
return { ...common, failure_class: "transport", failure_code: "timeout", source: "sdk_exception_type" };
|
|
153
|
+
}
|
|
154
|
+
if (metadata.name.includes("Connection")) {
|
|
155
|
+
return { ...common, failure_class: "transport", failure_code: "connection_failure", source: "sdk_exception_type" };
|
|
156
|
+
}
|
|
157
|
+
if (metadata.status !== null && metadata.status >= 400 && metadata.status < 500) {
|
|
158
|
+
return { ...common, failure_class: null, failure_code: "provider_request_rejected", source: "sdk_http_status" };
|
|
159
|
+
}
|
|
160
|
+
if (metadata.code !== null) {
|
|
161
|
+
return { ...common, failure_class: null, failure_code: "adapter_unknown", source: "sdk_error_code" };
|
|
162
|
+
}
|
|
163
|
+
return { ...common, failure_class: null, failure_code: "adapter_unknown", source: "sdk_exception_type" };
|
|
164
|
+
}
|
|
165
|
+
function noOpLogger() {
|
|
166
|
+
const noOp = () => undefined;
|
|
167
|
+
return { debug: noOp, info: noOp, warn: noOp, error: noOp };
|
|
168
|
+
}
|
|
169
|
+
export async function createSealedDispatchCapability(args) {
|
|
170
|
+
const selection = normalizeLlmModelSwitcher(args.llm);
|
|
171
|
+
if (!selection || selection.execution_route !== "direct_model_call" || selection.auth !== "api_key") {
|
|
172
|
+
throw new Error("dispatch fallback requires an explicit api_key direct-model selection.");
|
|
173
|
+
}
|
|
174
|
+
if (selection.model_provider !== "openai" && selection.model_provider !== "anthropic") {
|
|
175
|
+
throw new Error(`dispatch fallback has no sealed official SDK route for ${selection.model_provider}.`);
|
|
176
|
+
}
|
|
177
|
+
if (selection.base_url !== undefined) {
|
|
178
|
+
throw new Error("dispatch fallback rejects custom base_url; only the official SDK endpoint is supported.");
|
|
179
|
+
}
|
|
180
|
+
if (!selection.model_id || !selection.api_key_env || !selection.reasoning_effort) {
|
|
181
|
+
throw new Error("dispatch fallback requires model, api_key_env, and effort in settings.");
|
|
182
|
+
}
|
|
183
|
+
if (!ACCEPTED_REASONING_EFFORTS[selection.model_provider].has(selection.reasoning_effort)) {
|
|
184
|
+
throw new Error(`dispatch fallback rejects unsupported effort '${selection.reasoning_effort}'.`);
|
|
185
|
+
}
|
|
186
|
+
assertSealedEnvironment(selection.model_provider);
|
|
187
|
+
const credential = readCapturedCredential(selection.api_key_env);
|
|
188
|
+
const modelId = selection.model_id;
|
|
189
|
+
const reasoningEffort = selection.reasoning_effort;
|
|
190
|
+
const sealedSelection = Object.freeze({ ...selection });
|
|
191
|
+
const isOpenAI = selection.model_provider === "openai";
|
|
192
|
+
const protocolVersion = isOpenAI
|
|
193
|
+
? "openai_responses_v1"
|
|
194
|
+
: "anthropic_messages_v1";
|
|
195
|
+
const adapterPackageVersion = isOpenAI ? OPENAI_SDK_VERSION : ANTHROPIC_SDK_VERSION;
|
|
196
|
+
const resolvedCapabilities = resolveDispatchFallbackAdapterCapabilities({
|
|
197
|
+
executionAdapter: selection.execution_adapter,
|
|
198
|
+
adapterPackageVersion,
|
|
199
|
+
protocolVersion,
|
|
200
|
+
});
|
|
201
|
+
if (!resolvedCapabilities) {
|
|
202
|
+
throw new Error(`dispatch fallback adapter capability is not registered for ${selection.execution_adapter}@${adapterPackageVersion}/${protocolVersion}.`);
|
|
203
|
+
}
|
|
204
|
+
const capabilities = Object.freeze({ ...resolvedCapabilities });
|
|
205
|
+
const capabilityInstanceId = crypto.randomUUID();
|
|
206
|
+
const descriptor = Object.freeze(dispatchDescriptorProjection({
|
|
207
|
+
model_provider: selection.model_provider,
|
|
208
|
+
model_id: selection.model_id,
|
|
209
|
+
execution_adapter: selection.execution_adapter,
|
|
210
|
+
protocol_version: protocolVersion,
|
|
211
|
+
adapter_package_version: adapterPackageVersion,
|
|
212
|
+
auth: selection.auth,
|
|
213
|
+
endpoint_kind: "official_sdk",
|
|
214
|
+
service_tier: selection.service_tier ?? null,
|
|
215
|
+
reasoning_effort: selection.reasoning_effort,
|
|
216
|
+
dispatch_role: args.operation,
|
|
217
|
+
}));
|
|
218
|
+
let totalAdapterRequests = 0;
|
|
219
|
+
const sealedFetch = globalThis.fetch.bind(globalThis);
|
|
220
|
+
const countedFetch = async (input, init) => {
|
|
221
|
+
totalAdapterRequests += 1;
|
|
222
|
+
return sealedFetch(input, init);
|
|
223
|
+
};
|
|
224
|
+
const openAIClient = isOpenAI
|
|
225
|
+
? new (await import("openai")).default({
|
|
226
|
+
apiKey: credential,
|
|
227
|
+
adminAPIKey: null,
|
|
228
|
+
organization: null,
|
|
229
|
+
project: null,
|
|
230
|
+
webhookSecret: null,
|
|
231
|
+
baseURL: OFFICIAL_BASE_URLS.openai,
|
|
232
|
+
maxRetries: 0,
|
|
233
|
+
fetch: countedFetch,
|
|
234
|
+
logLevel: "off",
|
|
235
|
+
logger: noOpLogger(),
|
|
236
|
+
})
|
|
237
|
+
: null;
|
|
238
|
+
const anthropicClient = !isOpenAI
|
|
239
|
+
? new (await import("@anthropic-ai/sdk")).default({
|
|
240
|
+
apiKey: credential,
|
|
241
|
+
authToken: null,
|
|
242
|
+
credentials: null,
|
|
243
|
+
config: null,
|
|
244
|
+
profile: null,
|
|
245
|
+
webhookKey: null,
|
|
246
|
+
baseURL: OFFICIAL_BASE_URLS.anthropic,
|
|
247
|
+
maxRetries: 0,
|
|
248
|
+
fetch: countedFetch,
|
|
249
|
+
logLevel: "off",
|
|
250
|
+
logger: noOpLogger(),
|
|
251
|
+
})
|
|
252
|
+
: null;
|
|
253
|
+
return {
|
|
254
|
+
selection: sealedSelection,
|
|
255
|
+
public_descriptor: descriptor,
|
|
256
|
+
capabilities,
|
|
257
|
+
capability_instance_id: capabilityInstanceId,
|
|
258
|
+
async invokeOnce(input) {
|
|
259
|
+
if (!Number.isSafeInteger(input.max_tokens) || input.max_tokens < 1) {
|
|
260
|
+
throw new Error("sealed dispatch max_tokens must be a positive safe integer.");
|
|
261
|
+
}
|
|
262
|
+
const logicalDispatchId = input.logical_dispatch_id ?? crypto.randomUUID();
|
|
263
|
+
const requestCountBefore = totalAdapterRequests;
|
|
264
|
+
try {
|
|
265
|
+
let result;
|
|
266
|
+
if (openAIClient) {
|
|
267
|
+
const response = await openAIClient.responses.create({
|
|
268
|
+
model: modelId,
|
|
269
|
+
instructions: input.system_prompt,
|
|
270
|
+
input: input.user_prompt,
|
|
271
|
+
max_output_tokens: input.max_tokens,
|
|
272
|
+
store: false,
|
|
273
|
+
reasoning: { effort: reasoningEffort },
|
|
274
|
+
});
|
|
275
|
+
if (response.status !== "completed") {
|
|
276
|
+
throw new StructuredDispatchError({
|
|
277
|
+
descriptor_id: descriptor.descriptor_id,
|
|
278
|
+
capability_instance_id: capabilityInstanceId,
|
|
279
|
+
logical_dispatch_id: logicalDispatchId,
|
|
280
|
+
actual_adapter_request_count: totalAdapterRequests - requestCountBefore,
|
|
281
|
+
failure_class: null,
|
|
282
|
+
failure_code: "adapter_contract_violation",
|
|
283
|
+
source: "sdk_exception_type",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
result = {
|
|
287
|
+
text: response.output_text ?? "",
|
|
288
|
+
input_tokens: response.usage?.input_tokens ?? 0,
|
|
289
|
+
output_tokens: response.usage?.output_tokens ?? 0,
|
|
290
|
+
model_id: modelId,
|
|
291
|
+
effective_base_url: OFFICIAL_BASE_URLS.openai,
|
|
292
|
+
declared_billing_mode: "per_token",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const response = await anthropicClient.messages.create({
|
|
297
|
+
model: modelId,
|
|
298
|
+
max_tokens: input.max_tokens,
|
|
299
|
+
system: input.system_prompt,
|
|
300
|
+
messages: [{ role: "user", content: input.user_prompt }],
|
|
301
|
+
thinking: { type: "adaptive" },
|
|
302
|
+
output_config: { effort: reasoningEffort },
|
|
303
|
+
});
|
|
304
|
+
result = {
|
|
305
|
+
text: response.content
|
|
306
|
+
.filter((block) => block.type === "text")
|
|
307
|
+
.map((block) => block.text)
|
|
308
|
+
.join("\n"),
|
|
309
|
+
input_tokens: response.usage.input_tokens,
|
|
310
|
+
output_tokens: response.usage.output_tokens,
|
|
311
|
+
model_id: modelId,
|
|
312
|
+
effective_base_url: OFFICIAL_BASE_URLS.anthropic,
|
|
313
|
+
declared_billing_mode: "per_token",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const actualAdapterRequestCount = totalAdapterRequests - requestCountBefore;
|
|
317
|
+
if (actualAdapterRequestCount !== 1) {
|
|
318
|
+
throw new StructuredDispatchError({
|
|
319
|
+
descriptor_id: descriptor.descriptor_id,
|
|
320
|
+
capability_instance_id: capabilityInstanceId,
|
|
321
|
+
logical_dispatch_id: logicalDispatchId,
|
|
322
|
+
actual_adapter_request_count: actualAdapterRequestCount,
|
|
323
|
+
failure_class: null,
|
|
324
|
+
failure_code: "adapter_contract_violation",
|
|
325
|
+
source: "sdk_exception_type",
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
result,
|
|
330
|
+
logical_dispatch_id: logicalDispatchId,
|
|
331
|
+
actual_adapter_request_count: actualAdapterRequestCount,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
if (error instanceof StructuredDispatchError)
|
|
336
|
+
throw error;
|
|
337
|
+
const count = totalAdapterRequests - requestCountBefore;
|
|
338
|
+
if (count !== 1) {
|
|
339
|
+
throw new StructuredDispatchError({
|
|
340
|
+
descriptor_id: descriptor.descriptor_id,
|
|
341
|
+
capability_instance_id: capabilityInstanceId,
|
|
342
|
+
logical_dispatch_id: logicalDispatchId,
|
|
343
|
+
actual_adapter_request_count: count,
|
|
344
|
+
failure_class: null,
|
|
345
|
+
failure_code: "adapter_contract_violation",
|
|
346
|
+
source: "sdk_exception_type",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
throw new StructuredDispatchError(structuredEvidence({
|
|
350
|
+
error,
|
|
351
|
+
descriptorId: descriptor.descriptor_id,
|
|
352
|
+
capabilityInstanceId,
|
|
353
|
+
logicalDispatchId,
|
|
354
|
+
adapterRequestCount: count,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class StructuredDispatchError extends Error {
|
|
2
|
+
evidence;
|
|
3
|
+
constructor(evidence) {
|
|
4
|
+
super(`sealed dispatch failed: ${evidence.failure_code} ` +
|
|
5
|
+
`(descriptor=${evidence.descriptor_id}, logical_dispatch=${evidence.logical_dispatch_id})`);
|
|
6
|
+
this.name = "StructuredDispatchError";
|
|
7
|
+
this.evidence = evidence;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function readStructuredDispatchFailureEvidence(error) {
|
|
11
|
+
return error instanceof StructuredDispatchError ? error.evidence : null;
|
|
12
|
+
}
|
|
13
|
+
export function readStructuredDispatchFailureClass(error) {
|
|
14
|
+
return readStructuredDispatchFailureEvidence(error)?.failure_class ?? null;
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Markers that identify a directory as a Claude Code config dir.
|
|
6
|
+
*
|
|
7
|
+
* `settings.json` and `.credentials.json` are documented to live under
|
|
8
|
+
* `CLAUDE_CONFIG_DIR` (code.claude.com/docs/en/settings: "Credentials are
|
|
9
|
+
* stored in ~/.claude/.credentials.json, or under $CLAUDE_CONFIG_DIR if that
|
|
10
|
+
* variable is set"). `.claude.json` (user/local MCP + state) and `projects/`
|
|
11
|
+
* appear inside relocated profile dirs in practice. Requiring any one of these
|
|
12
|
+
* excludes sibling dirs like `.claude-sessions` that match the name glob but
|
|
13
|
+
* are not config roots.
|
|
14
|
+
*/
|
|
15
|
+
const CLAUDE_CONFIG_MARKERS = [
|
|
16
|
+
"settings.json",
|
|
17
|
+
".credentials.json",
|
|
18
|
+
".claude.json",
|
|
19
|
+
"projects",
|
|
20
|
+
];
|
|
21
|
+
function isDirectory(target) {
|
|
22
|
+
try {
|
|
23
|
+
return fsSync.statSync(target).isDirectory();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** True when `dir` is a directory carrying at least one Claude config marker. */
|
|
30
|
+
export function looksLikeClaudeConfigDir(dir) {
|
|
31
|
+
if (!isDirectory(dir))
|
|
32
|
+
return false;
|
|
33
|
+
return CLAUDE_CONFIG_MARKERS.some((marker) => fsSync.existsSync(path.join(dir, marker)));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Discover Claude Code config directories the user keeps under their home:
|
|
37
|
+
* the default `~/.claude` plus `~/.claude-*` variants that carry a config
|
|
38
|
+
* marker, plus any ambient `CLAUDE_CONFIG_DIR` (honored even if it lives
|
|
39
|
+
* outside the home glob). Returns absolute paths, deduped and deterministically
|
|
40
|
+
* sorted (default `~/.claude` first, then numeric-aware order).
|
|
41
|
+
*/
|
|
42
|
+
export function discoverClaudeProfiles(options = {}) {
|
|
43
|
+
const home = options.homeDir ?? os.homedir();
|
|
44
|
+
const found = new Set();
|
|
45
|
+
// Ambient CLAUDE_CONFIG_DIR is authoritative for "the active profile" and may
|
|
46
|
+
// point outside the home glob, so include it whenever it resolves to a dir.
|
|
47
|
+
const env = options.configDirEnv?.trim();
|
|
48
|
+
if (env && isDirectory(env))
|
|
49
|
+
found.add(path.resolve(env));
|
|
50
|
+
let entries = [];
|
|
51
|
+
try {
|
|
52
|
+
entries = fsSync.readdirSync(home);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
entries = [];
|
|
56
|
+
}
|
|
57
|
+
for (const name of entries) {
|
|
58
|
+
if (name !== ".claude" && !name.startsWith(".claude-"))
|
|
59
|
+
continue;
|
|
60
|
+
const dir = path.join(home, name);
|
|
61
|
+
if (looksLikeClaudeConfigDir(dir))
|
|
62
|
+
found.add(path.resolve(dir));
|
|
63
|
+
}
|
|
64
|
+
return [...found].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
65
|
+
}
|
|
@@ -42,6 +42,7 @@ export function createCliHost(spec, runner = defaultCommandRunner) {
|
|
|
42
42
|
const withNote = (summary) => spec.targetNote ? `${summary} (${spec.targetNote})` : summary;
|
|
43
43
|
return {
|
|
44
44
|
id: spec.id,
|
|
45
|
+
...(spec.key ? { key: spec.key } : {}),
|
|
45
46
|
displayName: spec.displayName,
|
|
46
47
|
detect,
|
|
47
48
|
plan(entry) {
|
|
@@ -121,13 +122,15 @@ export function createCliHost(spec, runner = defaultCommandRunner) {
|
|
|
121
122
|
*
|
|
122
123
|
* Config-dir resolution: explicit `configDir` wins; otherwise an ambient
|
|
123
124
|
* `CLAUDE_CONFIG_DIR` is honored (and shown in the plan); otherwise the claude
|
|
124
|
-
* default (`~/.claude`) applies.
|
|
125
|
+
* default (`~/.claude`) applies. `key`/`displayName` distinguish per-profile
|
|
126
|
+
* targets when `--all-claude-profiles` registers several at once.
|
|
125
127
|
*/
|
|
126
128
|
export function createClaudeCodeHost(options = {}) {
|
|
127
129
|
const effectiveDir = options.configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
128
130
|
const spec = {
|
|
129
131
|
id: "claude-code",
|
|
130
|
-
|
|
132
|
+
...(options.key ? { key: options.key } : {}),
|
|
133
|
+
displayName: options.displayName ?? "Claude Code",
|
|
131
134
|
cli: "claude",
|
|
132
135
|
addArgs: (entry) => [
|
|
133
136
|
"mcp",
|
|
@@ -23,6 +23,11 @@ const REVIEW_ACTOR_SEATS = {
|
|
|
23
23
|
lens: "worker",
|
|
24
24
|
synthesize: "worker",
|
|
25
25
|
};
|
|
26
|
+
// The PROVIDER-MANAGED subset of reconstruct actors — the two required seats
|
|
27
|
+
// this command (re)writes. Deliberately NARROWER than the full actor key set
|
|
28
|
+
// (settings-chain RECONSTRUCT_ACTOR_KEYS): optional per-role override seats
|
|
29
|
+
// (e.g. semantic_map_synthesize) are user-owned and must SURVIVE a provider
|
|
30
|
+
// switch — applyActorBlocks spreads the existing actors underneath.
|
|
26
31
|
const RECONSTRUCT_ACTORS = ["semantic_author", "confirmation_provider"];
|
|
27
32
|
/**
|
|
28
33
|
* Build a minimal actor LLM block from caller input. Only keys the caller
|
|
@@ -43,6 +48,8 @@ export function buildLlmBlock(input) {
|
|
|
43
48
|
block.service_tier = input.serviceTier;
|
|
44
49
|
if (input.apiKeyEnv !== undefined)
|
|
45
50
|
block.api_key_env = input.apiKeyEnv;
|
|
51
|
+
if (input.timeoutMs !== undefined)
|
|
52
|
+
block.timeout_ms = input.timeoutMs;
|
|
46
53
|
return block;
|
|
47
54
|
}
|
|
48
55
|
/**
|
|
@@ -114,7 +121,14 @@ function applyActorBlocks(existing, blocks) {
|
|
|
114
121
|
const reconstructExecution = isPlainObject(reconstruct.execution)
|
|
115
122
|
? { ...reconstruct.execution }
|
|
116
123
|
: {};
|
|
117
|
-
|
|
124
|
+
// Overwrite ONLY the provider-managed seats; preserve user-owned actor
|
|
125
|
+
// keys (e.g. the optional semantic_map_synthesize override seat) so a
|
|
126
|
+
// provider switch or bootstrap re-run cannot silently delete them
|
|
127
|
+
// (INV-MODEL-1 role-aware design §5.1-9).
|
|
128
|
+
const existingActors = isPlainObject(reconstructExecution.actors)
|
|
129
|
+
? reconstructExecution.actors
|
|
130
|
+
: {};
|
|
131
|
+
reconstructExecution.actors = { ...existingActors, ...blocks.reconstruct };
|
|
118
132
|
reconstruct.execution = reconstructExecution;
|
|
119
133
|
next.reconstruct = reconstruct;
|
|
120
134
|
}
|
|
@@ -233,6 +247,9 @@ const USAGE = [
|
|
|
233
247
|
" --effort <e> Reasoning effort",
|
|
234
248
|
" --service-tier <t> Service tier",
|
|
235
249
|
" --base-url <u> Base URL override",
|
|
250
|
+
" --timeout-ms <ms> Per-actor CLI-worker call timeout (positive integer ms).",
|
|
251
|
+
" Bounds the codex/claude direct-call worker; not the",
|
|
252
|
+
" api_key SDK route. See llm.timeout_ms.",
|
|
236
253
|
" --project Write the project seat (.onto/settings.json) instead",
|
|
237
254
|
" of the user seat (~/.onto/settings.json)",
|
|
238
255
|
" --help, -h Show this help",
|
|
@@ -246,6 +263,7 @@ export function parseConfigureProviderArgs(argv) {
|
|
|
246
263
|
effort: undefined,
|
|
247
264
|
serviceTier: undefined,
|
|
248
265
|
baseUrl: undefined,
|
|
266
|
+
timeoutMsRaw: undefined,
|
|
249
267
|
target: "user",
|
|
250
268
|
projectRoot: undefined,
|
|
251
269
|
settingsPath: undefined,
|
|
@@ -276,6 +294,9 @@ export function parseConfigureProviderArgs(argv) {
|
|
|
276
294
|
case "--base-url":
|
|
277
295
|
parsed.baseUrl = argv[++i];
|
|
278
296
|
break;
|
|
297
|
+
case "--timeout-ms":
|
|
298
|
+
parsed.timeoutMsRaw = argv[++i];
|
|
299
|
+
break;
|
|
279
300
|
case "--project":
|
|
280
301
|
parsed.target = "project";
|
|
281
302
|
break;
|
|
@@ -323,6 +344,16 @@ export async function runConfigureProvider(argv) {
|
|
|
323
344
|
console.error(USAGE);
|
|
324
345
|
return 1;
|
|
325
346
|
}
|
|
347
|
+
let timeoutMs;
|
|
348
|
+
if (parsed.timeoutMsRaw !== undefined) {
|
|
349
|
+
const n = Number(parsed.timeoutMsRaw);
|
|
350
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
351
|
+
console.error("[onto configure-provider] --timeout-ms must be a positive integer " +
|
|
352
|
+
`(ms); got "${parsed.timeoutMsRaw}".`);
|
|
353
|
+
return 1;
|
|
354
|
+
}
|
|
355
|
+
timeoutMs = n;
|
|
356
|
+
}
|
|
326
357
|
const input = {
|
|
327
358
|
provider: parsed.provider,
|
|
328
359
|
model: parsed.model,
|
|
@@ -333,6 +364,7 @@ export async function runConfigureProvider(argv) {
|
|
|
333
364
|
? { serviceTier: parsed.serviceTier }
|
|
334
365
|
: {}),
|
|
335
366
|
...(parsed.baseUrl !== undefined ? { baseUrl: parsed.baseUrl } : {}),
|
|
367
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
336
368
|
};
|
|
337
369
|
const targetSpec = {
|
|
338
370
|
target: parsed.target,
|
|
@@ -1,12 +1,37 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { createClaudeCodeHost, createCodexHost } from "./cli-host.js";
|
|
2
4
|
import { claudeDesktopConfigPath, createJsonConfigHost, cursorConfigPath, } from "./json-config-host.js";
|
|
5
|
+
/** Render an absolute path with the home prefix collapsed to `~` for display. */
|
|
6
|
+
function shortenHome(target) {
|
|
7
|
+
const home = os.homedir();
|
|
8
|
+
const rel = path.relative(home, target);
|
|
9
|
+
if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
|
|
10
|
+
return path.join("~", rel);
|
|
11
|
+
}
|
|
12
|
+
return target;
|
|
13
|
+
}
|
|
14
|
+
/** Build one Claude Code target per discovered profile config dir. */
|
|
15
|
+
function claudeProfileTargets(profiles) {
|
|
16
|
+
return profiles.map((dir) => createClaudeCodeHost({
|
|
17
|
+
configDir: dir,
|
|
18
|
+
key: `claude-code:${dir}`,
|
|
19
|
+
displayName: `Claude Code (${shortenHome(dir)})`,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
3
22
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
23
|
+
* The supported hosts in display order. CLI-backed hosts (Claude Code, Codex)
|
|
24
|
+
* come first; JSON-config hosts (Claude Desktop, Cursor) follow. When
|
|
25
|
+
* `claudeProfiles` is supplied, Claude Code expands into one target per profile.
|
|
6
26
|
*/
|
|
7
27
|
export function getDefaultHostTargets(options = {}) {
|
|
28
|
+
const claudeTargets = options.claudeProfiles && options.claudeProfiles.length > 0
|
|
29
|
+
? claudeProfileTargets(options.claudeProfiles)
|
|
30
|
+
: [
|
|
31
|
+
createClaudeCodeHost(options.claudeConfigDir ? { configDir: options.claudeConfigDir } : {}),
|
|
32
|
+
];
|
|
8
33
|
return [
|
|
9
|
-
|
|
34
|
+
...claudeTargets,
|
|
10
35
|
createCodexHost(),
|
|
11
36
|
createJsonConfigHost({
|
|
12
37
|
id: "claude-desktop",
|