@fancyboi999/open-tag-daemon 0.5.1 → 0.6.1

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.
@@ -3079,8 +3079,9 @@ function createLogger(component) {
3079
3079
  }
3080
3080
  const extra = fields && Object.keys(fields).length ? " " + safeJson(fields) : "";
3081
3081
  const line = `${rec.t} ${level.toUpperCase().padEnd(5)} [${component}] ${msg}${extra}`;
3082
+ const stream = consoleStream(component, level);
3082
3083
  try {
3083
- process.stderr.write(line + "\n");
3084
+ stream.write(line + "\n");
3084
3085
  } catch {
3085
3086
  }
3086
3087
  }
@@ -3092,6 +3093,10 @@ function createLogger(component) {
3092
3093
  child: (sub) => createLogger(`${component}:${sub}`)
3093
3094
  };
3094
3095
  }
3096
+ function consoleStream(component, level) {
3097
+ if (level === "error" || component === "cli" || component.startsWith("cli:")) return process.stderr;
3098
+ return process.stdout;
3099
+ }
3095
3100
  function safeJson(o) {
3096
3101
  try {
3097
3102
  return JSON.stringify(o, (_k, v) => typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v);
package/dist/cli.mjs CHANGED
@@ -3770,8 +3770,9 @@ function createLogger(component) {
3770
3770
  }
3771
3771
  const extra = fields && Object.keys(fields).length ? " " + safeJson(fields) : "";
3772
3772
  const line = `${rec.t} ${level.toUpperCase().padEnd(5)} [${component}] ${msg}${extra}`;
3773
+ const stream = consoleStream(component, level);
3773
3774
  try {
3774
- process.stderr.write(line + "\n");
3775
+ stream.write(line + "\n");
3775
3776
  } catch {
3776
3777
  }
3777
3778
  }
@@ -3783,6 +3784,10 @@ function createLogger(component) {
3783
3784
  child: (sub) => createLogger(`${component}:${sub}`)
3784
3785
  };
3785
3786
  }
3787
+ function consoleStream(component, level) {
3788
+ if (level === "error" || component === "cli" || component.startsWith("cli:")) return process.stderr;
3789
+ return process.stdout;
3790
+ }
3786
3791
  function safeJson(o) {
3787
3792
  try {
3788
3793
  return JSON.stringify(o, (_k, v) => typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v);
@@ -3797,6 +3802,7 @@ var MACHINE_REJECTED_CODE = 4001;
3797
3802
  // src/daemon/connection.ts
3798
3803
  var INITIAL_BACKOFF_MS = 1e3;
3799
3804
  var MAX_BACKOFF_MS = 3e4;
3805
+ var SERVER_STALE_MS = Number(process.env.OPEN_TAG_DAEMON_SERVER_STALE_MS ?? 9e4);
3800
3806
  var Connection = class {
3801
3807
  constructor(url, key, onMsg, onOpen, mkWs = (u) => new wrapper_default(u)) {
3802
3808
  this.url = url;
@@ -3813,9 +3819,11 @@ var Connection = class {
3813
3819
  ws = null;
3814
3820
  delay = INITIAL_BACKOFF_MS;
3815
3821
  timer = null;
3822
+ watchdog = null;
3816
3823
  should = true;
3817
3824
  accepted = false;
3818
3825
  // per-attempt: flips true once the server sends any frame (proof it accepted us, not rejected)
3826
+ lastServerFrameAt = 0;
3819
3827
  log = createLogger("daemon:conn");
3820
3828
  connect() {
3821
3829
  this.should = true;
@@ -3827,13 +3835,22 @@ var Connection = class {
3827
3835
  close() {
3828
3836
  this.should = false;
3829
3837
  if (this.timer) clearTimeout(this.timer);
3838
+ if (this.watchdog) {
3839
+ clearTimeout(this.watchdog);
3840
+ this.watchdog = null;
3841
+ }
3830
3842
  this.ws?.close();
3831
3843
  }
3832
3844
  doConnect() {
3833
3845
  if (!this.should) return;
3834
3846
  const wsUrl = this.url.replace(/^http/, "ws") + `/daemon/connect?key=${encodeURIComponent(this.key)}`;
3835
3847
  this.log.info("connecting", { url: this.url });
3848
+ if (this.watchdog) {
3849
+ clearTimeout(this.watchdog);
3850
+ this.watchdog = null;
3851
+ }
3836
3852
  this.accepted = false;
3853
+ this.lastServerFrameAt = 0;
3837
3854
  this.ws = this.mkWs(wsUrl);
3838
3855
  this.ws.on("open", () => {
3839
3856
  this.log.info("connected");
@@ -3844,6 +3861,8 @@ var Connection = class {
3844
3861
  this.accepted = true;
3845
3862
  this.delay = INITIAL_BACKOFF_MS;
3846
3863
  }
3864
+ this.lastServerFrameAt = Date.now();
3865
+ this.armWatchdog();
3847
3866
  let m;
3848
3867
  try {
3849
3868
  m = JSON.parse(d.toString());
@@ -3853,6 +3872,10 @@ var Connection = class {
3853
3872
  this.onMsg(m);
3854
3873
  });
3855
3874
  this.ws.on("close", (code, reason) => {
3875
+ if (this.watchdog) {
3876
+ clearTimeout(this.watchdog);
3877
+ this.watchdog = null;
3878
+ }
3856
3879
  if (code === MACHINE_REJECTED_CODE) {
3857
3880
  this.delay = MAX_BACKOFF_MS;
3858
3881
  this.log.error(
@@ -3866,6 +3889,18 @@ var Connection = class {
3866
3889
  });
3867
3890
  this.ws.on("error", (e) => this.log.error("ws error", { detail: String(e?.message ?? e) }));
3868
3891
  }
3892
+ armWatchdog() {
3893
+ if (this.watchdog) clearTimeout(this.watchdog);
3894
+ this.watchdog = setTimeout(() => {
3895
+ if (!this.accepted || !this.lastServerFrameAt || this.ws?.readyState !== wrapper_default.OPEN) return;
3896
+ this.log.warn("server heartbeat stale; closing socket to reconnect", { staleMs: Date.now() - this.lastServerFrameAt });
3897
+ try {
3898
+ this.ws.close();
3899
+ } catch {
3900
+ }
3901
+ }, SERVER_STALE_MS + 1);
3902
+ this.watchdog.unref?.();
3903
+ }
3869
3904
  scheduleReconnect() {
3870
3905
  if (!this.should || this.timer) return;
3871
3906
  this.log.info("reconnecting", { ms: this.delay });
@@ -5329,132 +5364,6 @@ var cursorRuntime = {
5329
5364
  }
5330
5365
  };
5331
5366
 
5332
- // src/daemon/demoRuntime.ts
5333
- import { randomUUID as randomUUID2 } from "node:crypto";
5334
- var DEMO_NOTICE = "I'm a **demo agent** running on a no-op runtime \u2014 I don't call any LLM, so I can't do real work here. To get a real AI teammate, self-host open-tag and connect a runtime like Claude Code, Codex, or your own API key. See [getopentag.com](https://getopentag.com) to get started.";
5335
- var GREETINGS = ["Hi there! \u{1F44B}", "Hello again! \u{1F44B}", "Hey! \u{1F44B}", "Thanks for your message! \u{1F44B}", "Received! \u{1F44B}"];
5336
- function extractBody(text) {
5337
- const m = /\] [^:]+: (.+)$/s.exec(text);
5338
- return m ? m[1].trim() : "";
5339
- }
5340
- function extractTarget(text) {
5341
- const m = /^\[target=(\S+)/.exec(text);
5342
- return m ? m[1] : null;
5343
- }
5344
- function buildReply(userBody, turnIndex) {
5345
- const greeting = GREETINGS[turnIndex % GREETINGS.length];
5346
- const snippet = userBody.slice(0, 120);
5347
- const tail = userBody.length > 120 ? "\u2026" : "";
5348
- const echo = snippet ? `
5349
-
5350
- > You said: "${snippet}${tail}"` : "";
5351
- return `${greeting} ${DEMO_NOTICE}${echo}`;
5352
- }
5353
- function makeTransport(serverUrl2, agentToken, agentId) {
5354
- const base = serverUrl2.replace(/\/$/, "");
5355
- const headers = {
5356
- "Authorization": `Bearer ${agentToken}`,
5357
- "x-agent-id": agentId,
5358
- "Content-Type": "application/json"
5359
- };
5360
- return {
5361
- async check() {
5362
- const r = await fetch(`${base}/agent-api/message/check`, { headers });
5363
- const d = await r.json();
5364
- return d.messages ?? [];
5365
- },
5366
- async send(target, content) {
5367
- await fetch(`${base}/agent-api/message/send`, {
5368
- method: "POST",
5369
- headers,
5370
- body: JSON.stringify({ target, content })
5371
- });
5372
- }
5373
- };
5374
- }
5375
- var DemoRun = class {
5376
- constructor(opts, cb, transport) {
5377
- this.opts = opts;
5378
- this.cb = cb;
5379
- this.sessionId = opts.sessionId || randomUUID2();
5380
- this.transport = transport ?? makeTransport(
5381
- opts.env.OPEN_TAG_SERVER_URL ?? "",
5382
- opts.env.OPEN_TAG_AGENT_TOKEN ?? "",
5383
- opts.env.OPEN_TAG_AGENT_ID ?? ""
5384
- );
5385
- cb.onSession(this.sessionId);
5386
- this.enqueue(opts.initialPrompt);
5387
- }
5388
- opts;
5389
- cb;
5390
- sessionId;
5391
- queue = [];
5392
- turnBusy = false;
5393
- stopped = false;
5394
- turnIndex = 0;
5395
- transport;
5396
- enqueue(text) {
5397
- if (this.stopped) return;
5398
- this.queue.push(text);
5399
- this.pump();
5400
- }
5401
- pump() {
5402
- if (this.stopped || this.turnBusy || this.queue.length === 0) return;
5403
- this.runTurn(this.queue.shift());
5404
- }
5405
- // Each turn: check messages → for each pending target, build + post a demo reply → mark online.
5406
- // Uses setImmediate to stay non-blocking before the await chain takes over.
5407
- runTurn(_prompt) {
5408
- this.turnBusy = true;
5409
- this.cb.onActivity("working", "turn");
5410
- setImmediate(() => void this.executeTurn());
5411
- }
5412
- async executeTurn() {
5413
- if (this.stopped) {
5414
- this.turnBusy = false;
5415
- return;
5416
- }
5417
- try {
5418
- const messages = await this.transport.check();
5419
- if (!messages.length) {
5420
- this.cb.onActivity("online", "");
5421
- this.turnBusy = false;
5422
- this.pump();
5423
- return;
5424
- }
5425
- const byTarget = /* @__PURE__ */ new Map();
5426
- for (const m of messages) {
5427
- const target = extractTarget(m.text);
5428
- if (!target) continue;
5429
- byTarget.set(target, extractBody(m.text));
5430
- }
5431
- for (const [target, body] of byTarget) {
5432
- const reply = buildReply(body, this.turnIndex++);
5433
- this.cb.onTrajectory([{ kind: "text", text: reply }]);
5434
- await this.transport.send(target, reply);
5435
- }
5436
- } catch (e) {
5437
- this.cb.log.warn("demo: turn failed", { detail: String(e?.message ?? e) });
5438
- }
5439
- if (!this.stopped) {
5440
- this.cb.onActivity("online", "");
5441
- this.turnBusy = false;
5442
- this.pump();
5443
- }
5444
- }
5445
- stop() {
5446
- this.stopped = true;
5447
- }
5448
- };
5449
- var demoRuntime = {
5450
- name: "demo",
5451
- experimental: true,
5452
- start(opts, cb) {
5453
- const run = new DemoRun(opts, cb);
5454
- return { deliver: (text) => run.enqueue(text), stop: () => run.stop() };
5455
- }
5456
- };
5457
-
5458
5367
  // src/daemon/runtimes.ts
5459
5368
  function has(tool) {
5460
5369
  try {
@@ -5465,10 +5374,9 @@ function has(tool) {
5465
5374
  }
5466
5375
  }
5467
5376
  function detectRuntimes() {
5468
- const found = ["claude", "codex", "copilot", "kimi", "opencode", "pi", "cursor-agent"].filter(has).map((t) => t === "cursor-agent" ? "cursor" : t);
5469
- return [...found, "demo"];
5377
+ return ["claude", "codex", "copilot", "kimi", "opencode", "pi", "cursor-agent"].filter(has).map((t) => t === "cursor-agent" ? "cursor" : t);
5470
5378
  }
5471
- var REG = { claude: claudeRuntime, codex: codexRuntime, copilot: copilotRuntime, opencode: opencodeRuntime, kimi: kimiRuntime, pi: piRuntime, cursor: cursorRuntime, demo: demoRuntime };
5379
+ var REG = { claude: claudeRuntime, codex: codexRuntime, copilot: copilotRuntime, opencode: opencodeRuntime, kimi: kimiRuntime, pi: piRuntime, cursor: cursorRuntime };
5472
5380
  function getRuntime(name) {
5473
5381
  return REG[name] ?? null;
5474
5382
  }
@@ -5993,8 +5901,6 @@ async function listModels(runtime) {
5993
5901
  const models = parseCodexModels(r.stdout);
5994
5902
  return models.length ? models : null;
5995
5903
  }
5996
- case "demo":
5997
- return [{ id: "demo", label: "Demo", provider: "demo", default: true }];
5998
5904
  default:
5999
5905
  return null;
6000
5906
  }
@@ -6085,7 +5991,7 @@ conn = new Connection(serverUrl, apiKey, (msg) => {
6085
5991
  runningAgents: mgr.running(),
6086
5992
  hostname: os4.hostname(),
6087
5993
  os: `${os4.platform()} ${os4.arch()}`,
6088
- daemonVersion: "0.5.1",
5994
+ daemonVersion: "0.6.1",
6089
5995
  machineId: readMachineId()
6090
5996
  // Stable identity: empty on first connection; server sends it back via ready:ack for persistence.
6091
5997
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fancyboi999/open-tag-daemon",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "open-tag compute-plane daemon — connect any machine to an open-tag server so its agents run there. No repo clone needed.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",