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 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
- constructor() {
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
- this.register(new BrowserCapability());
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 execSync4 } from "node:child_process";
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 = execSync4(
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 execSync4("git rev-parse HEAD", { cwd: this.cwd, timeout: 1e3, encoding: "utf8" }).trim();
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 Hono11 } from "hono";
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 Hono11();
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "A persistent, learning AI agent that runs on your machine. An agent that learns.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",