witnora 0.20.13 → 0.20.15

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 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") ?? brandedEnvironment("BASE_URL") ?? DEFAULT_WITNORA_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
- throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|exec|run|workflow-harness.");
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
@@ -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
@@ -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 stored = options.server && options.projectId && options.apiKey
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 && !options.server && !options.projectId && !options.apiKey) {
40
- return stored;
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 ?? env.WITNORA_BASE_URL ?? env.AGENTCERT_BASE_URL ?? stored?.server,
44
- projectId: options.projectId ?? env.WITNORA_PROJECT_ID ?? env.AGENTCERT_PROJECT_ID ?? stored?.projectId,
45
- apiKey: options.apiKey ?? env.WITNORA_API_KEY ?? env.AGENTCERT_API_KEY ?? stored?.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,18 @@ 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
+ const projectVariable = env.WITNORA_BASE_URL !== undefined ? "WITNORA_PROJECT_ID" : "AGENTCERT_PROJECT_ID";
72
+ throw new Error(`Onboarding project "${options.projectId}" differs from environment project "${projectId}". Update or unset ${projectVariable}, or pass --server <url> explicitly to choose the intended server; no authorization request was sent.`);
73
+ }
74
+ return normalizeServer(server ?? DEFAULT_WITNORA_SERVER);
75
+ }
53
76
  export function credentialsPath(options = {}) {
54
77
  const configHome = options.configHome
55
78
  ?? 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
- await replaceHarness(parseManagedWorkflowHarnessConfig(await readFile(workflowHarnessPath, "utf8")));
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
- fetch: request,
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
- await publishCurrentChangeManifests({ ...input, fetch: request }, harness ? await harness.status() : undefined);
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, DEFAULT_WITNORA_SERVER, loadConnection, saveConnection } from "./credentials.js";
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 = normalizeServer(options.server ?? DEFAULT_WITNORA_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
- ? "Connected. Nora is monitoring this Agent. The approved read-only local-output task is confirmed; no external Action transport is required.\n"
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
- : "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");
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
- const plans = approved.filter((plan) => ["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");
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.20.13",
3
+ "version": "0.20.15",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",