witnora 0.20.8 → 0.20.10
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 +7 -0
- package/dist/command-help.js +1 -0
- package/dist/gateway-exec.js +22 -3
- package/dist/onboard.js +47 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -162,6 +162,7 @@ else if (command === "onboard") {
|
|
|
162
162
|
actionTransport: readActionTransport(readFlag("--action-transport")),
|
|
163
163
|
waitForActionPathMs: readActionTransport(readFlag("--action-transport")) && !readBoolFlag("--no-wait-for-action-path") ? 10 * 60 * 1_000 : 0,
|
|
164
164
|
openBrowser: readBoolFlag("--no-browser") ? async () => { } : undefined,
|
|
165
|
+
confirmRunDetectedAgent: readBoolFlag("--no-run-agent") ? undefined : confirmDetectedAgentRun,
|
|
165
166
|
});
|
|
166
167
|
}
|
|
167
168
|
else if (command === "mcp") {
|
|
@@ -1045,6 +1046,12 @@ async function promptValue(label) {
|
|
|
1045
1046
|
prompt.close();
|
|
1046
1047
|
}
|
|
1047
1048
|
}
|
|
1049
|
+
async function confirmDetectedAgentRun(command) {
|
|
1050
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1051
|
+
return false;
|
|
1052
|
+
const answer = (await promptValue(`Run ${command.join(" ")} safely once now in this terminal? [Y/n] `)).toLowerCase();
|
|
1053
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
1054
|
+
}
|
|
1048
1055
|
async function promptSecret(label) {
|
|
1049
1056
|
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
1050
1057
|
throw new Error("Missing project API key. Set WITNORA_API_KEY (or legacy AGENTCERT_API_KEY) or pass --api-key in a trusted environment.");
|
package/dist/command-help.js
CHANGED
|
@@ -56,6 +56,7 @@ Options:
|
|
|
56
56
|
--repo <directory> Repository to configure (default: current directory)
|
|
57
57
|
--template <type> Override automatic repository detection
|
|
58
58
|
--action-transport <t> Test HTTP or MCP against the exact generated sandbox Action path
|
|
59
|
+
--no-run-agent Do not offer to run a detected bounded Agent command in this terminal
|
|
59
60
|
--no-browser Print the approval URL without opening it
|
|
60
61
|
`;
|
|
61
62
|
if (command === "mcp")
|
package/dist/gateway-exec.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
export async function runCustomerGatewayCommand(options) {
|
|
5
5
|
const repository = resolve(options.repository);
|
|
6
6
|
const gatewayDirectory = resolve(options.gatewayDirectory ?? join(repository, ".witnora", "gateway"));
|
|
@@ -14,8 +14,9 @@ export async function runCustomerGatewayCommand(options) {
|
|
|
14
14
|
if (!host || !Number.isInteger(port) || port < 1 || port > 65_535 || !gatewayToken) {
|
|
15
15
|
throw new Error("The local Gateway binding is incomplete. Run witnora onboard from this repository before starting the Agent workflow.");
|
|
16
16
|
}
|
|
17
|
+
const launch = await resolveLaunch(options.command, options.args ?? []);
|
|
17
18
|
return await new Promise((resolveChild, rejectChild) => {
|
|
18
|
-
const child = spawn(
|
|
19
|
+
const child = spawn(launch.command, launch.args, {
|
|
19
20
|
cwd: repository,
|
|
20
21
|
env: {
|
|
21
22
|
...process.env,
|
|
@@ -29,6 +30,24 @@ export async function runCustomerGatewayCommand(options) {
|
|
|
29
30
|
child.once("exit", (code) => resolveChild({ exitCode: code ?? 1 }));
|
|
30
31
|
});
|
|
31
32
|
}
|
|
33
|
+
async function resolveLaunch(command, args) {
|
|
34
|
+
const packageManager = basename(command).toLowerCase();
|
|
35
|
+
if (process.platform !== "win32" || packageManager !== "npm" && packageManager !== "npx")
|
|
36
|
+
return { command, args };
|
|
37
|
+
const cliName = packageManager === "npm" ? "npm-cli.js" : "npx-cli.js";
|
|
38
|
+
const candidates = [
|
|
39
|
+
packageManager === "npm" ? process.env.npm_execpath : undefined,
|
|
40
|
+
join(dirname(process.execPath), "node_modules", "npm", "bin", cliName),
|
|
41
|
+
].filter((candidate) => Boolean(candidate));
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
try {
|
|
44
|
+
await access(candidate);
|
|
45
|
+
return { command: process.execPath, args: [candidate, ...args] };
|
|
46
|
+
}
|
|
47
|
+
catch { /* try the next customer-local Node installation path */ }
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Could not locate ${packageManager} for the Agent workflow. Run the repository's direct Node or Python command instead.`);
|
|
50
|
+
}
|
|
32
51
|
async function readJson(path, label) {
|
|
33
52
|
try {
|
|
34
53
|
const value = JSON.parse(await readFile(path, "utf8"));
|
package/dist/onboard.js
CHANGED
|
@@ -13,6 +13,7 @@ import { discoverRuntimeReferences, planRuntimeSandboxModules } from "./runtime-
|
|
|
13
13
|
import { automaticRuntimeReferencesUsable, bootstrapLocalRuntime, verifyAutomaticRuntimeProbe } from "./runtime-bootstrap.js";
|
|
14
14
|
import { activateRealPathIntegrations } from "./real-path-activation.js";
|
|
15
15
|
import { installCurrentGatewayService, stopCurrentGatewayService } from "./gateway-service.js";
|
|
16
|
+
import { runCustomerGatewayCommand } from "./gateway-exec.js";
|
|
16
17
|
export async function runOnboard(options) {
|
|
17
18
|
const requestFetch = options.fetch ?? fetch;
|
|
18
19
|
const output = options.output ?? ((message) => process.stdout.write(message));
|
|
@@ -125,6 +126,7 @@ export async function runOnboard(options) {
|
|
|
125
126
|
let assuranceHarnessChanged = false;
|
|
126
127
|
let httpActionSetup;
|
|
127
128
|
let continuousService;
|
|
129
|
+
let localOutputTaskConfirmed = false;
|
|
128
130
|
const prepareRuntime = async (operation) => {
|
|
129
131
|
const delays = [250, 500, 1_000, 2_000, 4_000, 8_000];
|
|
130
132
|
let lastError;
|
|
@@ -330,15 +332,37 @@ export async function runOnboard(options) {
|
|
|
330
332
|
});
|
|
331
333
|
if (actionTransportTest?.state === "WAITING_FOR_ACTION_PATH" && (options.waitForActionPathMs ?? 0) > 0) {
|
|
332
334
|
output("\nBase setup is complete. Onboarding is waiting for the Business Task and Action path; it has not claimed completion.\n");
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
let ranDetectedAgent = false;
|
|
336
|
+
if (localWorkflowCommand && options.confirmRunDetectedAgent) {
|
|
337
|
+
output(`Detected Agent command: ${localWorkflowCommand.join(" ")}\n`);
|
|
338
|
+
if (await options.confirmRunDetectedAgent(localWorkflowCommand)) {
|
|
339
|
+
output("Running this Agent safely once in the current terminal...\n");
|
|
340
|
+
const [command, ...args] = localWorkflowCommand;
|
|
341
|
+
const result = await (options.runGatewayCommand ?? runCustomerGatewayCommand)({ repository: repositoryPath, command: command, args });
|
|
342
|
+
if (result.exitCode !== 0)
|
|
343
|
+
throw new Error(`The detected Agent command exited with code ${result.exitCode}. Onboarding remains fail closed.`);
|
|
344
|
+
output("Agent run completed. Keep the browser open and confirm the Business Task; this command will finish automatically.\n");
|
|
345
|
+
ranDetectedAgent = true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!ranDetectedAgent)
|
|
349
|
+
output(agentRunCommand
|
|
350
|
+
? `Run the Agent in its normal environment, or use this command from another terminal:\n${agentRunCommand}\n`
|
|
351
|
+
: "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
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`);
|
|
337
|
-
const
|
|
353
|
+
const resolution = await waitForOnboardingResolution({
|
|
338
354
|
repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey,
|
|
339
355
|
env: options.env, fetch: requestFetch, sleep: options.sleep, timeoutMs: options.waitForActionPathMs ?? 0,
|
|
340
356
|
});
|
|
341
|
-
if (
|
|
357
|
+
if (resolution?.kind === "LOCAL_OUTPUT") {
|
|
358
|
+
localOutputTaskConfirmed = true;
|
|
359
|
+
actionTransportTest = undefined;
|
|
360
|
+
if (assuranceHarnessReadiness.state === "WAITING_FOR_CUSTOMER_HARNESS") {
|
|
361
|
+
assuranceHarnessReadiness = { state: "NOT_REQUIRED_FOR_LOCAL_OUTPUT" };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (resolution?.kind === "REAL_PATH") {
|
|
365
|
+
const lateActivation = resolution.activation;
|
|
342
366
|
realPathActivation = lateActivation;
|
|
343
367
|
generatedFiles.push(...lateActivation.generatedFiles);
|
|
344
368
|
const harness = await activateManagedWorkflowHarness({ repository: repositoryPath, realPathActivations: lateActivation.activations, previousGeneratedModuleSha256: lateActivation.previousGeneratedModuleSha256 });
|
|
@@ -383,7 +407,9 @@ export async function runOnboard(options) {
|
|
|
383
407
|
: `Runtime: RECORDED_ONLY. ${runtimeReadiness.limitations[0]}\n`);
|
|
384
408
|
output(assuranceHarnessReadiness.state === "ACTIVE"
|
|
385
409
|
? `Assurance Harness: ACTIVE. Nora can dispatch authority-ready Replay and no-write Shadow evaluations without manual Workflow IDs, evaluator origins, credentials, or contract digests.${realPathActivation.activations.length ? ` ${realPathActivation.activations.length} exact sandbox real-path binding(s) passed read-only preflight.` : ""}\n`
|
|
386
|
-
:
|
|
410
|
+
: assuranceHarnessReadiness.state === "NOT_REQUIRED_FOR_LOCAL_OUTPUT"
|
|
411
|
+
? "Assurance Harness: NOT_REQUIRED_FOR_LOCAL_OUTPUT. The approved read-only local-output task is confirmed; no external Action transport is required.\n"
|
|
412
|
+
: `Assurance Harness: WAITING_FOR_CUSTOMER_HARNESS. ${assuranceHarnessReadiness.limitation}\n`);
|
|
387
413
|
if (httpActionSetup?.state === "READY" && httpActionSetup.action) {
|
|
388
414
|
output(`HTTP Action: READY. POST ${managedGateway.baseUrl}${httpActionSetup.action.path}; copy the action-scoped token and request example from .witnora/gateway/HTTP_ACTION.md.\n`);
|
|
389
415
|
}
|
|
@@ -393,9 +419,11 @@ export async function runOnboard(options) {
|
|
|
393
419
|
else if (actionTransportTest) {
|
|
394
420
|
output(`Action transport: ${actionTransportTest.transport.toUpperCase()} is waiting for one exact sandbox Task/action path. ${actionTransportTest.limitation}\n`);
|
|
395
421
|
}
|
|
396
|
-
output(
|
|
397
|
-
?
|
|
398
|
-
:
|
|
422
|
+
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
|
+
: actionTransportTest?.state === "WAITING_FOR_ACTION_PATH"
|
|
425
|
+
? `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");
|
|
399
427
|
output(localWorkflowCommand
|
|
400
428
|
? `Local sandbox command: ${agentRunCommand}\n`
|
|
401
429
|
: `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`);
|
|
@@ -458,14 +486,17 @@ async function loadMatchingOnboardConnection(options) {
|
|
|
458
486
|
return undefined;
|
|
459
487
|
}
|
|
460
488
|
}
|
|
461
|
-
async function
|
|
489
|
+
async function waitForOnboardingResolution(options) {
|
|
462
490
|
const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
463
491
|
let waited = 0;
|
|
464
492
|
let delay = 2_000;
|
|
465
493
|
while (waited < options.timeoutMs) {
|
|
466
494
|
const activation = await activateRealPathIntegrations(options);
|
|
467
495
|
if (activation.activations.length)
|
|
468
|
-
return activation;
|
|
496
|
+
return { kind: "REAL_PATH", activation };
|
|
497
|
+
const onboarding = await jsonRequest(options.fetch, `${options.server}/v1/projects/${encodeURIComponent(options.projectId)}/onboarding`, { method: "GET", headers: { authorization: `Bearer ${options.apiKey}` } });
|
|
498
|
+
if (onboarding.localOutputTaskConfirmed)
|
|
499
|
+
return { kind: "LOCAL_OUTPUT" };
|
|
469
500
|
const interval = Math.min(delay, options.timeoutMs - waited);
|
|
470
501
|
await sleep(interval);
|
|
471
502
|
waited += interval;
|
|
@@ -661,6 +692,11 @@ function relativeGeneratedFiles(repositoryPath, files) {
|
|
|
661
692
|
}
|
|
662
693
|
function randomSuffix() { return Math.random().toString(36).slice(2, 10); }
|
|
663
694
|
async function detectLocalWorkflowCommand(repositoryPath) {
|
|
695
|
+
const manifest = await optionalJson(join(repositoryPath, "package.json"));
|
|
696
|
+
const scripts = manifest?.scripts;
|
|
697
|
+
if (scripts && typeof scripts === "object" && !Array.isArray(scripts) && typeof scripts.agent === "string") {
|
|
698
|
+
return ["npm", "run", "agent"];
|
|
699
|
+
}
|
|
664
700
|
if (await exists(join(repositoryPath, "scripts", "run_workflow.py")))
|
|
665
701
|
return ["python", "scripts/run_workflow.py"];
|
|
666
702
|
if (await exists(join(repositoryPath, "scripts", "run-workflow.mjs")))
|