witnora 0.20.6 → 0.20.7

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/README.md CHANGED
@@ -56,8 +56,17 @@ explicit GitHub authorization and is never enabled implicitly.
56
56
  ### Customer-owned Gateway
57
57
 
58
58
  The default Setup Wizard installs and starts a customer-owned Gateway beside
59
- the Agent automatically. Run the Agent's normal sandbox workflow. Routine
60
- Gateway operations are available without keeping another terminal open:
59
+ the Agent automatically. For local sandbox runs, launch the Agent through the
60
+ credential wrapper printed by onboarding. It injects the local Gateway binding
61
+ only into that child process, without placing the Gateway token in shell history:
62
+
63
+ ```bash
64
+ npx witnora@latest gateway exec -- python scripts/run_workflow.py
65
+ ```
66
+
67
+ Production Agent platforms should provide the same two environment variables
68
+ through their own secret store. Routine Gateway operations are available
69
+ without keeping another terminal open:
61
70
 
62
71
  ```bash
63
72
  npx witnora@latest gateway status
package/dist/cli.js CHANGED
@@ -42,6 +42,7 @@ import { renderReleaseEvaluation, runReleaseEvaluation } from "./release-evaluat
42
42
  import { runPrivateCapabilityDiscovery } from "./private-discovery.js";
43
43
  import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, superviseManagedCustomerGateway, } from "./gateway.js";
44
44
  import { installCurrentGatewayService, stopCurrentGatewayOwners, uninstallCurrentGatewayService } from "./gateway-service.js";
45
+ import { runCustomerGatewayCommand } from "./gateway-exec.js";
45
46
  import { verifyEvidencePacketV02 } from "./evidence-v02.js";
46
47
  process.on("uncaughtException", reportFatalError);
47
48
  process.on("unhandledRejection", reportFatalError);
@@ -276,6 +277,24 @@ else if (command === "gateway") {
276
277
  }, () => stopManagedCustomerGateway({ repository, dir, configHome }));
277
278
  process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
278
279
  }
280
+ else if (action === "exec") {
281
+ const boundary = process.argv.indexOf("--", 4);
282
+ const wrapperArgs = boundary >= 0 ? process.argv.slice(4, boundary) : [];
283
+ const commandArgs = boundary >= 0 ? process.argv.slice(boundary + 1) : [];
284
+ if (!commandArgs[0])
285
+ throw new Error("Use witnora gateway exec [--repo <path>] [--dir <path>] -- <agent command> [arguments].");
286
+ const wrapperFlag = (name) => {
287
+ const index = wrapperArgs.indexOf(name);
288
+ return index >= 0 ? wrapperArgs[index + 1] : undefined;
289
+ };
290
+ const result = await runCustomerGatewayCommand({
291
+ repository: wrapperFlag("--repo") ?? process.cwd(),
292
+ gatewayDirectory: wrapperFlag("--dir"),
293
+ command: commandArgs[0],
294
+ args: commandArgs.slice(1),
295
+ });
296
+ process.exitCode = result.exitCode;
297
+ }
279
298
  else if (action === "run") {
280
299
  await runCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
281
300
  }
@@ -297,7 +316,7 @@ else if (command === "gateway") {
297
316
  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`);
298
317
  }
299
318
  else {
300
- throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|run|workflow-harness.");
319
+ throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|exec|run|workflow-harness.");
301
320
  }
302
321
  }
303
322
  else if (command === "discover") {
@@ -0,0 +1,42 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ export async function runCustomerGatewayCommand(options) {
5
+ const repository = resolve(options.repository);
6
+ const gatewayDirectory = resolve(options.gatewayDirectory ?? join(repository, ".witnora", "gateway"));
7
+ const [config, secrets] = await Promise.all([
8
+ readJson(join(gatewayDirectory, "gateway.json"), "Gateway configuration"),
9
+ readJson(join(gatewayDirectory, "secrets.json"), "Gateway secrets"),
10
+ ]);
11
+ const host = typeof config.host === "string" && config.host.trim() ? config.host.trim() : "";
12
+ const port = Number(config.port);
13
+ const gatewayToken = typeof secrets.gatewayToken === "string" ? secrets.gatewayToken.trim() : "";
14
+ if (!host || !Number.isInteger(port) || port < 1 || port > 65_535 || !gatewayToken) {
15
+ throw new Error("The local Gateway binding is incomplete. Run witnora onboard from this repository before starting the Agent workflow.");
16
+ }
17
+ return await new Promise((resolveChild, rejectChild) => {
18
+ const child = spawn(options.command, options.args ?? [], {
19
+ cwd: repository,
20
+ env: {
21
+ ...process.env,
22
+ WITNORA_GATEWAY_URL: `http://${host}:${port}`,
23
+ WITNORA_GATEWAY_TOKEN: gatewayToken,
24
+ },
25
+ stdio: "inherit",
26
+ windowsHide: true,
27
+ });
28
+ child.once("error", (error) => rejectChild(new Error(`Could not start the Agent workflow command: ${error.message}`)));
29
+ child.once("exit", (code) => resolveChild({ exitCode: code ?? 1 }));
30
+ });
31
+ }
32
+ async function readJson(path, label) {
33
+ try {
34
+ const value = JSON.parse(await readFile(path, "utf8"));
35
+ if (!value || typeof value !== "object" || Array.isArray(value))
36
+ throw new Error("expected an object");
37
+ return value;
38
+ }
39
+ catch {
40
+ throw new Error(`${label} is unavailable. Run witnora onboard from this repository before starting the Agent workflow.`);
41
+ }
42
+ }
package/dist/onboard.js CHANGED
@@ -317,6 +317,8 @@ export async function runOnboard(options) {
317
317
  generatedFiles,
318
318
  })
319
319
  : undefined;
320
+ const localWorkflowCommand = await detectLocalWorkflowCommand(repositoryPath);
321
+ const cliVersion = await currentCliVersion();
320
322
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
321
323
  status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
322
324
  connectedAgent: agentIdentity,
@@ -348,6 +350,9 @@ export async function runOnboard(options) {
348
350
  output(actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
349
351
  ? `Base Gateway connected, but ${actionTransportTest.transport.toUpperCase()} Action is not verified yet. Run the Agent's normal sandbox workflow from this repository; there is no Run button in Overview. Once Overview receives the activity, confirm one sandbox Business Task and choose its exact discovered Agent action, then rerun this same command.\n`
350
352
  : "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");
353
+ output(localWorkflowCommand
354
+ ? `Local sandbox command: npx --yes witnora@${cliVersion} gateway exec -- ${localWorkflowCommand.join(" ")}\n`
355
+ : `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`);
351
356
  return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
352
357
  repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
353
358
  gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
@@ -594,6 +599,17 @@ function relativeGeneratedFiles(repositoryPath, files) {
594
599
  return files.map((file) => file.startsWith(repositoryPath) ? file.slice(repositoryPath.length + 1).replaceAll("\\", "/") : file).filter(Boolean);
595
600
  }
596
601
  function randomSuffix() { return Math.random().toString(36).slice(2, 10); }
602
+ async function detectLocalWorkflowCommand(repositoryPath) {
603
+ if (await exists(join(repositoryPath, "scripts", "run_workflow.py")))
604
+ return ["python", "scripts/run_workflow.py"];
605
+ if (await exists(join(repositoryPath, "scripts", "run-workflow.mjs")))
606
+ return ["node", "scripts/run-workflow.mjs"];
607
+ return undefined;
608
+ }
609
+ async function currentCliVersion() {
610
+ const manifest = await optionalJson(fileURLToPath(new URL("../package.json", import.meta.url)));
611
+ return typeof manifest?.version === "string" && manifest.version.trim() ? manifest.version.trim() : "latest";
612
+ }
597
613
  export async function inspectRepository(repositoryPath, explicitTemplate) {
598
614
  const entries = await readdir(repositoryPath, { withFileTypes: true });
599
615
  const names = entries.map((entry) => entry.name).sort();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.20.6",
3
+ "version": "0.20.7",
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",