@proagentstore/cli 0.1.6 → 0.1.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.
|
@@ -25,7 +25,7 @@ function configFromArgs() {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
if (flag("--help") || flag("-h")) {
|
|
28
|
-
process.stdout.write(`
|
|
28
|
+
process.stdout.write(`FreeAgentStore browser runtime for ProAgentStore
|
|
29
29
|
|
|
30
30
|
Usage:
|
|
31
31
|
pags-browser-runner [--host 127.0.0.1] [--port 49171] [--data-dir path] [--token token] [--instance-id id] [--headless]
|
|
@@ -44,9 +44,9 @@ Endpoints:
|
|
|
44
44
|
}
|
|
45
45
|
const config = configFromArgs();
|
|
46
46
|
const started = await startRunnerServer(config);
|
|
47
|
-
process.stdout.write(`
|
|
47
|
+
process.stdout.write(`FAGS browser runtime listening at ${started.url}\n`);
|
|
48
48
|
process.stdout.write(`Data dir: ${config.dataDir}\n`);
|
|
49
|
-
process.stdout.write(
|
|
49
|
+
process.stdout.write("Control plane: PAGS; runtime plane: FAGS; brain placement: PAGS; runner role: tool-executor\n");
|
|
50
50
|
if (config.token)
|
|
51
51
|
process.stdout.write("Auth: bearer token required\n");
|
|
52
52
|
if (config.instanceId)
|
|
@@ -28,8 +28,10 @@ export class LocalRunner {
|
|
|
28
28
|
}
|
|
29
29
|
capabilities() {
|
|
30
30
|
return {
|
|
31
|
-
runtime: "
|
|
31
|
+
runtime: "fags-browser-runtime",
|
|
32
32
|
brainPlacement: "pags",
|
|
33
|
+
controlPlane: "pags",
|
|
34
|
+
runtimePlane: "fags",
|
|
33
35
|
runnerRole: "tool-executor",
|
|
34
36
|
capabilities: CAPABILITIES,
|
|
35
37
|
taskTypes: ["echo", "browser.open", "job.apply_basic"],
|
|
@@ -42,8 +42,10 @@ async function route(runner, req, res) {
|
|
|
42
42
|
if (req.method === "GET" && path === "/health") {
|
|
43
43
|
return json(res, 200, {
|
|
44
44
|
ok: true,
|
|
45
|
-
service: "
|
|
45
|
+
service: "freeagentstore-browser-runtime",
|
|
46
46
|
brainPlacement: "pags",
|
|
47
|
+
controlPlane: "pags",
|
|
48
|
+
runtimePlane: "fags",
|
|
47
49
|
instanceId: runner.config.instanceId,
|
|
48
50
|
});
|
|
49
51
|
}
|
package/dist/index.js
CHANGED
|
@@ -472,6 +472,7 @@ var publishCommand = new Command4("publish").description("Publish an agent to Pr
|
|
|
472
472
|
|
|
473
473
|
// src/commands/runner.ts
|
|
474
474
|
import { spawn as spawn2 } from "child_process";
|
|
475
|
+
import { randomUUID } from "crypto";
|
|
475
476
|
import { existsSync as existsSync4 } from "fs";
|
|
476
477
|
import { resolve as resolve4 } from "path";
|
|
477
478
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -507,6 +508,12 @@ function buildRunnerArgs(opts) {
|
|
|
507
508
|
if (opts.headless) args.push("--headless");
|
|
508
509
|
return args;
|
|
509
510
|
}
|
|
511
|
+
function buildCloudflaredArgs(localUrl) {
|
|
512
|
+
return ["tunnel", "--url", localUrl];
|
|
513
|
+
}
|
|
514
|
+
function parseCloudflaredTunnelUrl(text) {
|
|
515
|
+
return text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i)?.[0] || null;
|
|
516
|
+
}
|
|
510
517
|
function buildRuntimeRegistrationBody(opts, capabilities = []) {
|
|
511
518
|
return {
|
|
512
519
|
endpointUrl: clean(opts.endpointUrl) || opts.endpointUrl,
|
|
@@ -533,7 +540,7 @@ function findWorkspaceRoot() {
|
|
|
533
540
|
function bundledRunnerPath() {
|
|
534
541
|
return fileURLToPath2(new URL("./browser-runner/index.js", import.meta.url));
|
|
535
542
|
}
|
|
536
|
-
function
|
|
543
|
+
function runnerSpawnSpec(opts) {
|
|
537
544
|
const root = findWorkspaceRoot();
|
|
538
545
|
const localPackage = resolve4(root, "packages", "browser-runner", "src", "index.ts");
|
|
539
546
|
const bundledPackage = bundledRunnerPath();
|
|
@@ -549,9 +556,13 @@ function startRunnerForeground(opts) {
|
|
|
549
556
|
command = process.execPath;
|
|
550
557
|
args = [bundledPackage, ...runnerArgs];
|
|
551
558
|
}
|
|
559
|
+
return { command, args, cwd };
|
|
560
|
+
}
|
|
561
|
+
function startRunnerForeground(opts) {
|
|
562
|
+
const spec = runnerSpawnSpec(opts);
|
|
552
563
|
return new Promise((resolvePromise, reject) => {
|
|
553
|
-
const child = spawn2(command, args, {
|
|
554
|
-
cwd,
|
|
564
|
+
const child = spawn2(spec.command, spec.args, {
|
|
565
|
+
cwd: spec.cwd,
|
|
555
566
|
stdio: "inherit",
|
|
556
567
|
shell: process.platform === "win32"
|
|
557
568
|
});
|
|
@@ -562,6 +573,20 @@ function startRunnerForeground(opts) {
|
|
|
562
573
|
});
|
|
563
574
|
});
|
|
564
575
|
}
|
|
576
|
+
async function waitForLocalRunner(opts, timeoutMs = 15e3) {
|
|
577
|
+
const started = Date.now();
|
|
578
|
+
let lastError;
|
|
579
|
+
while (Date.now() - started < timeoutMs) {
|
|
580
|
+
try {
|
|
581
|
+
await requestRunner("GET", "/health", opts);
|
|
582
|
+
return;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
lastError = error;
|
|
585
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
throw new Error(`runner did not become healthy: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
589
|
+
}
|
|
565
590
|
async function requestRunner(method, path, opts, body) {
|
|
566
591
|
const headers = {
|
|
567
592
|
...runnerRequestHeaders(opts)
|
|
@@ -616,17 +641,112 @@ function collectCapability(value, previous = []) {
|
|
|
616
641
|
}
|
|
617
642
|
function createRunnerCommand() {
|
|
618
643
|
const command = new Command5("runner").description(
|
|
619
|
-
"Manage
|
|
644
|
+
"Manage the local FAGS browser runtime for ProAgentStore agents"
|
|
620
645
|
);
|
|
621
|
-
command.command("start").description("Start the local browser
|
|
646
|
+
command.command("start").description("Start the local FAGS browser runtime in the foreground").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Require this bearer token").option("--instance-id <id>", "Bind runner requests to a PAGS instance id").option("--headless", "Run Playwright headless").action(async (opts) => {
|
|
622
647
|
await startRunnerForeground(opts);
|
|
623
648
|
});
|
|
624
|
-
command.command("
|
|
649
|
+
command.command("connect <instanceId>").description("Start the local FAGS runtime, open a Cloudflare quick tunnel, and register it with PAGS").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Runner bearer token. Defaults to PAGS_RUNNER_TOKEN or a generated token").option("--headless", "Run Playwright headless").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--cloudflared <path>", "cloudflared executable", "cloudflared").option("--runner-version <version>", "Runner version").option("--skip-probe", "Skip PAGS FAGS-runtime probe after registration").action(async (instanceId, opts) => {
|
|
650
|
+
const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`;
|
|
651
|
+
const host = clean(opts.host) || "127.0.0.1";
|
|
652
|
+
const port = clean(opts.port) || "49171";
|
|
653
|
+
const localUrl = `http://${host}:${port}`;
|
|
654
|
+
const runnerOpts = {
|
|
655
|
+
...opts,
|
|
656
|
+
host,
|
|
657
|
+
port,
|
|
658
|
+
token: runnerToken,
|
|
659
|
+
instanceId
|
|
660
|
+
};
|
|
661
|
+
const spec = runnerSpawnSpec(runnerOpts);
|
|
662
|
+
const runner = spawn2(spec.command, spec.args, {
|
|
663
|
+
cwd: spec.cwd,
|
|
664
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
665
|
+
shell: process.platform === "win32"
|
|
666
|
+
});
|
|
667
|
+
runner.stdout?.on("data", (data) => process.stdout.write(data));
|
|
668
|
+
runner.stderr?.on("data", (data) => process.stderr.write(data));
|
|
669
|
+
runner.on("exit", (code) => {
|
|
670
|
+
if (code && code !== 0) writeError(`runner exited with code ${code}`);
|
|
671
|
+
});
|
|
672
|
+
let tunnel = null;
|
|
673
|
+
const shutdown = () => {
|
|
674
|
+
if (tunnel && !tunnel.killed) tunnel.kill("SIGTERM");
|
|
675
|
+
if (!runner.killed) runner.kill("SIGTERM");
|
|
676
|
+
};
|
|
677
|
+
process.once("SIGINT", () => {
|
|
678
|
+
shutdown();
|
|
679
|
+
process.exit(0);
|
|
680
|
+
});
|
|
681
|
+
process.once("SIGTERM", () => {
|
|
682
|
+
shutdown();
|
|
683
|
+
process.exit(0);
|
|
684
|
+
});
|
|
685
|
+
try {
|
|
686
|
+
await waitForLocalRunner({ url: localUrl, token: runnerToken, instanceId });
|
|
687
|
+
writeLine(`Local FAGS runtime healthy at ${localUrl}`);
|
|
688
|
+
const tunnelUrl = await new Promise((resolveTunnel, reject) => {
|
|
689
|
+
let output = "";
|
|
690
|
+
const cloudflared = clean(opts.cloudflared) || "cloudflared";
|
|
691
|
+
tunnel = spawn2(cloudflared, buildCloudflaredArgs(localUrl), {
|
|
692
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
693
|
+
shell: process.platform === "win32"
|
|
694
|
+
});
|
|
695
|
+
const onData = (data) => {
|
|
696
|
+
const text = data.toString("utf-8");
|
|
697
|
+
output += text;
|
|
698
|
+
process.stderr.write(text);
|
|
699
|
+
const parsed = parseCloudflaredTunnelUrl(output);
|
|
700
|
+
if (parsed) resolveTunnel(parsed);
|
|
701
|
+
};
|
|
702
|
+
tunnel.stdout?.on("data", onData);
|
|
703
|
+
tunnel.stderr?.on("data", onData);
|
|
704
|
+
tunnel.on("error", reject);
|
|
705
|
+
tunnel.on("exit", (code) => {
|
|
706
|
+
if (code && code !== 0) reject(new Error(`cloudflared exited with code ${code}`));
|
|
707
|
+
});
|
|
708
|
+
setTimeout(() => reject(new Error("timed out waiting for cloudflared tunnel URL")), 45e3).unref();
|
|
709
|
+
});
|
|
710
|
+
writeLine(`Tunnel ready: ${tunnelUrl}`);
|
|
711
|
+
await waitForLocalRunner({ url: tunnelUrl, token: runnerToken, instanceId }, 3e4);
|
|
712
|
+
writeLine("Tunnel health check passed");
|
|
713
|
+
const capabilities = await requestRunner("GET", "/capabilities", {
|
|
714
|
+
url: tunnelUrl,
|
|
715
|
+
token: runnerToken,
|
|
716
|
+
instanceId
|
|
717
|
+
});
|
|
718
|
+
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, {
|
|
719
|
+
endpointUrl: tunnelUrl,
|
|
720
|
+
token: runnerToken,
|
|
721
|
+
placement: "local",
|
|
722
|
+
capabilities: Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [],
|
|
723
|
+
runnerVersion: clean(opts.runnerVersion) || ""
|
|
724
|
+
});
|
|
725
|
+
writeLine(JSON.stringify(result, null, 2));
|
|
726
|
+
if (!opts.skipProbe) {
|
|
727
|
+
const status = await requestPags(
|
|
728
|
+
"GET",
|
|
729
|
+
`/v1/instances/${apiPathSegment(instanceId)}/runtime/status`,
|
|
730
|
+
opts
|
|
731
|
+
);
|
|
732
|
+
writeLine(JSON.stringify(status, null, 2));
|
|
733
|
+
}
|
|
734
|
+
writeLine("Connected. Keep this process running while the agent uses your FAGS browser runtime.");
|
|
735
|
+
await new Promise((resolvePromise) => {
|
|
736
|
+
runner.on("exit", () => resolvePromise());
|
|
737
|
+
tunnel?.on("exit", () => resolvePromise());
|
|
738
|
+
});
|
|
739
|
+
} catch (error) {
|
|
740
|
+
shutdown();
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
command.command("status").description("Check local FAGS runtime health and capabilities").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (opts) => {
|
|
625
745
|
const health = await requestRunner("GET", "/health", opts);
|
|
626
746
|
const capabilities = await requestRunner("GET", "/capabilities", opts);
|
|
627
747
|
writeLine(JSON.stringify({ health, capabilities }, null, 2));
|
|
628
748
|
});
|
|
629
|
-
command.command("task").description("Create a
|
|
749
|
+
command.command("task").description("Create a local FAGS runtime task").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
|
|
630
750
|
async (opts) => {
|
|
631
751
|
let input = {};
|
|
632
752
|
if (opts.input) {
|
|
@@ -647,7 +767,7 @@ function createRunnerCommand() {
|
|
|
647
767
|
writeLine(JSON.stringify(task, null, 2));
|
|
648
768
|
}
|
|
649
769
|
);
|
|
650
|
-
command.command("register <instanceId>").description("Register a local or managed
|
|
770
|
+
command.command("register <instanceId>").description("Register a local or managed FAGS runtime endpoint with a PAGS instance").requiredOption("--endpoint-url <url>", "FAGS runtime endpoint URL to store in PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--runner-token <token>", "FAGS runtime bearer token to store in PAGS").option("--url <url>", "Local FAGS runtime URL to probe for capabilities").option("--token <token>", "Local FAGS runtime bearer token for capability probe").option("--instance-id <id>", "PAGS instance id header for capability probe").option("--placement <placement>", "Runtime placement: local or managed", "local").option("--runner-version <version>", "Runner version").option("--capability <name>", "Runtime capability; repeatable", collectCapability, []).option("--probe", "Read capabilities from the FAGS runtime before registering").action(async (instanceId, opts) => {
|
|
651
771
|
let capabilities = opts.capability || [];
|
|
652
772
|
if (opts.probe) {
|
|
653
773
|
const data = await requestRunner("GET", "/capabilities", {
|
|
@@ -661,7 +781,7 @@ function createRunnerCommand() {
|
|
|
661
781
|
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts, body);
|
|
662
782
|
writeLine(JSON.stringify(result, null, 2));
|
|
663
783
|
});
|
|
664
|
-
command.command("runtime <instanceId>").description("Read the PAGS runtime registration for an instance").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--probe", "Ask PAGS to probe /health and /capabilities on the
|
|
784
|
+
command.command("runtime <instanceId>").description("Read the PAGS runtime registration for an instance").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--probe", "Ask PAGS to probe /health and /capabilities on the FAGS runtime").action(async (instanceId, opts) => {
|
|
665
785
|
const path = opts.probe ? `/v1/instances/${apiPathSegment(instanceId)}/runtime/status` : `/v1/instances/${apiPathSegment(instanceId)}/runtime`;
|
|
666
786
|
const result = await requestPags("GET", path, opts);
|
|
667
787
|
writeLine(JSON.stringify(result, null, 2));
|
|
@@ -670,7 +790,7 @@ function createRunnerCommand() {
|
|
|
670
790
|
const result = await requestPags("DELETE", `/v1/instances/${apiPathSegment(instanceId)}/runtime`, opts);
|
|
671
791
|
writeLine(JSON.stringify(result, null, 2));
|
|
672
792
|
});
|
|
673
|
-
command.command("run <instanceId>").description("Create a task through PAGS on an instance's registered
|
|
793
|
+
command.command("run <instanceId>").description("Create a task through PAGS on an instance's registered FAGS runtime").requiredOption("--type <type>", "Task type, e.g. echo or browser.open").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--input <json>", "Task input JSON").option("--job-url <url>", "Shortcut input for browser.open").option("--approve", "Create task in needs_approval state").option("--approval-prompt <text>", "Approval prompt").action(
|
|
674
794
|
async (instanceId, opts) => {
|
|
675
795
|
let input = {};
|
|
676
796
|
if (opts.input) {
|
|
@@ -691,7 +811,7 @@ function createRunnerCommand() {
|
|
|
691
811
|
writeLine(JSON.stringify(result, null, 2));
|
|
692
812
|
}
|
|
693
813
|
);
|
|
694
|
-
command.command("approve-task <instanceId> <taskId>").description("Approve a registered
|
|
814
|
+
command.command("approve-task <instanceId> <taskId>").description("Approve a registered FAGS runtime task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
|
|
695
815
|
const result = await requestPags(
|
|
696
816
|
"POST",
|
|
697
817
|
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/approve`,
|
|
@@ -699,7 +819,7 @@ function createRunnerCommand() {
|
|
|
699
819
|
);
|
|
700
820
|
writeLine(JSON.stringify(result, null, 2));
|
|
701
821
|
});
|
|
702
|
-
command.command("cancel-task <instanceId> <taskId>").description("Cancel a registered
|
|
822
|
+
command.command("cancel-task <instanceId> <taskId>").description("Cancel a registered FAGS runtime task through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").action(async (instanceId, taskId, opts) => {
|
|
703
823
|
const result = await requestPags(
|
|
704
824
|
"POST",
|
|
705
825
|
`/v1/instances/${apiPathSegment(instanceId)}/tasks/${apiPathSegment(taskId)}/cancel`,
|
|
@@ -707,7 +827,7 @@ function createRunnerCommand() {
|
|
|
707
827
|
);
|
|
708
828
|
writeLine(JSON.stringify(result, null, 2));
|
|
709
829
|
});
|
|
710
|
-
command.command("task-events <instanceId>").description("Read registered
|
|
830
|
+
command.command("task-events <instanceId>").description("Read registered FAGS runtime task events through PAGS").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--limit <n>", "Number of events", "50").action(async (instanceId, opts) => {
|
|
711
831
|
const result = await requestPags(
|
|
712
832
|
"GET",
|
|
713
833
|
`/v1/instances/${apiPathSegment(instanceId)}/task-events?limit=${Number(opts.limit) || 50}`,
|
|
@@ -719,11 +839,11 @@ function createRunnerCommand() {
|
|
|
719
839
|
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/approve`, opts);
|
|
720
840
|
writeLine(JSON.stringify(task, null, 2));
|
|
721
841
|
});
|
|
722
|
-
command.command("cancel <taskId>").description("Cancel a local
|
|
842
|
+
command.command("cancel <taskId>").description("Cancel a local FAGS runtime task").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").action(async (taskId, opts) => {
|
|
723
843
|
const task = await requestRunner("POST", `/tasks/${apiPathSegment(taskId)}/cancel`, opts);
|
|
724
844
|
writeLine(JSON.stringify(task, null, 2));
|
|
725
845
|
});
|
|
726
|
-
command.command("events").description("List recent
|
|
846
|
+
command.command("events").description("List recent FAGS runtime events").option("--url <url>", "Runner URL").option("--token <token>", "Runner bearer token").option("--instance-id <id>", "PAGS instance id header").option("--limit <n>", "Number of events", "50").action(async (opts) => {
|
|
727
847
|
const events = await requestRunner("GET", `/events?limit=${Number(opts.limit) || 50}`, opts);
|
|
728
848
|
writeLine(JSON.stringify(events, null, 2));
|
|
729
849
|
});
|