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 +11 -2
- package/dist/cli.js +21 -1
- package/dist/gateway-exec.js +42 -0
- package/dist/onboard.js +80 -3
- package/package.json +1 -1
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.
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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();
|