@papercraneai/sandbox-agent 0.1.15 → 0.1.17-beta.0
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/index.js +72 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -46,7 +46,9 @@ function parseArgs() {
|
|
|
46
46
|
devPort: parseInt(process.env.DEV_PORT || "3000"),
|
|
47
47
|
workdir: join(homedir(), ".papercrane", "sandbox"),
|
|
48
48
|
agentOnly: false,
|
|
49
|
-
hostMode: (process.env.HOST_MODE || "localhost")
|
|
49
|
+
hostMode: (process.env.HOST_MODE || "localhost"),
|
|
50
|
+
agentEndpoint: null,
|
|
51
|
+
dashboardsEndpoint: null,
|
|
50
52
|
};
|
|
51
53
|
for (let i = 0; i < args.length; i++) {
|
|
52
54
|
const arg = args[i];
|
|
@@ -79,6 +81,17 @@ function parseArgs() {
|
|
|
79
81
|
}
|
|
80
82
|
result.hostMode = mode;
|
|
81
83
|
}
|
|
84
|
+
else if (arg === "--agent-endpoint" && args[i + 1]) {
|
|
85
|
+
// URL papercrane should use to reach this agent. Required for BYOC
|
|
86
|
+
// deployments where localhost / LAN auto-detection isn't reachable
|
|
87
|
+
// by papercrane (e.g., a Tailscale MagicDNS URL).
|
|
88
|
+
result.agentEndpoint = args[++i];
|
|
89
|
+
}
|
|
90
|
+
else if (arg === "--dashboards-endpoint" && args[i + 1]) {
|
|
91
|
+
// URL papercrane (and ultimately the browser iframe) should use to
|
|
92
|
+
// reach this sandbox's dev server. Same rationale as --agent-endpoint.
|
|
93
|
+
result.dashboardsEndpoint = args[++i];
|
|
94
|
+
}
|
|
82
95
|
else if (arg === "--version" || arg === "-v") {
|
|
83
96
|
console.log(pkg.version);
|
|
84
97
|
process.exit(0);
|
|
@@ -95,15 +108,18 @@ Modes:
|
|
|
95
108
|
Agent-only: Just runs the agent API (use with --agent-only)
|
|
96
109
|
|
|
97
110
|
Options:
|
|
98
|
-
--workdir <path>
|
|
99
|
-
--agent-only
|
|
100
|
-
--port <port>
|
|
101
|
-
--dev-port <port>
|
|
102
|
-
--host-mode <mode>
|
|
103
|
-
--register
|
|
104
|
-
--token <token>
|
|
105
|
-
--papercrane-url <url>
|
|
106
|
-
--
|
|
111
|
+
--workdir <path> Project directory (default: ~/.papercrane/sandbox)
|
|
112
|
+
--agent-only Only run agent API (no template setup, no dev server)
|
|
113
|
+
--port <port> Agent API port (default: 3001)
|
|
114
|
+
--dev-port <port> Dev server port, standalone only (default: 3000)
|
|
115
|
+
--host-mode <mode> Host for advertised URLs: localhost or lan (default: localhost)
|
|
116
|
+
--register Register with Papercrane server (requires --token)
|
|
117
|
+
--token <token> Connection token from Papercrane UI
|
|
118
|
+
--papercrane-url <url> Papercrane server URL (default: https://fly.papercrane.ai)
|
|
119
|
+
--agent-endpoint <url> URL Papercrane uses to reach this agent (overrides default
|
|
120
|
+
localhost/LAN auto-detection; required for BYOC deployments)
|
|
121
|
+
--dashboards-endpoint <url> URL Papercrane / iframe uses for the dev server (same purpose)
|
|
122
|
+
--help, -h Show this help message
|
|
107
123
|
|
|
108
124
|
Environment Variables:
|
|
109
125
|
PORT Agent API port (same as --port)
|
|
@@ -115,6 +131,9 @@ Examples:
|
|
|
115
131
|
sandbox-agent # Standalone: full setup
|
|
116
132
|
sandbox-agent --workdir ~/my-project # Standalone: custom directory
|
|
117
133
|
sandbox-agent --agent-only --workdir /tmp/app # Agent-only: just the API
|
|
134
|
+
sandbox-agent --register --token tok_abc \\
|
|
135
|
+
--agent-endpoint http://sandbox-abc.tail-x.ts.net:3001 \\
|
|
136
|
+
--dashboards-endpoint http://sandbox-abc.tail-x.ts.net:3000 # BYOC self-register
|
|
118
137
|
`);
|
|
119
138
|
process.exit(0);
|
|
120
139
|
}
|
|
@@ -380,6 +399,7 @@ const sessionContext = {};
|
|
|
380
399
|
// Registration state
|
|
381
400
|
let environmentId = null;
|
|
382
401
|
let connectionToken = null;
|
|
402
|
+
let registeredPapercraneUrl = null;
|
|
383
403
|
let heartbeatInterval = null;
|
|
384
404
|
// Client-side tool: ShowPreview
|
|
385
405
|
const showPreviewTool = tool("ShowPreview", "Shows the preview iframe for a specific route. Call this after creating or modifying a visualization so the user can see the result immediately. The preview will appear in the user's browser.", {
|
|
@@ -1471,21 +1491,22 @@ app.post("/chat", async (req, res) => {
|
|
|
1471
1491
|
// =============================================================================
|
|
1472
1492
|
// Registration & Heartbeat
|
|
1473
1493
|
// =============================================================================
|
|
1474
|
-
async function registerWithPapercrane() {
|
|
1475
|
-
|
|
1494
|
+
async function registerWithPapercrane(overrides) {
|
|
1495
|
+
const token = overrides?.token ?? cliArgs.token;
|
|
1496
|
+
const papercraneUrl = overrides?.papercraneUrl ?? cliArgs.papercraneUrl;
|
|
1497
|
+
if (!token) {
|
|
1476
1498
|
console.error("Error: --token is required when using --register");
|
|
1477
1499
|
return false;
|
|
1478
1500
|
}
|
|
1479
|
-
console.log(`Registering with Papercrane server at ${
|
|
1501
|
+
console.log(`Registering with Papercrane server at ${papercraneUrl}...`);
|
|
1480
1502
|
try {
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
const
|
|
1484
|
-
const res = await fetch(`${cliArgs.papercraneUrl}/api/environments/register`, {
|
|
1503
|
+
const agentEndpoint = overrides?.agentEndpoint ?? `http://${cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost"}:${cliArgs.agentPort}`;
|
|
1504
|
+
const previewEndpoint = overrides?.previewEndpoint ?? `http://${cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost"}:${cliArgs.devPort}`;
|
|
1505
|
+
const res = await fetch(`${papercraneUrl}/api/environments/register`, {
|
|
1485
1506
|
method: "POST",
|
|
1486
1507
|
headers: { "Content-Type": "application/json" },
|
|
1487
1508
|
body: JSON.stringify({
|
|
1488
|
-
connectionToken:
|
|
1509
|
+
connectionToken: token,
|
|
1489
1510
|
agentEndpoint,
|
|
1490
1511
|
previewEndpoint
|
|
1491
1512
|
})
|
|
@@ -1497,7 +1518,8 @@ async function registerWithPapercrane() {
|
|
|
1497
1518
|
}
|
|
1498
1519
|
const data = await res.json();
|
|
1499
1520
|
environmentId = data.environmentId;
|
|
1500
|
-
connectionToken =
|
|
1521
|
+
connectionToken = token;
|
|
1522
|
+
registeredPapercraneUrl = papercraneUrl;
|
|
1501
1523
|
console.log(`✓ Successfully registered as environment #${environmentId}`);
|
|
1502
1524
|
console.log(` Agent endpoint: ${agentEndpoint}`);
|
|
1503
1525
|
console.log(` Preview endpoint: ${previewEndpoint}`);
|
|
@@ -1513,7 +1535,7 @@ async function sendHeartbeat() {
|
|
|
1513
1535
|
if (!environmentId || !connectionToken)
|
|
1514
1536
|
return;
|
|
1515
1537
|
try {
|
|
1516
|
-
const res = await fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
|
|
1538
|
+
const res = await fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
|
|
1517
1539
|
method: "POST",
|
|
1518
1540
|
headers: {
|
|
1519
1541
|
"Content-Type": "application/json",
|
|
@@ -1562,7 +1584,7 @@ function handleShutdown(exitCode = 0) {
|
|
|
1562
1584
|
// Notify Papercrane of disconnect
|
|
1563
1585
|
if (environmentId && connectionToken) {
|
|
1564
1586
|
console.log("Disconnecting from Papercrane...");
|
|
1565
|
-
fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
|
|
1587
|
+
fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
|
|
1566
1588
|
method: "POST",
|
|
1567
1589
|
headers: {
|
|
1568
1590
|
"Content-Type": "application/json",
|
|
@@ -1613,9 +1635,18 @@ async function start() {
|
|
|
1613
1635
|
log.info({ mode }, "Standalone mode: setting up template and dev server");
|
|
1614
1636
|
PROJECT_DIR = await setupTemplate();
|
|
1615
1637
|
}
|
|
1616
|
-
// Register with Papercrane if requested
|
|
1638
|
+
// Register with Papercrane if requested. CLI overrides for the agent /
|
|
1639
|
+
// dashboards URLs are required for BYOC deployments where the default
|
|
1640
|
+
// localhost/LAN fallback isn't reachable from papercrane (e.g., the
|
|
1641
|
+
// agent's only ingress is a Tailscale MagicDNS hostname).
|
|
1617
1642
|
if (cliArgs.register) {
|
|
1618
|
-
const
|
|
1643
|
+
const overrides = cliArgs.agentEndpoint && cliArgs.dashboardsEndpoint
|
|
1644
|
+
? {
|
|
1645
|
+
agentEndpoint: cliArgs.agentEndpoint,
|
|
1646
|
+
previewEndpoint: cliArgs.dashboardsEndpoint,
|
|
1647
|
+
}
|
|
1648
|
+
: undefined;
|
|
1649
|
+
const success = await registerWithPapercrane(overrides);
|
|
1619
1650
|
if (!success) {
|
|
1620
1651
|
process.exit(1);
|
|
1621
1652
|
}
|
|
@@ -1691,6 +1722,24 @@ async function start() {
|
|
|
1691
1722
|
console.log(` Connected to Papercrane at ${cliArgs.papercraneUrl}`);
|
|
1692
1723
|
}
|
|
1693
1724
|
console.log(`\nReady to accept connections.`);
|
|
1725
|
+
// Auto-register when running in k8s/BYOC — env vars set by KubernetesSandboxProvider
|
|
1726
|
+
if (process.env.CONNECTION_TOKEN && process.env.PAPERCRANE_URL) {
|
|
1727
|
+
// POD_IP is injected via the Kubernetes Downward API — use it so the control plane
|
|
1728
|
+
// gets a real routable IP (reachable via Tailscale subnet routing) rather than
|
|
1729
|
+
// a cluster-internal DNS name that's only resolvable inside the cluster.
|
|
1730
|
+
const podIp = process.env.POD_IP;
|
|
1731
|
+
const agentEndpoint = podIp ? `http://${podIp}:${PORT}` : (process.env.AGENT_ENDPOINT ?? `http://localhost:${PORT}`);
|
|
1732
|
+
const previewEndpoint = podIp ? `http://${podIp}:3000` : agentEndpoint.replace(`:${PORT}`, ":3000");
|
|
1733
|
+
registerWithPapercrane({
|
|
1734
|
+
token: process.env.CONNECTION_TOKEN,
|
|
1735
|
+
papercraneUrl: process.env.PAPERCRANE_URL,
|
|
1736
|
+
agentEndpoint,
|
|
1737
|
+
previewEndpoint,
|
|
1738
|
+
}).then((ok) => {
|
|
1739
|
+
if (!ok)
|
|
1740
|
+
console.error("Auto-registration failed — sandbox will not be reachable by control plane");
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1694
1743
|
});
|
|
1695
1744
|
}
|
|
1696
1745
|
catch (error) {
|