0agent 1.0.27 → 1.0.28
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/bin/0agent.js +56 -0
- package/dist/daemon.mjs +394 -8
- package/package.json +1 -1
package/bin/0agent.js
CHANGED
|
@@ -126,6 +126,10 @@ switch (cmd) {
|
|
|
126
126
|
await runMemoryCommand(args.slice(1));
|
|
127
127
|
break;
|
|
128
128
|
|
|
129
|
+
case 'codespace':
|
|
130
|
+
await runCodespaceCommand(args.slice(1));
|
|
131
|
+
break;
|
|
132
|
+
|
|
129
133
|
default:
|
|
130
134
|
showHelp();
|
|
131
135
|
break;
|
|
@@ -1095,6 +1099,58 @@ async function daemonMemorySync(direction) {
|
|
|
1095
1099
|
} catch { return null; }
|
|
1096
1100
|
}
|
|
1097
1101
|
|
|
1102
|
+
// ─── Codespace commands ───────────────────────────────────────────────────────
|
|
1103
|
+
|
|
1104
|
+
async function runCodespaceCommand(csArgs) {
|
|
1105
|
+
const sub = csArgs[0] ?? 'status';
|
|
1106
|
+
|
|
1107
|
+
switch (sub) {
|
|
1108
|
+
case 'setup': {
|
|
1109
|
+
console.log('\n Setting up GitHub Codespace browser backend...\n');
|
|
1110
|
+
const result = await fetch(`${BASE_URL}/api/codespace/setup`, { method: 'POST' }).catch(() => null);
|
|
1111
|
+
const data = result?.ok ? await result.json().catch(() => null) : null;
|
|
1112
|
+
if (data?.started) {
|
|
1113
|
+
console.log(` \x1b[32m✓\x1b[0m Codespace provisioning started`);
|
|
1114
|
+
console.log(` \x1b[2mFirst time: ~2-3 min. Check with: 0agent codespace status\x1b[0m\n`);
|
|
1115
|
+
} else {
|
|
1116
|
+
console.log(` \x1b[33m⚠\x1b[0m ${data?.error ?? 'Configure GitHub memory first: 0agent memory connect github'}\n`);
|
|
1117
|
+
}
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
case 'status': {
|
|
1121
|
+
const result = await fetch(`${BASE_URL}/api/codespace/status`).catch(() => null);
|
|
1122
|
+
const data = result?.ok ? await result.json().catch(() => null) : null;
|
|
1123
|
+
if (data) {
|
|
1124
|
+
const state = data.state ?? 'unknown';
|
|
1125
|
+
const icon = state === 'Available' ? '\x1b[32m✓\x1b[0m' : state === 'Shutdown' ? '\x1b[33m●\x1b[0m' : '\x1b[2m○\x1b[0m';
|
|
1126
|
+
console.log(`\n Browser backend: ${icon} ${state}`);
|
|
1127
|
+
if (data.ready) console.log(` Tunnel: \x1b[32m✓ open\x1b[0m → http://localhost:3001`);
|
|
1128
|
+
if (data.name) console.log(` Codespace: ${data.name}`);
|
|
1129
|
+
console.log(` Cost: ~60 hours/month free on GitHub personal\n`);
|
|
1130
|
+
} else {
|
|
1131
|
+
console.log('\n Codespace not configured. Run: 0agent codespace setup\n');
|
|
1132
|
+
}
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
case 'start': {
|
|
1136
|
+
process.stdout.write(' Starting codespace...');
|
|
1137
|
+
const result = await fetch(`${BASE_URL}/api/codespace/start`, { method: 'POST' }).catch(() => null);
|
|
1138
|
+
const data = result?.ok ? await result.json().catch(() => null) : null;
|
|
1139
|
+
console.log(data?.ok ? ' \x1b[32m✓\x1b[0m' : ` \x1b[31m✗\x1b[0m ${data?.error ?? 'failed'}`);
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case 'stop': {
|
|
1143
|
+
process.stdout.write(' Stopping codespace (preserves state)...');
|
|
1144
|
+
const result = await fetch(`${BASE_URL}/api/codespace/stop`, { method: 'POST' }).catch(() => null);
|
|
1145
|
+
const data = result?.ok ? await result.json().catch(() => null) : null;
|
|
1146
|
+
console.log(data?.ok ? ' \x1b[32m✓\x1b[0m Stopped. Hours saved.' : ` \x1b[31m✗\x1b[0m`);
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
default:
|
|
1150
|
+
console.log(' Usage: 0agent codespace setup | status | start | stop');
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1098
1154
|
// ─── Result preview — confirms the agent's work actually ran ────────────────
|
|
1099
1155
|
|
|
1100
1156
|
async function showResultPreview(result) {
|
package/dist/daemon.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
3
5
|
var __esm = (fn, res) => function __init() {
|
|
4
6
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
7
|
};
|
|
@@ -7,6 +9,15 @@ var __export = (target, all) => {
|
|
|
7
9
|
for (var name in all)
|
|
8
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
11
|
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
10
21
|
|
|
11
22
|
// packages/core/src/graph/GraphNode.ts
|
|
12
23
|
function createNode(params) {
|
|
@@ -2418,6 +2429,80 @@ var init_FileCapability = __esm({
|
|
|
2418
2429
|
}
|
|
2419
2430
|
});
|
|
2420
2431
|
|
|
2432
|
+
// packages/daemon/src/capabilities/CodespaceBrowserCapability.ts
|
|
2433
|
+
var CodespaceBrowserCapability_exports = {};
|
|
2434
|
+
__export(CodespaceBrowserCapability_exports, {
|
|
2435
|
+
CodespaceBrowserCapability: () => CodespaceBrowserCapability
|
|
2436
|
+
});
|
|
2437
|
+
var CodespaceBrowserCapability;
|
|
2438
|
+
var init_CodespaceBrowserCapability = __esm({
|
|
2439
|
+
"packages/daemon/src/capabilities/CodespaceBrowserCapability.ts"() {
|
|
2440
|
+
"use strict";
|
|
2441
|
+
init_BrowserCapability();
|
|
2442
|
+
CodespaceBrowserCapability = class {
|
|
2443
|
+
constructor(manager) {
|
|
2444
|
+
this.manager = manager;
|
|
2445
|
+
}
|
|
2446
|
+
name = "browser_open";
|
|
2447
|
+
description = "Open a URL in a cloud Linux browser (GitHub Codespace). Full JS, screenshots, clicks. Falls back to local Chrome.";
|
|
2448
|
+
fallback = new BrowserCapability();
|
|
2449
|
+
provisioningPromise = null;
|
|
2450
|
+
toolDefinition = {
|
|
2451
|
+
name: "browser_open",
|
|
2452
|
+
description: "Open URL in cloud Linux browser (GitHub Codespace). Handles JS-heavy SPAs, auth flows, screenshots. Fallback: local Chrome.",
|
|
2453
|
+
input_schema: {
|
|
2454
|
+
type: "object",
|
|
2455
|
+
properties: {
|
|
2456
|
+
url: { type: "string", description: "URL to open" },
|
|
2457
|
+
action: { type: "string", description: '"read" (default) | "screenshot" | "links" | "click" | "fill" | "snapshot"' },
|
|
2458
|
+
selector: { type: "string", description: "CSS selector for element to extract or interact with" },
|
|
2459
|
+
wait_for: { type: "string", description: "CSS selector to wait for before extracting" },
|
|
2460
|
+
wait_ms: { type: "number", description: "Additional wait after page load (for JS-heavy pages)" },
|
|
2461
|
+
value: { type: "string", description: "Value to fill (for action: fill)" }
|
|
2462
|
+
},
|
|
2463
|
+
required: ["url"]
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
async execute(input, cwd) {
|
|
2467
|
+
const start = Date.now();
|
|
2468
|
+
try {
|
|
2469
|
+
if (!this.manager.isReady()) {
|
|
2470
|
+
if (!this.provisioningPromise) {
|
|
2471
|
+
this.provisioningPromise = this.manager.getReadyUrl().finally(() => {
|
|
2472
|
+
this.provisioningPromise = null;
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
await this.provisioningPromise;
|
|
2476
|
+
}
|
|
2477
|
+
const res = await fetch(`${this.manager.localUrl}/browse`, {
|
|
2478
|
+
method: "POST",
|
|
2479
|
+
headers: { "Content-Type": "application/json" },
|
|
2480
|
+
body: JSON.stringify(input),
|
|
2481
|
+
signal: AbortSignal.timeout(6e4)
|
|
2482
|
+
});
|
|
2483
|
+
const data = await res.json();
|
|
2484
|
+
if (!data.ok) throw new Error(String(data.error ?? "Browse failed"));
|
|
2485
|
+
return {
|
|
2486
|
+
success: true,
|
|
2487
|
+
output: String(data.data ?? ""),
|
|
2488
|
+
structured: data,
|
|
2489
|
+
duration_ms: Date.now() - start
|
|
2490
|
+
};
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2493
|
+
console.warn("[CodespaceBrowser] Falling back to local Chrome:", errMsg);
|
|
2494
|
+
const result = await this.fallback.execute(input, cwd);
|
|
2495
|
+
return {
|
|
2496
|
+
...result,
|
|
2497
|
+
fallback_used: "local-chrome",
|
|
2498
|
+
duration_ms: Date.now() - start
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2421
2506
|
// packages/daemon/src/capabilities/CapabilityRegistry.ts
|
|
2422
2507
|
var CapabilityRegistry;
|
|
2423
2508
|
var init_CapabilityRegistry = __esm({
|
|
@@ -2430,9 +2515,28 @@ var init_CapabilityRegistry = __esm({
|
|
|
2430
2515
|
init_FileCapability();
|
|
2431
2516
|
CapabilityRegistry = class {
|
|
2432
2517
|
capabilities = /* @__PURE__ */ new Map();
|
|
2433
|
-
|
|
2518
|
+
/**
|
|
2519
|
+
* Constructor optionally accepts a CodespaceManager.
|
|
2520
|
+
* If provided and the gh CLI is available, uses CodespaceBrowserCapability
|
|
2521
|
+
* for browser_open — cloud Linux browser via SSH tunnel.
|
|
2522
|
+
*
|
|
2523
|
+
* SECURITY: The registry is only instantiated inside AgentExecutor,
|
|
2524
|
+
* which is only created for AUTHORISED subagents (trust_level: 1,
|
|
2525
|
+
* task_type: browser_task). The main agent does NOT have direct access
|
|
2526
|
+
* to browser_open without going through a subagent spawn.
|
|
2527
|
+
*/
|
|
2528
|
+
constructor(codespaceManager) {
|
|
2434
2529
|
this.register(new WebSearchCapability());
|
|
2435
|
-
|
|
2530
|
+
if (codespaceManager) {
|
|
2531
|
+
try {
|
|
2532
|
+
const { CodespaceBrowserCapability: CodespaceBrowserCapability2 } = (init_CodespaceBrowserCapability(), __toCommonJS(CodespaceBrowserCapability_exports));
|
|
2533
|
+
this.register(new CodespaceBrowserCapability2(codespaceManager));
|
|
2534
|
+
} catch {
|
|
2535
|
+
this.register(new BrowserCapability());
|
|
2536
|
+
}
|
|
2537
|
+
} else {
|
|
2538
|
+
this.register(new BrowserCapability());
|
|
2539
|
+
}
|
|
2436
2540
|
this.register(new ScraperCapability());
|
|
2437
2541
|
this.register(new ShellCapability());
|
|
2438
2542
|
this.register(new FileCapability());
|
|
@@ -2932,7 +3036,7 @@ var ProactiveSurface_exports = {};
|
|
|
2932
3036
|
__export(ProactiveSurface_exports, {
|
|
2933
3037
|
ProactiveSurface: () => ProactiveSurface
|
|
2934
3038
|
});
|
|
2935
|
-
import { execSync as
|
|
3039
|
+
import { execSync as execSync5 } from "node:child_process";
|
|
2936
3040
|
import { existsSync as existsSync12, readFileSync as readFileSync12, statSync, readdirSync as readdirSync5 } from "node:fs";
|
|
2937
3041
|
import { resolve as resolve11, join as join3 } from "node:path";
|
|
2938
3042
|
function readdirSafe(dir) {
|
|
@@ -3001,7 +3105,7 @@ var init_ProactiveSurface = __esm({
|
|
|
3001
3105
|
try {
|
|
3002
3106
|
const currentHead = this.getGitHead();
|
|
3003
3107
|
if (!currentHead || currentHead === this.lastKnownHead) return null;
|
|
3004
|
-
const log =
|
|
3108
|
+
const log = execSync5(
|
|
3005
3109
|
`git log ${this.lastKnownHead}..${currentHead} --oneline --stat`,
|
|
3006
3110
|
{ cwd: this.cwd, timeout: 3e3, encoding: "utf8" }
|
|
3007
3111
|
).trim();
|
|
@@ -3077,7 +3181,7 @@ var init_ProactiveSurface = __esm({
|
|
|
3077
3181
|
}
|
|
3078
3182
|
getGitHead() {
|
|
3079
3183
|
try {
|
|
3080
|
-
return
|
|
3184
|
+
return execSync5("git rev-parse HEAD", { cwd: this.cwd, timeout: 1e3, encoding: "utf8" }).trim();
|
|
3081
3185
|
} catch {
|
|
3082
3186
|
return "";
|
|
3083
3187
|
}
|
|
@@ -4644,7 +4748,7 @@ var SkillRegistry = class {
|
|
|
4644
4748
|
};
|
|
4645
4749
|
|
|
4646
4750
|
// packages/daemon/src/HTTPServer.ts
|
|
4647
|
-
import { Hono as
|
|
4751
|
+
import { Hono as Hono12 } from "hono";
|
|
4648
4752
|
import { serve } from "@hono/node-server";
|
|
4649
4753
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4650
4754
|
import { resolve as resolve7, dirname as dirname3 } from "node:path";
|
|
@@ -4989,6 +5093,50 @@ function llmRoutes() {
|
|
|
4989
5093
|
return app;
|
|
4990
5094
|
}
|
|
4991
5095
|
|
|
5096
|
+
// packages/daemon/src/routes/codespace.ts
|
|
5097
|
+
import { Hono as Hono11 } from "hono";
|
|
5098
|
+
function codespaceRoutes(deps) {
|
|
5099
|
+
const app = new Hono11();
|
|
5100
|
+
app.get("/status", async (c) => {
|
|
5101
|
+
const mgr = deps.getManager();
|
|
5102
|
+
if (!mgr) return c.json({ configured: false, state: "not_configured" });
|
|
5103
|
+
const info = mgr.findExisting();
|
|
5104
|
+
const ping = mgr.isReady() ? await mgr.ping().catch(() => null) : null;
|
|
5105
|
+
return c.json({
|
|
5106
|
+
configured: true,
|
|
5107
|
+
state: info?.state ?? "not_found",
|
|
5108
|
+
name: info?.name ?? null,
|
|
5109
|
+
ready: mgr.isReady(),
|
|
5110
|
+
browser_ok: ping?.ok ?? false
|
|
5111
|
+
});
|
|
5112
|
+
});
|
|
5113
|
+
app.post("/setup", async (c) => {
|
|
5114
|
+
const result = await deps.setup();
|
|
5115
|
+
return c.json(result);
|
|
5116
|
+
});
|
|
5117
|
+
app.post("/start", async (c) => {
|
|
5118
|
+
const mgr = deps.getManager();
|
|
5119
|
+
if (!mgr) return c.json({ ok: false, error: "Not configured" }, 404);
|
|
5120
|
+
try {
|
|
5121
|
+
const url = await mgr.getReadyUrl();
|
|
5122
|
+
return c.json({ ok: true, url });
|
|
5123
|
+
} catch (err) {
|
|
5124
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
5125
|
+
}
|
|
5126
|
+
});
|
|
5127
|
+
app.post("/stop", async (c) => {
|
|
5128
|
+
const mgr = deps.getManager();
|
|
5129
|
+
if (!mgr) return c.json({ ok: false, error: "Not configured" }, 404);
|
|
5130
|
+
try {
|
|
5131
|
+
await mgr.stop();
|
|
5132
|
+
return c.json({ ok: true });
|
|
5133
|
+
} catch (err) {
|
|
5134
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
5135
|
+
}
|
|
5136
|
+
});
|
|
5137
|
+
return app;
|
|
5138
|
+
}
|
|
5139
|
+
|
|
4992
5140
|
// packages/daemon/src/HTTPServer.ts
|
|
4993
5141
|
function findGraphHtml() {
|
|
4994
5142
|
const candidates = [
|
|
@@ -5014,7 +5162,7 @@ var HTTPServer = class {
|
|
|
5014
5162
|
deps;
|
|
5015
5163
|
constructor(deps) {
|
|
5016
5164
|
this.deps = deps;
|
|
5017
|
-
this.app = new
|
|
5165
|
+
this.app = new Hono12();
|
|
5018
5166
|
this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
|
|
5019
5167
|
this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
|
|
5020
5168
|
this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
|
|
@@ -5025,6 +5173,10 @@ var HTTPServer = class {
|
|
|
5025
5173
|
this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
|
|
5026
5174
|
this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
|
|
5027
5175
|
this.app.route("/api/llm", llmRoutes());
|
|
5176
|
+
this.app.route("/api/codespace", codespaceRoutes({
|
|
5177
|
+
getManager: deps.getCodespaceManager ?? (() => null),
|
|
5178
|
+
setup: deps.setupCodespace ?? (async () => ({ started: false, error: "Not configured" }))
|
|
5179
|
+
}));
|
|
5028
5180
|
const serveGraph = (c) => {
|
|
5029
5181
|
try {
|
|
5030
5182
|
const html = readFileSync8(GRAPH_HTML_PATH, "utf8");
|
|
@@ -5428,6 +5580,7 @@ var GitHubMemorySync = class {
|
|
|
5428
5580
|
}
|
|
5429
5581
|
const readme = this.generateReadme(nodes.length, edges.length);
|
|
5430
5582
|
pushes.push(putFile(token, owner, repo, "README.md", readme, commitMsg));
|
|
5583
|
+
await this.ensureCodespaceFiles(token, owner, repo, commitMsg);
|
|
5431
5584
|
await Promise.all(pushes);
|
|
5432
5585
|
this.lastPushAt = now;
|
|
5433
5586
|
this.pendingChanges = false;
|
|
@@ -5623,6 +5776,36 @@ var GitHubMemorySync = class {
|
|
|
5623
5776
|
} catch {
|
|
5624
5777
|
}
|
|
5625
5778
|
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Push the codespace browser server files to the memory repo.
|
|
5781
|
+
* This makes the memory repo a valid Codespace template — one repo for everything.
|
|
5782
|
+
* Only pushes if the files don't exist yet (idempotent).
|
|
5783
|
+
*/
|
|
5784
|
+
async ensureCodespaceFiles(token, owner, repo, msg) {
|
|
5785
|
+
const existing = await getFileSha(token, owner, repo, ".devcontainer/devcontainer.json");
|
|
5786
|
+
if (existing) return;
|
|
5787
|
+
const devcontainer = JSON.stringify({
|
|
5788
|
+
name: "0agent Browser Sandbox",
|
|
5789
|
+
image: "mcr.microsoft.com/devcontainers/javascript-node:22",
|
|
5790
|
+
postCreateCommand: "cd /workspaces && npm install && npx playwright install chromium --with-deps && pip3 install scrapling --quiet 2>/dev/null || true",
|
|
5791
|
+
postStartCommand: 'cd /workspaces && pkill -f "node server.js" 2>/dev/null; nohup node server.js > /tmp/browser-server.log 2>&1 &',
|
|
5792
|
+
forwardPorts: [3e3],
|
|
5793
|
+
portsAttributes: {
|
|
5794
|
+
"3000": { label: "0agent Browser", onAutoForward: "silent", visibility: "private" }
|
|
5795
|
+
}
|
|
5796
|
+
}, null, 2);
|
|
5797
|
+
const packageJson = JSON.stringify({
|
|
5798
|
+
name: "0agent-browser-server",
|
|
5799
|
+
version: "1.0.0",
|
|
5800
|
+
dependencies: { playwright: "^1.42.0" }
|
|
5801
|
+
}, null, 2);
|
|
5802
|
+
await Promise.all([
|
|
5803
|
+
putFile(token, owner, repo, ".devcontainer/devcontainer.json", devcontainer, msg),
|
|
5804
|
+
putFile(token, owner, repo, "package.json", packageJson, msg),
|
|
5805
|
+
// Note: server.js is too large to inline here — users pull it from the npm package at codespace start
|
|
5806
|
+
putFile(token, owner, repo, ".gitignore", "node_modules/\n*.log\n", msg)
|
|
5807
|
+
]);
|
|
5808
|
+
}
|
|
5626
5809
|
generateReadme(nodeCount, edgeCount) {
|
|
5627
5810
|
return `# 0agent Memory
|
|
5628
5811
|
|
|
@@ -5663,6 +5846,189 @@ git checkout <commit> graph/ # restore graph files
|
|
|
5663
5846
|
}
|
|
5664
5847
|
};
|
|
5665
5848
|
|
|
5849
|
+
// packages/daemon/src/CodespaceManager.ts
|
|
5850
|
+
import { execSync as execSync4, spawn as spawn3 } from "node:child_process";
|
|
5851
|
+
var BROWSER_PORT_REMOTE = 3e3;
|
|
5852
|
+
var BROWSER_PORT_LOCAL = 3001;
|
|
5853
|
+
var DISPLAY_NAME = "0agent-browser";
|
|
5854
|
+
var FORWARD_TIMEOUT_S = 60;
|
|
5855
|
+
var CodespaceManager = class {
|
|
5856
|
+
forwardProcess = null;
|
|
5857
|
+
_ready = false;
|
|
5858
|
+
_localUrl = `http://localhost:${BROWSER_PORT_LOCAL}`;
|
|
5859
|
+
memoryRepo;
|
|
5860
|
+
// e.g. "cadetmaze/0agent-memory"
|
|
5861
|
+
constructor(memoryRepo) {
|
|
5862
|
+
this.memoryRepo = memoryRepo;
|
|
5863
|
+
}
|
|
5864
|
+
/** Is the tunnel open and browser server responding? */
|
|
5865
|
+
isReady() {
|
|
5866
|
+
return this._ready;
|
|
5867
|
+
}
|
|
5868
|
+
/** URL to call the browser server (via SSH tunnel). */
|
|
5869
|
+
get localUrl() {
|
|
5870
|
+
return this._localUrl;
|
|
5871
|
+
}
|
|
5872
|
+
// ─── Main entry point ──────────────────────────────────────────────────────
|
|
5873
|
+
/**
|
|
5874
|
+
* Ensure the codespace is running, browser server is started, and tunnel is open.
|
|
5875
|
+
* Returns the local URL to call (http://localhost:3001).
|
|
5876
|
+
* First call: 2-3 minutes (cold provision). Subsequent: 30s or instant.
|
|
5877
|
+
*/
|
|
5878
|
+
async getReadyUrl() {
|
|
5879
|
+
const name = await this.ensureRunning();
|
|
5880
|
+
await this.startBrowserServer(name);
|
|
5881
|
+
await this.openTunnel(name);
|
|
5882
|
+
return this._localUrl;
|
|
5883
|
+
}
|
|
5884
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
5885
|
+
/** Find existing 0agent-browser codespace, or create one from the memory repo. */
|
|
5886
|
+
async getOrCreate() {
|
|
5887
|
+
const existing = this.findExisting();
|
|
5888
|
+
if (existing) return existing.name;
|
|
5889
|
+
console.log(`[Codespace] Creating browser codespace from ${this.memoryRepo}...`);
|
|
5890
|
+
console.log("[Codespace] First time: ~2-3 minutes. Subsequent starts: ~30 seconds.");
|
|
5891
|
+
try {
|
|
5892
|
+
const result = execSync4(
|
|
5893
|
+
`gh codespace create --repo "${this.memoryRepo}" --machine basicLinux32gb --display-name "${DISPLAY_NAME}" --json name`,
|
|
5894
|
+
{ encoding: "utf8", timeout: 3e5 }
|
|
5895
|
+
);
|
|
5896
|
+
const parsed = JSON.parse(result.trim());
|
|
5897
|
+
console.log(`[Codespace] Created: ${parsed.name}`);
|
|
5898
|
+
return parsed.name;
|
|
5899
|
+
} catch (err) {
|
|
5900
|
+
throw new Error(`Failed to create codespace: ${err instanceof Error ? err.message : String(err)}`);
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
/** Find the 0agent-browser codespace by display name. */
|
|
5904
|
+
findExisting() {
|
|
5905
|
+
try {
|
|
5906
|
+
const out = execSync4("gh codespace list --json name,state,displayName,repository", {
|
|
5907
|
+
encoding: "utf8",
|
|
5908
|
+
timeout: 1e4
|
|
5909
|
+
});
|
|
5910
|
+
const list = JSON.parse(out.trim());
|
|
5911
|
+
return list.find((c) => c.displayName === DISPLAY_NAME) ?? null;
|
|
5912
|
+
} catch {
|
|
5913
|
+
return null;
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
/** Ensure the codespace is in Available state. */
|
|
5917
|
+
async ensureRunning() {
|
|
5918
|
+
const name = await this.getOrCreate();
|
|
5919
|
+
const info = this.findExisting();
|
|
5920
|
+
if (info?.state === "Shutdown") {
|
|
5921
|
+
console.log("[Codespace] Starting stopped codespace (~30s)...");
|
|
5922
|
+
execSync4(`gh codespace start --codespace "${name}"`, { timeout: 12e4 });
|
|
5923
|
+
await this.waitForState(name, "Available", 60);
|
|
5924
|
+
console.log("[Codespace] Codespace is running");
|
|
5925
|
+
} else if (info?.state === "Starting") {
|
|
5926
|
+
console.log("[Codespace] Codespace is starting...");
|
|
5927
|
+
await this.waitForState(name, "Available", 120);
|
|
5928
|
+
}
|
|
5929
|
+
return name;
|
|
5930
|
+
}
|
|
5931
|
+
/** Start the browser server inside the codespace (idempotent). */
|
|
5932
|
+
async startBrowserServer(name) {
|
|
5933
|
+
try {
|
|
5934
|
+
execSync4(
|
|
5935
|
+
`gh codespace exec --codespace "${name}" -- bash -c "pgrep -f 'node server.js' > /dev/null 2>&1 || (cd /workspaces && nohup node server.js > /tmp/browser-server.log 2>&1 &)"`,
|
|
5936
|
+
{ timeout: 3e4 }
|
|
5937
|
+
);
|
|
5938
|
+
} catch {
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
/** Open an SSH tunnel via gh CLI: codespace:3000 → localhost:3001. */
|
|
5942
|
+
async openTunnel(name) {
|
|
5943
|
+
this.closeTunnel();
|
|
5944
|
+
console.log(`[Codespace] Opening tunnel port ${BROWSER_PORT_REMOTE} \u2192 localhost:${BROWSER_PORT_LOCAL}...`);
|
|
5945
|
+
this.forwardProcess = spawn3(
|
|
5946
|
+
"gh",
|
|
5947
|
+
["codespace", "ports", "forward", `${BROWSER_PORT_REMOTE}:${BROWSER_PORT_LOCAL}`, "--codespace", name],
|
|
5948
|
+
{ stdio: ["ignore", "ignore", "ignore"] }
|
|
5949
|
+
);
|
|
5950
|
+
this.forwardProcess.unref();
|
|
5951
|
+
this.forwardProcess.on("close", (code) => {
|
|
5952
|
+
if (this._ready) {
|
|
5953
|
+
console.log("[Codespace] Tunnel closed \u2014 reconnecting...");
|
|
5954
|
+
this._ready = false;
|
|
5955
|
+
this.openTunnel(name).catch(() => {
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
});
|
|
5959
|
+
const deadline = Date.now() + FORWARD_TIMEOUT_S * 1e3;
|
|
5960
|
+
while (Date.now() < deadline) {
|
|
5961
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
5962
|
+
try {
|
|
5963
|
+
const res = await fetch(`${this._localUrl}/health`, { signal: AbortSignal.timeout(2e3) });
|
|
5964
|
+
if (res.ok) {
|
|
5965
|
+
this._ready = true;
|
|
5966
|
+
console.log("[Codespace] Browser server ready at " + this._localUrl);
|
|
5967
|
+
return;
|
|
5968
|
+
}
|
|
5969
|
+
} catch {
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
throw new Error(`Browser server did not respond within ${FORWARD_TIMEOUT_S}s`);
|
|
5973
|
+
}
|
|
5974
|
+
closeTunnel() {
|
|
5975
|
+
if (this.forwardProcess) {
|
|
5976
|
+
try {
|
|
5977
|
+
this.forwardProcess.kill("SIGTERM");
|
|
5978
|
+
} catch {
|
|
5979
|
+
}
|
|
5980
|
+
this.forwardProcess = null;
|
|
5981
|
+
}
|
|
5982
|
+
this._ready = false;
|
|
5983
|
+
}
|
|
5984
|
+
/** Stop the codespace to save free-tier hours. State is preserved. */
|
|
5985
|
+
async stop() {
|
|
5986
|
+
this.closeTunnel();
|
|
5987
|
+
const info = this.findExisting();
|
|
5988
|
+
if (info?.state === "Available") {
|
|
5989
|
+
execSync4(`gh codespace stop --codespace "${info.name}"`, { timeout: 3e4 });
|
|
5990
|
+
console.log("[Codespace] Stopped (state preserved, restarts in 30s when needed)");
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
/** Delete the codespace entirely. */
|
|
5994
|
+
async delete() {
|
|
5995
|
+
this.closeTunnel();
|
|
5996
|
+
const info = this.findExisting();
|
|
5997
|
+
if (info) {
|
|
5998
|
+
execSync4(`gh codespace delete --codespace "${info.name}" --force`, { timeout: 3e4 });
|
|
5999
|
+
console.log("[Codespace] Deleted");
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
// ─── Health checking ───────────────────────────────────────────────────────
|
|
6003
|
+
/** Ping the browser server. Returns null if not reachable. */
|
|
6004
|
+
async ping() {
|
|
6005
|
+
try {
|
|
6006
|
+
const res = await fetch(`${this._localUrl}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
6007
|
+
return res.ok ? await res.json() : null;
|
|
6008
|
+
} catch {
|
|
6009
|
+
return null;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
6013
|
+
async waitForState(name, target, maxSeconds) {
|
|
6014
|
+
for (let i = 0; i < maxSeconds / 2; i++) {
|
|
6015
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
6016
|
+
const info = this.findExisting();
|
|
6017
|
+
if (info?.state === target) return;
|
|
6018
|
+
}
|
|
6019
|
+
throw new Error(`Codespace did not reach ${target} state within ${maxSeconds}s`);
|
|
6020
|
+
}
|
|
6021
|
+
/** Check if gh CLI is installed and authenticated. */
|
|
6022
|
+
static isAvailable() {
|
|
6023
|
+
try {
|
|
6024
|
+
execSync4("gh auth status", { stdio: "ignore", timeout: 5e3 });
|
|
6025
|
+
return true;
|
|
6026
|
+
} catch {
|
|
6027
|
+
return false;
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
};
|
|
6031
|
+
|
|
5666
6032
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
5667
6033
|
var ZeroAgentDaemon = class {
|
|
5668
6034
|
config = null;
|
|
@@ -5678,6 +6044,7 @@ var ZeroAgentDaemon = class {
|
|
|
5678
6044
|
githubMemorySync = null;
|
|
5679
6045
|
memorySyncTimer = null;
|
|
5680
6046
|
proactiveSurfaceInstance = null;
|
|
6047
|
+
codespaceManager = null;
|
|
5681
6048
|
startedAt = 0;
|
|
5682
6049
|
pidFilePath;
|
|
5683
6050
|
constructor() {
|
|
@@ -5718,6 +6085,13 @@ var ZeroAgentDaemon = class {
|
|
|
5718
6085
|
this.graph
|
|
5719
6086
|
);
|
|
5720
6087
|
console.log(`[0agent] Memory sync: github.com/${ghMemCfg.owner}/${ghMemCfg.repo}`);
|
|
6088
|
+
if (CodespaceManager.isAvailable()) {
|
|
6089
|
+
const memRepo = `${ghMemCfg.owner}/${ghMemCfg.repo}`;
|
|
6090
|
+
this.codespaceManager = new CodespaceManager(memRepo);
|
|
6091
|
+
this.codespaceManager.getReadyUrl().catch(() => {
|
|
6092
|
+
});
|
|
6093
|
+
console.log(`[0agent] Browser backend: github.com codespace (from ${memRepo})`);
|
|
6094
|
+
}
|
|
5721
6095
|
this.githubMemorySync.pull().then((r) => {
|
|
5722
6096
|
if (r.pulled) console.log(`[0agent] Memory pulled: +${r.nodes_synced} nodes, +${r.edges_synced} edges`);
|
|
5723
6097
|
}).catch(() => {
|
|
@@ -5792,7 +6166,17 @@ var ZeroAgentDaemon = class {
|
|
|
5792
6166
|
skillRegistry: this.skillRegistry,
|
|
5793
6167
|
getStatus: () => this.getStatus(),
|
|
5794
6168
|
getMemorySync: () => memSyncRef,
|
|
5795
|
-
proactiveSurface
|
|
6169
|
+
proactiveSurface,
|
|
6170
|
+
getCodespaceManager: () => this.codespaceManager,
|
|
6171
|
+
setupCodespace: async () => {
|
|
6172
|
+
if (!this.codespaceManager) return { started: false, error: "GitHub memory not configured. Run: 0agent memory connect github" };
|
|
6173
|
+
try {
|
|
6174
|
+
this.codespaceManager.getReadyUrl().catch(console.error);
|
|
6175
|
+
return { started: true };
|
|
6176
|
+
} catch (err) {
|
|
6177
|
+
return { started: false, error: err instanceof Error ? err.message : String(err) };
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
5796
6180
|
});
|
|
5797
6181
|
await this.httpServer.start();
|
|
5798
6182
|
writeFileSync7(this.pidFilePath, String(process.pid), "utf8");
|
|
@@ -5829,6 +6213,8 @@ var ZeroAgentDaemon = class {
|
|
|
5829
6213
|
this.memorySyncTimer = null;
|
|
5830
6214
|
}
|
|
5831
6215
|
this.githubMemorySync = null;
|
|
6216
|
+
this.codespaceManager?.closeTunnel();
|
|
6217
|
+
this.codespaceManager = null;
|
|
5832
6218
|
this.sessionManager = null;
|
|
5833
6219
|
this.skillRegistry = null;
|
|
5834
6220
|
this.inferenceEngine = null;
|