witnora 0.20.13 → 0.20.14
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 +15 -2
- package/dist/command-help.js +5 -0
- package/dist/credentials.js +28 -6
- package/dist/gateway.js +24 -4
- package/dist/onboard.js +7 -5
- package/dist/real-path-activation.js +22 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -155,7 +155,7 @@ else if (command === "onboard") {
|
|
|
155
155
|
throw new Error("--project <project-id> is required.");
|
|
156
156
|
await runOnboard({
|
|
157
157
|
projectId,
|
|
158
|
-
server: readFlag("--server")
|
|
158
|
+
server: readFlag("--server"),
|
|
159
159
|
name: readFlag("--name"),
|
|
160
160
|
repository: readFlag("--repo") ?? process.cwd(),
|
|
161
161
|
template: readFlag("--template") ? parseAgentTemplate(readFlag("--template")) : undefined,
|
|
@@ -282,6 +282,8 @@ else if (command === "gateway") {
|
|
|
282
282
|
configHome,
|
|
283
283
|
}, () => stopManagedCustomerGateway({ repository, dir, configHome }));
|
|
284
284
|
process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
|
|
285
|
+
if (!readBoolFlag("--json"))
|
|
286
|
+
process.stdout.write("Automatic startup remains installed if previously configured. To remove it, run witnora gateway service uninstall with the same --repo and --dir options. Saved credentials and evidence are retained.\n");
|
|
285
287
|
}
|
|
286
288
|
else if (action === "exec") {
|
|
287
289
|
const boundary = process.argv.indexOf("--", 4);
|
|
@@ -322,7 +324,9 @@ else if (command === "gateway") {
|
|
|
322
324
|
process.stdout.write(`Configured the Managed Workflow Harness for ${result.config.workflowIds?.length ?? "all active"} Workflow Contract(s).\nConfig: ${result.path}\nRestart the managed Gateway to activate it.\n`);
|
|
323
325
|
}
|
|
324
326
|
else {
|
|
325
|
-
|
|
327
|
+
process.stdout.write(renderCommandHelp("gateway"));
|
|
328
|
+
if (action !== "help")
|
|
329
|
+
process.exitCode = 1;
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
else if (command === "discover") {
|
|
@@ -901,11 +905,18 @@ else if (command === "conformance") {
|
|
|
901
905
|
}
|
|
902
906
|
else {
|
|
903
907
|
process.stdout.write(`Usage:
|
|
908
|
+
Connection and discovery:
|
|
904
909
|
witnora onboard --project <project-id>
|
|
910
|
+
witnora discover [--connection <name>] [--repo <directory>]
|
|
911
|
+
witnora gateway --help
|
|
912
|
+
witnora gateway status|doctor|start|restart|stop
|
|
913
|
+
witnora gateway service install|uninstall
|
|
905
914
|
witnora mcp
|
|
906
915
|
witnora try --template workflow [--push]
|
|
907
916
|
witnora init --subject my-browser-agent
|
|
908
917
|
witnora connect --server https://witnora.com --project <project-id>
|
|
918
|
+
|
|
919
|
+
Local setup and evaluation:
|
|
909
920
|
witnora sandbox init
|
|
910
921
|
witnora sandbox certify --adapter ./witnora.sandbox.mjs
|
|
911
922
|
witnora sandbox push --adapter ./witnora.sandbox.mjs
|
|
@@ -913,6 +924,8 @@ else {
|
|
|
913
924
|
witnora browser-adapter certify --adapter ./witnora.browser-adapter.mjs
|
|
914
925
|
witnora init --out witnora.config.json --tripwire-config tripwire.yml --force
|
|
915
926
|
witnora init --subject my-browser-agent --github-action
|
|
927
|
+
|
|
928
|
+
Evidence, reports, and history:
|
|
916
929
|
witnora report --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .witnora/latest --subject my-agent
|
|
917
930
|
witnora corpus ingest --tripwire .tripwire/latest/tripwire-result.json --out .witnora/corpus/corpus.jsonl --subject my-agent
|
|
918
931
|
witnora corpus review --corpus .witnora/corpus/corpus.jsonl --reviews .witnora/corpus/failure-reviews.jsonl --pattern-key <failure-key> --type wrong_click --status corrected
|
package/dist/command-help.js
CHANGED
|
@@ -48,6 +48,8 @@ writes missing starter files, starts a customer-owned Gateway in the background,
|
|
|
48
48
|
isolated synthetic self-test receipt. The self-test does not create assurance evidence or establish
|
|
49
49
|
CURRENT status. When --action-transport is selected, Witnora validates that exact local transport
|
|
50
50
|
without proposing or executing an Action and reports the bounded result to Hosted.
|
|
51
|
+
Use witnora gateway stop to stop it now, or witnora gateway service uninstall to remove
|
|
52
|
+
automatic startup. Both preserve saved credentials and historical evidence.
|
|
51
53
|
|
|
52
54
|
Options:
|
|
53
55
|
--server <url> Hosted server (default: https://witnora.com)
|
|
@@ -86,6 +88,7 @@ Options:
|
|
|
86
88
|
witnora gateway service install|uninstall
|
|
87
89
|
witnora gateway stop
|
|
88
90
|
witnora gateway run
|
|
91
|
+
witnora gateway exec -- <agent command>
|
|
89
92
|
witnora gateway workflow-harness --workflow <id> --evaluator-origin http://127.0.0.1:<port>/ \\
|
|
90
93
|
--evaluator-credential-handle file://... --evaluator-contract-sha256 <sha256>
|
|
91
94
|
|
|
@@ -96,6 +99,8 @@ under customer control. The service command installs an OS-native auto-start and
|
|
|
96
99
|
boundary. Repair safely reinstalls that boundary, restarts the existing generated Gateway,
|
|
97
100
|
and reruns digest, credential, Probe, and health checks. The run command is the foreground
|
|
98
101
|
debugging path.
|
|
102
|
+
Stopping the Gateway preserves its installed service. Use gateway service uninstall
|
|
103
|
+
to remove automatic startup without deleting saved credentials or historical evidence.
|
|
99
104
|
|
|
100
105
|
The reference Gateway establishes RECORDED evidence only. ENFORCED requires target
|
|
101
106
|
write credentials behind a controlled execution adapter. OUTCOME VERIFIED requires a
|
package/dist/credentials.js
CHANGED
|
@@ -33,16 +33,27 @@ export async function loadConnection(name, options = {}) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function resolveConnection(options = {}) {
|
|
35
35
|
const env = options.env ?? process.env;
|
|
36
|
-
const
|
|
36
|
+
const explicit = options.server && options.projectId && options.apiKey;
|
|
37
|
+
const stored = explicit
|
|
37
38
|
? undefined
|
|
38
39
|
: await loadConnection(options.name, options);
|
|
39
|
-
if (options.name && stored && !
|
|
40
|
-
|
|
40
|
+
if (options.name && !stored && !explicit) {
|
|
41
|
+
throw new Error(`Saved connection "${options.name}" was not found. Run \`witnora onboard --project <project-id> --name ${options.name}\` or provide all three connection flags.`);
|
|
42
|
+
}
|
|
43
|
+
// A browser-approved connection is one credential tuple. Ambient variables must
|
|
44
|
+
// not redirect it to another project or supply that project's key.
|
|
45
|
+
const witnoraEnvironment = env.WITNORA_BASE_URL !== undefined || env.WITNORA_PROJECT_ID !== undefined || env.WITNORA_API_KEY !== undefined;
|
|
46
|
+
const source = stored ?? (witnoraEnvironment
|
|
47
|
+
? { server: env.WITNORA_BASE_URL, projectId: env.WITNORA_PROJECT_ID, apiKey: env.WITNORA_API_KEY }
|
|
48
|
+
: { server: env.AGENTCERT_BASE_URL, projectId: env.AGENTCERT_PROJECT_ID, apiKey: env.AGENTCERT_API_KEY });
|
|
49
|
+
if (!explicit && ((options.server && source.server && normalizeServer(options.server) !== normalizeServer(source.server))
|
|
50
|
+
|| (options.projectId && source.projectId && options.projectId.trim() !== source.projectId.trim()))) {
|
|
51
|
+
throw new Error("Partial connection flags change the saved or environment server/project. Provide --server, --project, and --api-key together to select another credential tuple; no request was sent.");
|
|
41
52
|
}
|
|
42
53
|
const candidate = {
|
|
43
|
-
server: options.server ??
|
|
44
|
-
projectId: options.projectId ??
|
|
45
|
-
apiKey: options.apiKey ??
|
|
54
|
+
server: options.server ?? source.server,
|
|
55
|
+
projectId: options.projectId ?? source.projectId,
|
|
56
|
+
apiKey: options.apiKey ?? source.apiKey,
|
|
46
57
|
};
|
|
47
58
|
const missing = Object.entries(candidate).filter(([, value]) => !value).map(([key]) => key);
|
|
48
59
|
if (missing.length > 0) {
|
|
@@ -50,6 +61,17 @@ export async function resolveConnection(options = {}) {
|
|
|
50
61
|
}
|
|
51
62
|
return validateConnection(candidate);
|
|
52
63
|
}
|
|
64
|
+
export function resolveOnboardServer(options) {
|
|
65
|
+
if (options.server)
|
|
66
|
+
return normalizeServer(options.server);
|
|
67
|
+
const env = options.env ?? process.env;
|
|
68
|
+
const server = env.WITNORA_BASE_URL ?? env.AGENTCERT_BASE_URL;
|
|
69
|
+
const projectId = env.WITNORA_BASE_URL !== undefined ? env.WITNORA_PROJECT_ID : env.AGENTCERT_PROJECT_ID;
|
|
70
|
+
if (server && projectId && projectId !== options.projectId) {
|
|
71
|
+
throw new Error(`Onboarding project "${options.projectId}" differs from environment project "${projectId}". Pass --server <url> explicitly to choose the intended server; no authorization request was sent.`);
|
|
72
|
+
}
|
|
73
|
+
return normalizeServer(server ?? DEFAULT_WITNORA_SERVER);
|
|
74
|
+
}
|
|
53
75
|
export function credentialsPath(options = {}) {
|
|
54
76
|
const configHome = options.configHome
|
|
55
77
|
?? process.env.WITNORA_CONFIG_HOME
|
package/dist/gateway.js
CHANGED
|
@@ -919,6 +919,7 @@ export async function createContinuousAssuranceController(input) {
|
|
|
919
919
|
const managed = input.managed ?? await configManagedWorkflowHarnessImport();
|
|
920
920
|
let harness;
|
|
921
921
|
let harnessFingerprint;
|
|
922
|
+
let harnessHasRealPathBindings = false;
|
|
922
923
|
let timer;
|
|
923
924
|
let reconciling = false;
|
|
924
925
|
let closing = false;
|
|
@@ -942,29 +943,38 @@ export async function createContinuousAssuranceController(input) {
|
|
|
942
943
|
const previous = harness;
|
|
943
944
|
harness = next;
|
|
944
945
|
harnessFingerprint = fingerprint;
|
|
946
|
+
harnessHasRealPathBindings = config.schemaVersion === MANAGED_WORKFLOW_HARNESS_SCHEMA && Boolean(config.realPathActivations?.length);
|
|
945
947
|
await previous?.close();
|
|
946
948
|
};
|
|
947
949
|
const workflowHarnessPath = join(input.directory, "workflow-harness.json");
|
|
948
950
|
if (await exists(workflowHarnessPath)) {
|
|
949
951
|
try {
|
|
950
|
-
|
|
952
|
+
const current = parseManagedWorkflowHarnessConfig(await readFile(workflowHarnessPath, "utf8"));
|
|
953
|
+
// Reconcile generated provider bindings against this Agent before starting
|
|
954
|
+
// a cached Harness; older installations may contain another Agent's plan.
|
|
955
|
+
if (current.schemaVersion !== MANAGED_WORKFLOW_HARNESS_SCHEMA || !current.realPathActivations?.length)
|
|
956
|
+
await replaceHarness(current);
|
|
951
957
|
}
|
|
952
958
|
catch (error) {
|
|
953
959
|
lastReportedError = message(error);
|
|
954
960
|
process.stderr.write(`Existing Assurance Harness will be repaired in the background: ${lastReportedError}\n`);
|
|
955
961
|
}
|
|
956
962
|
}
|
|
957
|
-
const reconcile = async () => {
|
|
963
|
+
const reconcile = async (signal) => {
|
|
958
964
|
if (closing || reconciling)
|
|
959
965
|
return;
|
|
960
966
|
reconciling = true;
|
|
967
|
+
const reconciliationFetch = signal
|
|
968
|
+
? (resource, init) => request(resource, { ...init, signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal })
|
|
969
|
+
: request;
|
|
961
970
|
try {
|
|
962
971
|
const activation = await activateRealPathIntegrations({
|
|
963
972
|
repository: input.repository,
|
|
964
973
|
server: input.server,
|
|
965
974
|
projectId: input.projectId,
|
|
966
975
|
apiKey: input.apiKey,
|
|
967
|
-
|
|
976
|
+
agentIdentity: input.config.agentIdentity ?? { externalId: input.config.connectionName, version: "" },
|
|
977
|
+
fetch: reconciliationFetch,
|
|
968
978
|
});
|
|
969
979
|
if (activation.state === "READY_TO_START") {
|
|
970
980
|
const configured = await activateManagedWorkflowHarness({
|
|
@@ -975,7 +985,13 @@ export async function createContinuousAssuranceController(input) {
|
|
|
975
985
|
});
|
|
976
986
|
await replaceHarness(configured.config);
|
|
977
987
|
}
|
|
978
|
-
|
|
988
|
+
else if (harnessHasRealPathBindings) {
|
|
989
|
+
await harness?.close();
|
|
990
|
+
harness = undefined;
|
|
991
|
+
harnessFingerprint = undefined;
|
|
992
|
+
harnessHasRealPathBindings = false;
|
|
993
|
+
}
|
|
994
|
+
await publishCurrentChangeManifests({ ...input, fetch: reconciliationFetch }, harness ? await harness.status() : undefined);
|
|
979
995
|
lastReportedError = undefined;
|
|
980
996
|
}
|
|
981
997
|
catch (error) {
|
|
@@ -988,6 +1004,10 @@ export async function createContinuousAssuranceController(input) {
|
|
|
988
1004
|
reconciling = false;
|
|
989
1005
|
}
|
|
990
1006
|
};
|
|
1007
|
+
// The collector sends its first signed heartbeat as soon as its server starts.
|
|
1008
|
+
// Resolve the exact Harness first so HTTP readiness cannot race that heartbeat.
|
|
1009
|
+
// A failed or timed-out lookup leaves cached provider bindings unstarted.
|
|
1010
|
+
await reconcile(AbortSignal.timeout(5_000));
|
|
991
1011
|
return {
|
|
992
1012
|
start() {
|
|
993
1013
|
if (timer || closing)
|
package/dist/onboard.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { credentialsPath,
|
|
5
|
+
import { credentialsPath, loadConnection, resolveOnboardServer, saveConnection } from "./credentials.js";
|
|
6
6
|
import { authorizeProjectConnection } from "./device-authorization.js";
|
|
7
7
|
import { verifyControlPlaneConnection } from "./control-plane.js";
|
|
8
8
|
import { inferPrivateCapabilities, runPrivateCapabilityDiscovery } from "./private-discovery.js";
|
|
@@ -17,7 +17,7 @@ import { runCustomerGatewayCommand } from "./gateway-exec.js";
|
|
|
17
17
|
export async function runOnboard(options) {
|
|
18
18
|
const requestFetch = options.fetch ?? fetch;
|
|
19
19
|
const output = options.output ?? ((message) => process.stdout.write(message));
|
|
20
|
-
const server =
|
|
20
|
+
const server = resolveOnboardServer(options);
|
|
21
21
|
const repositoryPath = resolve(options.repository ?? process.cwd());
|
|
22
22
|
const repository = await inspectRepository(repositoryPath, options.template);
|
|
23
23
|
const agentIdentity = {
|
|
@@ -249,7 +249,7 @@ export async function runOnboard(options) {
|
|
|
249
249
|
}
|
|
250
250
|
const runtimeConfigured = await gatewayHasRuntimeWorker(repositoryPath);
|
|
251
251
|
generatedFiles.push(...await generateAutopilotFiles(repositoryPath, repository.name, runtimeConfigured));
|
|
252
|
-
realPathActivation = await activateRealPathIntegrations({ repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey, env: options.env, fetch: requestFetch });
|
|
252
|
+
realPathActivation = await activateRealPathIntegrations({ repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey, agentIdentity, env: options.env, fetch: requestFetch });
|
|
253
253
|
generatedFiles.push(...realPathActivation.generatedFiles);
|
|
254
254
|
try {
|
|
255
255
|
const activation = await activateManagedWorkflowHarness({ repository: repositoryPath, realPathActivations: realPathActivation.activations, previousGeneratedModuleSha256: realPathActivation.previousGeneratedModuleSha256 });
|
|
@@ -352,6 +352,7 @@ export async function runOnboard(options) {
|
|
|
352
352
|
output(`Keep this command open for up to ${Math.max(1, Math.ceil((options.waitForActionPathMs ?? 0) / 60_000))} minutes. It will finish automatically after you confirm the Business Task in the browser; no second onboard command is required. Press Ctrl+C to pause safely.\n`);
|
|
353
353
|
const resolution = await waitForOnboardingResolution({
|
|
354
354
|
repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey,
|
|
355
|
+
agentIdentity,
|
|
355
356
|
env: options.env, fetch: requestFetch, sleep: options.sleep, timeoutMs: options.waitForActionPathMs ?? 0,
|
|
356
357
|
});
|
|
357
358
|
if (resolution?.kind === "LOCAL_OUTPUT") {
|
|
@@ -420,13 +421,14 @@ export async function runOnboard(options) {
|
|
|
420
421
|
output(`Action transport: ${actionTransportTest.transport.toUpperCase()} is waiting for one exact sandbox Task/action path. ${actionTransportTest.limitation}\n`);
|
|
421
422
|
}
|
|
422
423
|
output(localOutputTaskConfirmed
|
|
423
|
-
? "
|
|
424
|
+
? "Setup complete. Check this Agent's connection in the workspace; setup alone does not establish a live connection for this Agent, version, and environment. The approved read-only local-output task is confirmed; no external Action transport is required.\n"
|
|
424
425
|
: actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
|
|
425
426
|
? `Base Gateway connected, but ${actionTransportTest.transport.toUpperCase()} Action is not verified yet. Run the Agent once and confirm its Business Task in the browser. Recovery only: rerun this command if the original waiting process was closed.\n`
|
|
426
|
-
: "
|
|
427
|
+
: "Setup complete. Check this Agent's connection in the workspace; setup alone does not establish a live connection for this Agent, version, and environment. Run it normally to send source-signed activity.\n");
|
|
427
428
|
output(localWorkflowCommand
|
|
428
429
|
? `Local sandbox command: ${agentRunCommand}\n`
|
|
429
430
|
: `For a local sandbox workflow, use npx --yes witnora@${cliVersion} gateway exec -- <your normal sandbox command> so the Gateway token stays out of shell history.\n`);
|
|
431
|
+
output("To stop now: npx witnora@latest gateway stop. To remove automatic startup: npx witnora@latest gateway service uninstall. Run these in this Agent repository; saved credentials and evidence are retained.\n");
|
|
430
432
|
return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
|
|
431
433
|
repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
|
|
432
434
|
gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
|
|
@@ -12,7 +12,28 @@ export async function activateRealPathIntegrations(options) {
|
|
|
12
12
|
throw new Error(`Could not load approved real-path integrations (${response.status}).`);
|
|
13
13
|
const body = await boundedJson(response);
|
|
14
14
|
const approved = Array.isArray(body.integrations) ? body.integrations.map(parsePlan).filter((plan) => plan.status === "READY_TO_ACTIVATE" || plan.status === "HARNESS_ACTIVE") : [];
|
|
15
|
-
|
|
15
|
+
let plans = approved.filter((plan) => plan.projectId === options.projectId && ["STRIPE_REFUND", "SHOPIFY_DISPUTE", "ZENDESK_TICKET", "SALESFORCE_RECORD", "HUBSPOT_CRM_RECORD", "POSTGRES_RECORD", "QUEUE_JOB"].includes(plan.generated.providerPackId) && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
|
|
16
|
+
if (plans.length && options.agentIdentity) {
|
|
17
|
+
const identity = options.agentIdentity;
|
|
18
|
+
if (!identity.externalId || !identity.version)
|
|
19
|
+
plans = [];
|
|
20
|
+
else {
|
|
21
|
+
const setupResponse = await request(`${base}/v1/projects/${encodeURIComponent(options.projectId)}/setup-plans`, { method: "GET", headers: { authorization: `Bearer ${options.apiKey}` } });
|
|
22
|
+
if (!setupResponse.ok)
|
|
23
|
+
throw new Error(`Could not resolve this Agent's exact setup binding (${setupResponse.status}). No provider preflight was attempted.`);
|
|
24
|
+
const setup = await boundedJson(setupResponse);
|
|
25
|
+
const plan = setup.plan;
|
|
26
|
+
const connectedAgentId = plan?.autopilot?.install?.connectedAgentId;
|
|
27
|
+
// Autopilot credentials can read their own setup, not the workspace-wide
|
|
28
|
+
// Agent directory. Installation events bind the canonical ID to the exact
|
|
29
|
+
// locally observed identity; before that event, activation stays pending.
|
|
30
|
+
const matches = Array.isArray(setup.events) ? setup.events.filter((event) => event?.type === "execution_completed"
|
|
31
|
+
&& event.details?.connectedAgentExternalId === identity.externalId && event.details?.connectedAgentVersion === identity.version
|
|
32
|
+
&& typeof event.details?.connectedAgentId === "string" && (!connectedAgentId || event.details.connectedAgentId === connectedAgentId)) : [];
|
|
33
|
+
const ids = [...new Set(matches.map((event) => event.details.connectedAgentId))];
|
|
34
|
+
plans = ids.length === 1 ? plans.filter((integration) => integration.subject.agentId === ids[0] && integration.subject.agentVersion === identity.version) : [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
16
37
|
if (!plans.length)
|
|
17
38
|
return { state: "NOT_CONFIGURED", created: false, activations: [], generatedFiles: [], rollback: async () => undefined };
|
|
18
39
|
const environment = options.env ?? process.env;
|