witnora 0.20.6 → 0.20.8

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);
@@ -159,6 +160,7 @@ else if (command === "onboard") {
159
160
  repository: readFlag("--repo") ?? process.cwd(),
160
161
  template: readFlag("--template") ? parseAgentTemplate(readFlag("--template")) : undefined,
161
162
  actionTransport: readActionTransport(readFlag("--action-transport")),
163
+ waitForActionPathMs: readActionTransport(readFlag("--action-transport")) && !readBoolFlag("--no-wait-for-action-path") ? 10 * 60 * 1_000 : 0,
162
164
  openBrowser: readBoolFlag("--no-browser") ? async () => { } : undefined,
163
165
  });
164
166
  }
@@ -276,6 +278,24 @@ else if (command === "gateway") {
276
278
  }, () => stopManagedCustomerGateway({ repository, dir, configHome }));
277
279
  process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
278
280
  }
281
+ else if (action === "exec") {
282
+ const boundary = process.argv.indexOf("--", 4);
283
+ const wrapperArgs = boundary >= 0 ? process.argv.slice(4, boundary) : [];
284
+ const commandArgs = boundary >= 0 ? process.argv.slice(boundary + 1) : [];
285
+ if (!commandArgs[0])
286
+ throw new Error("Use witnora gateway exec [--repo <path>] [--dir <path>] -- <agent command> [arguments].");
287
+ const wrapperFlag = (name) => {
288
+ const index = wrapperArgs.indexOf(name);
289
+ return index >= 0 ? wrapperArgs[index + 1] : undefined;
290
+ };
291
+ const result = await runCustomerGatewayCommand({
292
+ repository: wrapperFlag("--repo") ?? process.cwd(),
293
+ gatewayDirectory: wrapperFlag("--dir"),
294
+ command: commandArgs[0],
295
+ args: commandArgs.slice(1),
296
+ });
297
+ process.exitCode = result.exitCode;
298
+ }
279
299
  else if (action === "run") {
280
300
  await runCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
281
301
  }
@@ -297,7 +317,7 @@ else if (command === "gateway") {
297
317
  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
318
  }
299
319
  else {
300
- throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|run|workflow-harness.");
320
+ throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|exec|run|workflow-harness.");
301
321
  }
302
322
  }
303
323
  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
@@ -304,7 +304,7 @@ export async function runOnboard(options) {
304
304
  const runtimeReadiness = doctor.overall === "READY_FOR_RUNTIME" && runtimeBinding
305
305
  ? { state: "LOCAL_SANDBOX_READY", checkedAt: new Date().toISOString(), limitations: [] }
306
306
  : { 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."] };
307
- const actionTransportTest = options.actionTransport
307
+ let actionTransportTest = options.actionTransport
308
308
  ? await runActionTransportTest({
309
309
  transport: options.actionTransport,
310
310
  repository: repositoryPath,
@@ -317,12 +317,60 @@ export async function runOnboard(options) {
317
317
  generatedFiles,
318
318
  })
319
319
  : undefined;
320
+ const localWorkflowCommand = await detectLocalWorkflowCommand(repositoryPath);
321
+ const cliVersion = await currentCliVersion();
322
+ const agentRunCommand = localWorkflowCommand
323
+ ? `npx --yes witnora@${cliVersion} gateway exec -- ${localWorkflowCommand.join(" ")}`
324
+ : undefined;
320
325
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
321
326
  status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
322
327
  connectedAgent: agentIdentity,
328
+ agentRunCommand,
323
329
  ...(runtimeBinding ? { runtimeBinding } : {}),
324
330
  });
325
- output(`\nWitnora Setup Autopilot completed for ${repository.name}.\n`);
331
+ if (actionTransportTest?.state === "WAITING_FOR_ACTION_PATH" && (options.waitForActionPathMs ?? 0) > 0) {
332
+ output("\nBase setup is complete. Onboarding is waiting for the Business Task and Action path; it has not claimed completion.\n");
333
+ output(agentRunCommand
334
+ ? `Open a second terminal in this repository and run the Agent safely once:\n${agentRunCommand}\n`
335
+ : "Run the Agent normally in its sandbox. If it has no work now, you may enter the workspace and finish Action verification after its first real run.\n");
336
+ 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`);
337
+ const lateActivation = await waitForRealPathActivation({
338
+ repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey,
339
+ env: options.env, fetch: requestFetch, sleep: options.sleep, timeoutMs: options.waitForActionPathMs ?? 0,
340
+ });
341
+ if (lateActivation) {
342
+ realPathActivation = lateActivation;
343
+ generatedFiles.push(...lateActivation.generatedFiles);
344
+ const harness = await activateManagedWorkflowHarness({ repository: repositoryPath, realPathActivations: lateActivation.activations, previousGeneratedModuleSha256: lateActivation.previousGeneratedModuleSha256 });
345
+ if (harness.created)
346
+ generatedFiles.push(harness.path);
347
+ assuranceHarnessReadiness = { state: "ACTIVE" };
348
+ httpActionSetup = await configureCustomerHttpAction({ repository: repositoryPath, activations: lateActivation.activations });
349
+ generatedFiles.push(...httpActionSetup.generatedFiles);
350
+ if (harness.changed || httpActionSetup.changed) {
351
+ await stopGatewayOwners();
352
+ if (!options.gatewayLifecycle) {
353
+ await installCurrentGatewayService({ repository: repositoryPath, cliEntry: fileURLToPath(new URL("./cli.js", import.meta.url)), configHome: options.configHome });
354
+ managedGateway = await waitForInstalledGatewayService({ repository: repositoryPath, fetch: requestFetch, sleep: options.sleep, timeoutMs: options.timeoutMs });
355
+ }
356
+ else {
357
+ managedGateway = await gatewayLifecycle.start({ repository: repositoryPath, configHome: options.configHome, fetch: requestFetch, output });
358
+ }
359
+ }
360
+ actionTransportTest = await runActionTransportTest({
361
+ transport: options.actionTransport, repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey,
362
+ repositoryIdentity: repository, httpActionReady: httpActionSetup.state === "READY" && Boolean(httpActionSetup.action),
363
+ fetch: requestFetch, generatedFiles,
364
+ });
365
+ await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
366
+ status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
367
+ connectedAgent: agentIdentity, agentRunCommand, ...(runtimeBinding ? { runtimeBinding } : {}),
368
+ });
369
+ }
370
+ }
371
+ output(actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
372
+ ? `\nWitnora base setup completed for ${repository.name}; Action onboarding is paused and still pending.\n`
373
+ : `\nWitnora Setup Autopilot completed for ${repository.name}.\n`);
326
374
  output(`Project: ${token.projectId}\nTemplate: ${repository.template} (${repository.kind})\n`);
327
375
  output(`Credentials: ${credentialsPath}\nSelf-test receipt: ${receiptPath}\n`);
328
376
  output(`Private discovery: ${discovery.capabilityCount} capability group(s); ${discovery.unknownCapabilityCount} pending confirmation.\n`);
@@ -346,8 +394,11 @@ export async function runOnboard(options) {
346
394
  output(`Action transport: ${actionTransportTest.transport.toUpperCase()} is waiting for one exact sandbox Task/action path. ${actionTransportTest.limitation}\n`);
347
395
  }
348
396
  output(actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
349
- ? `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`
397
+ ? `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`
350
398
  : "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");
399
+ output(localWorkflowCommand
400
+ ? `Local sandbox command: ${agentRunCommand}\n`
401
+ : `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
402
  return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
352
403
  repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
353
404
  gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
@@ -407,6 +458,21 @@ async function loadMatchingOnboardConnection(options) {
407
458
  return undefined;
408
459
  }
409
460
  }
461
+ async function waitForRealPathActivation(options) {
462
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
463
+ let waited = 0;
464
+ let delay = 2_000;
465
+ while (waited < options.timeoutMs) {
466
+ const activation = await activateRealPathIntegrations(options);
467
+ if (activation.activations.length)
468
+ return activation;
469
+ const interval = Math.min(delay, options.timeoutMs - waited);
470
+ await sleep(interval);
471
+ waited += interval;
472
+ delay = Math.min(15_000, Math.round(delay * 1.5));
473
+ }
474
+ return undefined;
475
+ }
410
476
  async function runActionTransportTest(options) {
411
477
  if (!options.httpActionReady)
412
478
  return {
@@ -594,6 +660,17 @@ function relativeGeneratedFiles(repositoryPath, files) {
594
660
  return files.map((file) => file.startsWith(repositoryPath) ? file.slice(repositoryPath.length + 1).replaceAll("\\", "/") : file).filter(Boolean);
595
661
  }
596
662
  function randomSuffix() { return Math.random().toString(36).slice(2, 10); }
663
+ async function detectLocalWorkflowCommand(repositoryPath) {
664
+ if (await exists(join(repositoryPath, "scripts", "run_workflow.py")))
665
+ return ["python", "scripts/run_workflow.py"];
666
+ if (await exists(join(repositoryPath, "scripts", "run-workflow.mjs")))
667
+ return ["node", "scripts/run-workflow.mjs"];
668
+ return undefined;
669
+ }
670
+ async function currentCliVersion() {
671
+ const manifest = await optionalJson(fileURLToPath(new URL("../package.json", import.meta.url)));
672
+ return typeof manifest?.version === "string" && manifest.version.trim() ? manifest.version.trim() : "latest";
673
+ }
597
674
  export async function inspectRepository(repositoryPath, explicitTemplate) {
598
675
  const entries = await readdir(repositoryPath, { withFileTypes: true });
599
676
  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.8",
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",