witnora 0.19.2 → 0.20.2
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/cli.js +8 -0
- package/dist/command-help.js +4 -1
- package/dist/mcp.js +25 -0
- package/dist/onboard.js +76 -2
- package/dist/real-path-activation.js +20 -7
- package/mcp.d.ts +9 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -158,6 +158,7 @@ else if (command === "onboard") {
|
|
|
158
158
|
name: readFlag("--name"),
|
|
159
159
|
repository: readFlag("--repo") ?? process.cwd(),
|
|
160
160
|
template: readFlag("--template") ? parseAgentTemplate(readFlag("--template")) : undefined,
|
|
161
|
+
actionTransport: readActionTransport(readFlag("--action-transport")),
|
|
161
162
|
openBrowser: readBoolFlag("--no-browser") ? async () => { } : undefined,
|
|
162
163
|
});
|
|
163
164
|
}
|
|
@@ -906,6 +907,13 @@ else {
|
|
|
906
907
|
witnora schema validate --schema evidence-bundle --file .witnora/latest/agentcert-evidence.json
|
|
907
908
|
`);
|
|
908
909
|
}
|
|
910
|
+
function readActionTransport(value) {
|
|
911
|
+
if (value === undefined)
|
|
912
|
+
return undefined;
|
|
913
|
+
if (value === "http" || value === "mcp")
|
|
914
|
+
return value;
|
|
915
|
+
throw new Error("--action-transport must be http or mcp.");
|
|
916
|
+
}
|
|
909
917
|
async function loadConfig(path) {
|
|
910
918
|
if (!path) {
|
|
911
919
|
return undefined;
|
package/dist/command-help.js
CHANGED
|
@@ -41,11 +41,13 @@ Options:
|
|
|
41
41
|
return `Usage:
|
|
42
42
|
witnora onboard --project <project-id>
|
|
43
43
|
witnora onboard --project <project-id> --template <browser|coding|mcp|workflow|data>
|
|
44
|
+
witnora onboard --project <project-id> --action-transport <http|mcp>
|
|
44
45
|
|
|
45
46
|
Opens one browser authorization, saves a restricted project credential, detects the repository,
|
|
46
47
|
writes missing starter files, starts a customer-owned Gateway in the background, and records an
|
|
47
48
|
isolated synthetic self-test receipt. The self-test does not create assurance evidence or establish
|
|
48
|
-
CURRENT status.
|
|
49
|
+
CURRENT status. When --action-transport is selected, Witnora validates that exact local transport
|
|
50
|
+
without proposing or executing an Action and reports the bounded result to Hosted.
|
|
49
51
|
|
|
50
52
|
Options:
|
|
51
53
|
--server <url> Hosted server (default: https://witnora.com)
|
|
@@ -53,6 +55,7 @@ Options:
|
|
|
53
55
|
--name <name> Saved connection name (default: repository name)
|
|
54
56
|
--repo <directory> Repository to configure (default: current directory)
|
|
55
57
|
--template <type> Override automatic repository detection
|
|
58
|
+
--action-transport <t> Test HTTP or MCP against the exact generated sandbox Action path
|
|
56
59
|
--no-browser Print the approval URL without opening it
|
|
57
60
|
`;
|
|
58
61
|
if (command === "mcp")
|
package/dist/mcp.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
2
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
6
|
import { z } from "zod";
|
|
@@ -52,6 +54,29 @@ export async function runWitnoraMcpStdioServer(options = {}) {
|
|
|
52
54
|
const { server } = await createWitnoraMcpServerFromRepository(options);
|
|
53
55
|
await server.connect(new StdioServerTransport());
|
|
54
56
|
}
|
|
57
|
+
export async function testWitnoraMcpConnection(options = {}) {
|
|
58
|
+
const { server, actionTool, action } = await createWitnoraMcpServerFromRepository(options);
|
|
59
|
+
const client = new Client({ name: "witnora-onboarding-connection-test", version: "1.0.0" });
|
|
60
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
61
|
+
try {
|
|
62
|
+
await server.connect(serverTransport);
|
|
63
|
+
await client.connect(clientTransport);
|
|
64
|
+
const listed = await client.listTools();
|
|
65
|
+
const tools = listed.tools.map((tool) => tool.name).sort();
|
|
66
|
+
const expected = [actionTool, "witnora_get_action"].sort();
|
|
67
|
+
if (JSON.stringify(tools) !== JSON.stringify(expected)) {
|
|
68
|
+
throw new Error("Witnora MCP connection test found an unexpected tool surface.");
|
|
69
|
+
}
|
|
70
|
+
if (tools.some((tool) => /approve|execute|verify/i.test(tool))) {
|
|
71
|
+
throw new Error("Witnora MCP connection test found a forbidden authority-bearing tool.");
|
|
72
|
+
}
|
|
73
|
+
return { actionId: action.id, actionTool, tools };
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await client.close().catch(() => undefined);
|
|
77
|
+
await server.close().catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
55
80
|
export function witnoraMcpActionToolName(actionId) {
|
|
56
81
|
const normalized = actionId.toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "action";
|
|
57
82
|
const candidate = `witnora_${normalized}`;
|
package/dist/onboard.js
CHANGED
|
@@ -8,7 +8,7 @@ import { verifyControlPlaneConnection } from "./control-plane.js";
|
|
|
8
8
|
import { inferPrivateCapabilities, runPrivateCapabilityDiscovery } from "./private-discovery.js";
|
|
9
9
|
import { parseAgentTemplate, starterAdapter, starterProfile, starterTripwireConfig } from "./onboarding-templates.js";
|
|
10
10
|
import { writeTryEvidence } from "./try.js";
|
|
11
|
-
import { doctorCustomerGateway, activateManagedWorkflowHarness, configureCustomerHttpAction, ensureCustomerGatewayPortAvailable, initializeCustomerGateway, inspectLocalRuntimeBinding, inspectCustomerGatewayFiles, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, upgradeCustomerGatewayRuntime, RuntimeSetupNotReadyError, } from "./gateway.js";
|
|
11
|
+
import { doctorCustomerGateway, activateManagedWorkflowHarness, configureCustomerHttpAction, ensureCustomerGatewayPortAvailable, initializeCustomerGateway, inspectLocalRuntimeBinding, inspectCustomerGatewayFiles, loadCustomerMcpActionBinding, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, upgradeCustomerGatewayRuntime, RuntimeSetupNotReadyError, } from "./gateway.js";
|
|
12
12
|
import { discoverRuntimeReferences, planRuntimeSandboxModules } from "./runtime-sandbox-kit.js";
|
|
13
13
|
import { automaticRuntimeReferencesUsable, bootstrapLocalRuntime, verifyAutomaticRuntimeProbe } from "./runtime-bootstrap.js";
|
|
14
14
|
import { activateRealPathIntegrations } from "./real-path-activation.js";
|
|
@@ -294,6 +294,19 @@ export async function runOnboard(options) {
|
|
|
294
294
|
const runtimeReadiness = doctor.overall === "READY_FOR_RUNTIME" && runtimeBinding
|
|
295
295
|
? { state: "LOCAL_SANDBOX_READY", checkedAt: new Date().toISOString(), limitations: [] }
|
|
296
296
|
: { state: "RECORDED_ONLY", checkedAt: new Date().toISOString(), limitations: [runtimeLimitation ?? "The local sandbox Runtime worker has not established exact adapter, probe, source-key, identity, mandate, and readiness bindings."] };
|
|
297
|
+
const actionTransportTest = options.actionTransport
|
|
298
|
+
? await runActionTransportTest({
|
|
299
|
+
transport: options.actionTransport,
|
|
300
|
+
repository: repositoryPath,
|
|
301
|
+
server,
|
|
302
|
+
projectId: token.projectId,
|
|
303
|
+
apiKey: token.apiKey,
|
|
304
|
+
repositoryIdentity: repository,
|
|
305
|
+
httpActionReady: httpActionSetup?.state === "READY" && Boolean(httpActionSetup.action),
|
|
306
|
+
fetch: requestFetch,
|
|
307
|
+
generatedFiles,
|
|
308
|
+
})
|
|
309
|
+
: undefined;
|
|
297
310
|
await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
|
|
298
311
|
status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
|
|
299
312
|
connectedAgent: agentIdentity,
|
|
@@ -316,11 +329,20 @@ export async function runOnboard(options) {
|
|
|
316
329
|
if (httpActionSetup?.state === "READY" && httpActionSetup.action) {
|
|
317
330
|
output(`HTTP Action: READY. POST ${managedGateway.baseUrl}${httpActionSetup.action.path}; copy the action-scoped token and request example from .witnora/gateway/HTTP_ACTION.md.\n`);
|
|
318
331
|
}
|
|
319
|
-
|
|
332
|
+
if (actionTransportTest?.state === "PASSED") {
|
|
333
|
+
output(`Action transport: ${actionTransportTest.transport.toUpperCase()} connection test PASSED at the customer-owned Gateway. No Action was proposed or executed.\n`);
|
|
334
|
+
}
|
|
335
|
+
else if (actionTransportTest) {
|
|
336
|
+
output(`Action transport: ${actionTransportTest.transport.toUpperCase()} is waiting for one exact sandbox Task/action path. ${actionTransportTest.limitation}\n`);
|
|
337
|
+
}
|
|
338
|
+
output(actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
|
|
339
|
+
? `Base Gateway connected, but ${actionTransportTest.transport.toUpperCase()} Action is not verified yet. Go to Overview, run the Agent once, confirm one exact sandbox Business Task/action path, then rerun this same command.\n`
|
|
340
|
+
: "Connected. Nora is monitoring this Agent. Run it normally whenever it is ready; the first source-signed activity will appear automatically without blocking setup.\n");
|
|
320
341
|
return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
|
|
321
342
|
repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
|
|
322
343
|
gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
|
|
323
344
|
gateway: managedGateway, runtimeReadiness, assuranceHarnessReadiness, continuousService,
|
|
345
|
+
actionTransportTest,
|
|
324
346
|
...(httpActionSetup?.state === "READY" && httpActionSetup.action ? { httpAction: {
|
|
325
347
|
state: "READY",
|
|
326
348
|
method: "POST",
|
|
@@ -346,6 +368,58 @@ export async function runOnboard(options) {
|
|
|
346
368
|
throw new Error(`Witnora Setup Autopilot rolled back this install attempt: ${diagnosis}`);
|
|
347
369
|
}
|
|
348
370
|
}
|
|
371
|
+
async function runActionTransportTest(options) {
|
|
372
|
+
if (!options.httpActionReady)
|
|
373
|
+
return {
|
|
374
|
+
state: "WAITING_FOR_ACTION_PATH",
|
|
375
|
+
transport: options.transport,
|
|
376
|
+
limitation: "Define and approve one exact sandbox Task/action Harness, then rerun this same onboarding command.",
|
|
377
|
+
};
|
|
378
|
+
const binding = await loadCustomerMcpActionBinding({ repository: options.repository });
|
|
379
|
+
const checkedAt = new Date().toISOString();
|
|
380
|
+
const passed = options.transport === "http"
|
|
381
|
+
? {
|
|
382
|
+
state: "PASSED",
|
|
383
|
+
transport: "http",
|
|
384
|
+
checkedAt,
|
|
385
|
+
actionId: binding.action.id,
|
|
386
|
+
endpoint: `${binding.baseUrl}${binding.action.path}`,
|
|
387
|
+
}
|
|
388
|
+
: await import("./mcp.js").then(async ({ testWitnoraMcpConnection }) => {
|
|
389
|
+
const result = await testWitnoraMcpConnection({ repository: options.repository, fetch: options.fetch });
|
|
390
|
+
return {
|
|
391
|
+
state: "PASSED",
|
|
392
|
+
transport: "mcp",
|
|
393
|
+
checkedAt,
|
|
394
|
+
actionId: result.actionId,
|
|
395
|
+
actionTool: result.actionTool,
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
const report = {
|
|
399
|
+
transport: passed.transport,
|
|
400
|
+
actionId: passed.actionId,
|
|
401
|
+
...(passed.transport === "http" ? { endpoint: passed.endpoint } : { actionTool: passed.actionTool }),
|
|
402
|
+
};
|
|
403
|
+
const receipt = await jsonRequest(options.fetch, `${options.server}/v1/projects/${encodeURIComponent(options.projectId)}/onboarding/self-test`, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers: { authorization: `Bearer ${options.apiKey}`, "content-type": "application/json" },
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
template: options.repositoryIdentity.template,
|
|
408
|
+
repository: {
|
|
409
|
+
kind: options.repositoryIdentity.kind,
|
|
410
|
+
name: options.repositoryIdentity.name,
|
|
411
|
+
fingerprintSha256: options.repositoryIdentity.fingerprintSha256,
|
|
412
|
+
actionTransportTest: report,
|
|
413
|
+
},
|
|
414
|
+
bundleSha256: createHash("sha256").update(JSON.stringify(report)).digest("hex"),
|
|
415
|
+
}),
|
|
416
|
+
});
|
|
417
|
+
const receiptPath = join(options.repository, ".witnora", "onboarding", "receipts", `action-transport-${passed.transport}-${Date.now()}.json`);
|
|
418
|
+
await mkdir(dirname(receiptPath), { recursive: true });
|
|
419
|
+
await writeFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
420
|
+
options.generatedFiles.push(receiptPath);
|
|
421
|
+
return { ...passed, receiptPath };
|
|
422
|
+
}
|
|
349
423
|
async function waitForInstalledGatewayService(options) {
|
|
350
424
|
const pause = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
351
425
|
const deadline = Date.now() + (options.timeoutMs ?? 12_000);
|
|
@@ -84,9 +84,7 @@ export async function activateRealPathIntegrations(options) {
|
|
|
84
84
|
}
|
|
85
85
|
async function providerPreflight(request, env, plan, now, postgresClientFactory) {
|
|
86
86
|
if (plan.generated.providerPackId === "STRIPE_REFUND") {
|
|
87
|
-
const secret = env
|
|
88
|
-
if (!secret?.startsWith("sk_test_") || secret.length < 12)
|
|
89
|
-
throw new Error("Stripe test-mode activation requires STRIPE_SECRET_KEY to reference an sk_test_ credential in the customer environment.");
|
|
87
|
+
const secret = stripeTestCredential(env);
|
|
90
88
|
return stripePreflight(request, secret, plan, now);
|
|
91
89
|
}
|
|
92
90
|
if (plan.generated.providerPackId === "SHOPIFY_DISPUTE")
|
|
@@ -252,7 +250,7 @@ import {join} from "node:path";
|
|
|
252
250
|
const contracts=${JSON.stringify(contracts)};
|
|
253
251
|
const sha=(value)=>createHash("sha256").update(typeof value==="string"?value:JSON.stringify(value)).digest("hex");
|
|
254
252
|
const request=async(url,key)=>{const response=await fetch(url,{method:"GET",headers:{accept:"application/json",authorization:"Bearer "+key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Provider read-only observation failed ("+response.status+").");return response.json();};
|
|
255
|
-
const observe=async(contract,resourceId,repository)=>{if(contract.sandboxObservation)return structuredClone(contract.sandboxObservation);const key=(await readFile(join(repository,contract.credentialHandle),"utf8")).trim();if(!key)throw new Error("The local read-only Provider credential is unavailable.");if(contract.providerPackId==="STRIPE_REFUND"){if(!key.startsWith("sk_test_"))throw new Error("Stripe test-mode credential is unavailable.");return request("https://api.stripe.com/v1/refunds/"+encodeURIComponent(resourceId),key);}if(contract.providerPackId==="SHOPIFY_DISPUTE"){const response=await fetch(contract.providerConfiguration.origin+"/admin/api/2025-07/shopify_payments/disputes/"+encodeURIComponent(resourceId)+".json",{method:"GET",headers:{accept:"application/json","x-shopify-access-token":key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Provider read-only observation failed ("+response.status+").");return (await response.json()).dispute;}if(contract.providerPackId==="ZENDESK_TICKET")return (await request(contract.providerConfiguration.origin+"/api/v2/tickets/"+encodeURIComponent(resourceId)+".json",key)).ticket;if(contract.providerPackId==="SALESFORCE_RECORD")return request(contract.providerConfiguration.origin+"/services/data/v61.0/sobjects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId),key);if(contract.providerPackId==="HUBSPOT_CRM_RECORD")return (await request("https://api.hubapi.com/crm/v3/objects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId)+"?properties="+encodeURIComponent(contract.criterion.field),key)).properties;if(contract.providerPackId==="POSTGRES_RECORD"){for(const name of [contract.providerConfiguration.viewName,contract.providerConfiguration.idColumn,contract.criterion.field])if(!/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(name))throw new Error("PostgreSQL identifier is invalid.");const {Client}=await import("pg");const client=new Client({connectionString:key,application_name:"witnora-read-only-probe"});await client.connect();try{await client.query("BEGIN READ ONLY");const result=await client.query({name:"witnora-provider-observe",text:'SELECT "'+contract.criterion.field+'" FROM "'+contract.providerConfiguration.viewName+'" WHERE "'+contract.providerConfiguration.idColumn+'" = $1 LIMIT 1',values:[resourceId]});await client.query("ROLLBACK");return result.rows[0]??{};}catch(error){await client.query("ROLLBACK").catch(()=>{});throw error;}finally{await client.end();}}throw new Error("Provider Harness contract is unsupported.");};
|
|
253
|
+
const observe=async(contract,resourceId,repository)=>{if(contract.sandboxObservation)return structuredClone(contract.sandboxObservation);const key=(await readFile(join(repository,contract.credentialHandle),"utf8")).trim();if(!key)throw new Error("The local read-only Provider credential is unavailable.");if(contract.providerPackId==="STRIPE_REFUND"){if(!key.startsWith("rk_test_")&&!key.startsWith("sk_test_"))throw new Error("Stripe test-mode credential is unavailable.");return request("https://api.stripe.com/v1/refunds/"+encodeURIComponent(resourceId),key);}if(contract.providerPackId==="SHOPIFY_DISPUTE"){const response=await fetch(contract.providerConfiguration.origin+"/admin/api/2025-07/shopify_payments/disputes/"+encodeURIComponent(resourceId)+".json",{method:"GET",headers:{accept:"application/json","x-shopify-access-token":key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Provider read-only observation failed ("+response.status+").");return (await response.json()).dispute;}if(contract.providerPackId==="ZENDESK_TICKET")return (await request(contract.providerConfiguration.origin+"/api/v2/tickets/"+encodeURIComponent(resourceId)+".json",key)).ticket;if(contract.providerPackId==="SALESFORCE_RECORD")return request(contract.providerConfiguration.origin+"/services/data/v61.0/sobjects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId),key);if(contract.providerPackId==="HUBSPOT_CRM_RECORD")return (await request("https://api.hubapi.com/crm/v3/objects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId)+"?properties="+encodeURIComponent(contract.criterion.field),key)).properties;if(contract.providerPackId==="POSTGRES_RECORD"){for(const name of [contract.providerConfiguration.viewName,contract.providerConfiguration.idColumn,contract.criterion.field])if(!/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(name))throw new Error("PostgreSQL identifier is invalid.");const {Client}=await import("pg");const client=new Client({connectionString:key,application_name:"witnora-read-only-probe"});await client.connect();try{await client.query("BEGIN READ ONLY");const result=await client.query({name:"witnora-provider-observe",text:'SELECT "'+contract.criterion.field+'" FROM "'+contract.providerConfiguration.viewName+'" WHERE "'+contract.providerConfiguration.idColumn+'" = $1 LIMIT 1',values:[resourceId]});await client.query("ROLLBACK");return result.rows[0]??{};}catch(error){await client.query("ROLLBACK").catch(()=>{});throw error;}finally{await client.end();}}throw new Error("Provider Harness contract is unsupported.");};
|
|
256
254
|
export function createWitnoraBusinessTaskEvaluatorOptions(context){return {
|
|
257
255
|
loadShadowObservations:async(task)=>{const contract=contracts.find((item)=>item.taskContractId===task.id);if(!contract)throw new Error("No exact real-path contract.");const path=join(context.repository,".witnora","scenarios",task.id+".json");const value=JSON.parse(await readFile(path,"utf8"));if(!Array.isArray(value)||value.length>100)throw new Error("Local scenario file is invalid.");return value.map((item)=>({...item,source:"LIVE_SHADOW"}));},
|
|
258
256
|
evaluateShadowCandidate:async(candidate,task)=>{const contract=contracts.find((item)=>item.taskContractId===task.id);if(!contract)throw new Error("No exact real-path contract.");const resourceId=String(candidate.input?.resourceId??"");if(!/^[A-Za-z0-9._:-]{1,200}$/.test(resourceId))throw new Error("Provider resourceId is invalid.");for(const pathId of contract.actionPathIds)candidate.propose({pathId,parametersDigestSha256:sha(resourceId)});const raw=await observe(contract,resourceId,context.repository);const actual=raw?.[contract.criterion.field];const observed=actual===undefined?{}:{[contract.criterion.field]:actual};return {resultDigestSha256:sha(observed),criteria:[{id:contract.criterion.id,passed:actual===contract.criterion.expected}]};}
|
|
@@ -265,11 +263,14 @@ async function persistProviderCredential(repository, plan, environment) {
|
|
|
265
263
|
return;
|
|
266
264
|
if (plan.generated.providerPackId === "QUEUE_JOB" && plan.environment === "sandbox")
|
|
267
265
|
return;
|
|
268
|
-
const name = providerCredentialEnvironmentName(plan.generated.providerPackId);
|
|
269
|
-
const value = environment[name];
|
|
270
266
|
const directory = join(repository, ".witnora", "provider-credentials");
|
|
271
267
|
const target = join(directory, `${plan.id}.secret`);
|
|
272
268
|
const current = await readFile(target, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
|
|
269
|
+
const name = providerCredentialEnvironmentName(plan.generated.providerPackId);
|
|
270
|
+
const stripeCredentialSupplied = Boolean(environment.STRIPE_RESTRICTED_TEST_KEY?.trim() || environment.STRIPE_SECRET_KEY?.trim());
|
|
271
|
+
const value = plan.generated.providerPackId === "STRIPE_REFUND"
|
|
272
|
+
? stripeCredentialSupplied ? stripeTestCredential(environment) : undefined
|
|
273
|
+
: environment[name];
|
|
273
274
|
if (!value) {
|
|
274
275
|
if (current !== undefined)
|
|
275
276
|
return;
|
|
@@ -294,7 +295,7 @@ function sameActivationPlans(plans, activations) {
|
|
|
294
295
|
}
|
|
295
296
|
function providerCredentialEnvironmentName(packId) {
|
|
296
297
|
if (packId === "STRIPE_REFUND")
|
|
297
|
-
return "
|
|
298
|
+
return "STRIPE_RESTRICTED_TEST_KEY";
|
|
298
299
|
if (packId === "SHOPIFY_DISPUTE")
|
|
299
300
|
return "SHOPIFY_READ_ACCESS_TOKEN";
|
|
300
301
|
if (packId === "ZENDESK_TICKET")
|
|
@@ -307,6 +308,18 @@ function providerCredentialEnvironmentName(packId) {
|
|
|
307
308
|
return "WITNORA_POSTGRES_READ_URL";
|
|
308
309
|
throw new Error(`Provider credential mapping is not supported for ${packId}.`);
|
|
309
310
|
}
|
|
311
|
+
function stripeTestCredential(environment) {
|
|
312
|
+
const restricted = environment.STRIPE_RESTRICTED_TEST_KEY?.trim();
|
|
313
|
+
if (restricted) {
|
|
314
|
+
if (!restricted.startsWith("rk_test_") || restricted.length < 12)
|
|
315
|
+
throw new Error("Stripe sandbox activation requires STRIPE_RESTRICTED_TEST_KEY to contain an rk_test_ restricted test key.");
|
|
316
|
+
return restricted;
|
|
317
|
+
}
|
|
318
|
+
const legacy = environment.STRIPE_SECRET_KEY?.trim();
|
|
319
|
+
if (!legacy?.startsWith("sk_test_") || legacy.length < 12)
|
|
320
|
+
throw new Error("Stripe sandbox activation requires a read-only rk_test_ key in STRIPE_RESTRICTED_TEST_KEY. STRIPE_SECRET_KEY remains a legacy sk_test_ fallback only.");
|
|
321
|
+
return legacy;
|
|
322
|
+
}
|
|
310
323
|
function parsePlan(value) { if (!value || typeof value !== "object" || Array.isArray(value))
|
|
311
324
|
throw new Error("Real-path integration response is invalid."); const plan = value; if (plan.schemaVersion !== "witnora.real_path_integration.v0.1" || !plan.id || !plan.taskContractId || !plan.subject?.agentId || !plan.subject.agentVersion || !plan.generated || plan.generated.boundaries?.rawPayloadUpload !== false || plan.generated.boundaries.rawCredentialUpload !== false || plan.generated.boundaries.evaluatorWrites !== false || plan.generated.boundaries.firstAcceptanceProductionWrites !== 0 || !digest(plan.digestSha256) || !digest(plan.taskContractDigestSha256) || !digest(plan.generated.providerContractDigestSha256))
|
|
312
325
|
throw new Error("Real-path integration response failed its safety contract."); return plan; }
|
package/mcp.d.ts
CHANGED
|
@@ -39,4 +39,13 @@ export function runWitnoraMcpStdioServer(options?: {
|
|
|
39
39
|
fetch?: typeof fetch;
|
|
40
40
|
}): Promise<void>;
|
|
41
41
|
|
|
42
|
+
export function testWitnoraMcpConnection(options?: {
|
|
43
|
+
repository?: string;
|
|
44
|
+
dir?: string;
|
|
45
|
+
fetch?: typeof fetch;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
actionTool: string;
|
|
48
|
+
tools: string[];
|
|
49
|
+
}>;
|
|
50
|
+
|
|
42
51
|
export function witnoraMcpActionToolName(actionId: string): string;
|