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.
Files changed (128) hide show
  1. package/.onto/authority/core-lexicon.yaml +1 -1
  2. package/.onto/authority/supported-models.yaml +137 -0
  3. package/.onto/domains/accounting-kr/competency_qs.md +153 -153
  4. package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
  5. package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
  6. package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
  7. package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
  8. package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
  9. package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
  10. package/.onto/processes/review/productized-live-path.md +9 -5
  11. package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
  12. package/.onto/processes/review/review-target-profile-contract.md +47 -4
  13. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
  14. package/.onto/processes/shared/target-material-kind-contract.md +1 -1
  15. package/README.md +64 -1
  16. package/dist/cli.js +11 -1
  17. package/dist/core-api/reconstruct-api.js +575 -24
  18. package/dist/core-api/review-api.js +70 -5
  19. package/dist/core-api/review-progress.js +7 -0
  20. package/dist/core-api/runtime-observation.js +7 -0
  21. package/dist/core-runtime/artifact-io.js +75 -3
  22. package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
  23. package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
  24. package/dist/core-runtime/cli/prepare-review-session.js +34 -1
  25. package/dist/core-runtime/cli/render-review-final-output.js +2 -1
  26. package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
  27. package/dist/core-runtime/cli/run-seats.js +18 -0
  28. package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
  29. package/dist/core-runtime/cli/unit-resubmit.js +269 -0
  30. package/dist/core-runtime/cli/worker-structured-output.js +80 -2
  31. package/dist/core-runtime/discovery/host-detection.js +71 -22
  32. package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
  33. package/dist/core-runtime/discovery/review-cert-record.js +442 -0
  34. package/dist/core-runtime/discovery/review-execution-units.js +22 -0
  35. package/dist/core-runtime/discovery/seat-inventory.js +167 -0
  36. package/dist/core-runtime/discovery/settings-chain.js +311 -52
  37. package/dist/core-runtime/discovery/supported-models.js +282 -29
  38. package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
  39. package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
  40. package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
  41. package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
  42. package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
  43. package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
  44. package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
  45. package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
  46. package/dist/core-runtime/effort-calibration-ingest.js +135 -0
  47. package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
  48. package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
  49. package/dist/core-runtime/llm/llm-caller.js +188 -47
  50. package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
  51. package/dist/core-runtime/llm/model-switcher.js +1 -1
  52. package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
  53. package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
  54. package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
  55. package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
  56. package/dist/core-runtime/onboard/cli-host.js +5 -2
  57. package/dist/core-runtime/onboard/configure-provider.js +33 -1
  58. package/dist/core-runtime/onboard/host-target.js +28 -3
  59. package/dist/core-runtime/onboard/register.js +54 -12
  60. package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
  61. package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
  62. package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
  63. package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
  64. package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
  65. package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
  66. package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
  67. package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
  68. package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
  69. package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
  70. package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
  71. package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
  72. package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
  73. package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
  74. package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
  75. package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
  76. package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
  77. package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
  78. package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
  79. package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
  80. package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
  81. package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
  82. package/dist/core-runtime/reconstruct/output-budget.js +6 -0
  83. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
  84. package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
  85. package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
  86. package/dist/core-runtime/reconstruct/record.js +42 -0
  87. package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
  88. package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
  89. package/dist/core-runtime/reconstruct/run.js +5198 -536
  90. package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
  91. package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
  92. package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
  93. package/dist/core-runtime/reconstruct/source-observations.js +166 -0
  94. package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
  95. package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
  96. package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
  97. package/dist/core-runtime/review/continuation-plan.js +12 -5
  98. package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
  99. package/dist/core-runtime/review/materializers.js +100 -11
  100. package/dist/core-runtime/review/nesting-batch.js +6 -2
  101. package/dist/core-runtime/review/obligation-shardability.js +138 -0
  102. package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
  103. package/dist/core-runtime/review/review-artifact-utils.js +217 -5
  104. package/dist/core-runtime/review/review-execution-steps.js +32 -8
  105. package/dist/core-runtime/review/review-progress-contract.js +8 -0
  106. package/dist/core-runtime/review/review-prompt-budget.js +97 -0
  107. package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
  108. package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
  109. package/dist/core-runtime/route-identity.js +182 -0
  110. package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
  111. package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
  112. package/dist/core-runtime/target-material-kind.js +58 -0
  113. package/dist/mcp/server.js +12 -5
  114. package/dist/tui/app.js +167 -0
  115. package/dist/tui/data/event-follower.js +116 -0
  116. package/dist/tui/data/node-detail.js +42 -0
  117. package/dist/tui/data/projection-poll.js +26 -0
  118. package/dist/tui/data/session-discovery.js +65 -0
  119. package/dist/tui/index.js +108 -0
  120. package/dist/tui/screens/log.js +25 -0
  121. package/dist/tui/screens/node-detail.js +17 -0
  122. package/dist/tui/screens/session-selector.js +26 -0
  123. package/dist/tui/screens/workflow-tree.js +90 -0
  124. package/dist/tui/view-model/reconstruct-adapter.js +162 -0
  125. package/dist/tui/view-model/review-adapter.js +270 -0
  126. package/dist/tui/view-model/tree-view-model.js +19 -0
  127. package/package.json +18 -3
  128. 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
- displayName: "Claude Code",
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
- reconstructExecution.actors = blocks.reconstruct;
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 four supported hosts in display order. CLI-backed hosts (Claude Code,
5
- * Codex) come first; JSON-config hosts (Claude Desktop, Cursor) follow.
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
- createClaudeCodeHost(options.claudeConfigDir ? { configDir: options.claudeConfigDir } : {}),
34
+ ...claudeTargets,
10
35
  createCodexHost(),
11
36
  createJsonConfigHost({
12
37
  id: "claude-desktop",