@openape/ape-agent 2.9.0 → 2.9.2

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.
Files changed (2) hide show
  1. package/dist/bridge.mjs +80 -52
  2. package/package.json +4 -4
package/dist/bridge.mjs CHANGED
@@ -1541,7 +1541,62 @@ import { basename, join as join22 } from "path";
1541
1541
 
1542
1542
  // ../../packages/core/dist/index.js
1543
1543
  import * as jose from "jose";
1544
+ import { lookup } from "dns/promises";
1545
+ import { isIP } from "net";
1544
1546
  var HKDF_INFO = new TextEncoder().encode("openape-sealed-box-v1");
1547
+ function isBlockedAddress(ip) {
1548
+ const fam = isIP(ip);
1549
+ if (fam === 4) {
1550
+ const o3 = ip.split(".");
1551
+ const a2 = Number(o3[0]);
1552
+ const b2 = Number(o3[1]);
1553
+ if (a2 === 0) return true;
1554
+ if (a2 === 127) return true;
1555
+ if (a2 === 10) return true;
1556
+ if (a2 === 172 && b2 >= 16 && b2 <= 31) return true;
1557
+ if (a2 === 192 && b2 === 168) return true;
1558
+ if (a2 === 169 && b2 === 254) return true;
1559
+ if (a2 === 100 && b2 >= 64 && b2 <= 127) return true;
1560
+ return false;
1561
+ }
1562
+ const low = ip.toLowerCase().replace(/^\[|\]$/g, "");
1563
+ if (low === "::" || low === "::1") return true;
1564
+ if (low.startsWith("fe80")) return true;
1565
+ if (low.startsWith("fc") || low.startsWith("fd")) return true;
1566
+ const mapped = low.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
1567
+ if (mapped) return isBlockedAddress(mapped[1]);
1568
+ return false;
1569
+ }
1570
+ async function assertPublicUrl(rawUrl, opts = {}) {
1571
+ let url;
1572
+ try {
1573
+ url = new URL(rawUrl);
1574
+ } catch {
1575
+ throw new Error(`Invalid URL: ${rawUrl}`);
1576
+ }
1577
+ const allowedSchemes = opts.allowHttp === true ? /* @__PURE__ */ new Set(["https:", "http:"]) : /* @__PURE__ */ new Set(["https:"]);
1578
+ if (!allowedSchemes.has(url.protocol)) {
1579
+ const expected = opts.allowHttp === true ? "http(s)" : "https";
1580
+ throw new Error(`URL must use ${expected}:// (got ${url.protocol}//) \u2014 ${rawUrl}`);
1581
+ }
1582
+ const host = url.hostname.replace(/^\[|\]$/g, "");
1583
+ const addresses = [];
1584
+ if (isIP(host)) {
1585
+ addresses.push(host);
1586
+ } else {
1587
+ const results = await lookup(host, { all: true });
1588
+ for (const r3 of results) addresses.push(r3.address);
1589
+ }
1590
+ if (addresses.length === 0) {
1591
+ throw new Error(`Host did not resolve: ${host}`);
1592
+ }
1593
+ for (const addr of addresses) {
1594
+ if (isBlockedAddress(addr)) {
1595
+ throw new Error(`Refusing to fetch a private/loopback address (${addr}) for ${rawUrl}`);
1596
+ }
1597
+ }
1598
+ return url;
1599
+ }
1545
1600
 
1546
1601
  // ../../packages/grants/dist/index.js
1547
1602
  function normalizeSelector(selector) {
@@ -3090,8 +3145,6 @@ import { dirname, normalize, resolve } from "path";
3090
3145
  import { homedir as homedir23 } from "os";
3091
3146
  import { resolve as resolve2 } from "path";
3092
3147
  import process2 from "process";
3093
- import { lookup } from "dns/promises";
3094
- import { isIP } from "net";
3095
3148
  import { execFileSync } from "child_process";
3096
3149
  import { execFileSync as execFileSync2 } from "child_process";
3097
3150
  var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
@@ -3584,59 +3637,10 @@ var gitWorktreeTools = [
3584
3637
  }
3585
3638
  }
3586
3639
  ];
3587
- function isBlockedAddress(ip) {
3588
- const fam = isIP(ip);
3589
- if (fam === 4) {
3590
- const o3 = ip.split(".");
3591
- const a2 = Number(o3[0]);
3592
- const b2 = Number(o3[1]);
3593
- if (a2 === 0) return true;
3594
- if (a2 === 127) return true;
3595
- if (a2 === 10) return true;
3596
- if (a2 === 172 && b2 >= 16 && b2 <= 31) return true;
3597
- if (a2 === 192 && b2 === 168) return true;
3598
- if (a2 === 169 && b2 === 254) return true;
3599
- if (a2 === 100 && b2 >= 64 && b2 <= 127) return true;
3600
- return false;
3601
- }
3602
- const low = ip.toLowerCase().replace(/^\[|\]$/g, "");
3603
- if (low === "::" || low === "::1") return true;
3604
- if (low.startsWith("fe80")) return true;
3605
- if (low.startsWith("fc") || low.startsWith("fd")) return true;
3606
- const mapped = low.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
3607
- if (mapped) return isBlockedAddress(mapped[1]);
3608
- return false;
3609
- }
3610
- async function assertPublicUrl(rawUrl) {
3611
- let url;
3612
- try {
3613
- url = new URL(rawUrl);
3614
- } catch {
3615
- throw new Error(`Invalid URL: ${rawUrl}`);
3616
- }
3617
- if (url.protocol !== "https:" && url.protocol !== "http:") {
3618
- throw new Error(`url must be http(s) (got ${url.protocol})`);
3619
- }
3620
- const host = url.hostname.replace(/^\[|\]$/g, "");
3621
- const addresses = [];
3622
- if (isIP(host)) {
3623
- addresses.push(host);
3624
- } else {
3625
- const results = await lookup(host, { all: true });
3626
- for (const r3 of results) addresses.push(r3.address);
3627
- }
3628
- if (addresses.length === 0) throw new Error(`Host did not resolve: ${host}`);
3629
- for (const addr of addresses) {
3630
- if (isBlockedAddress(addr)) {
3631
- throw new Error(`Refusing to fetch a private/loopback address (${addr}) for ${rawUrl}`);
3632
- }
3633
- }
3634
- return url;
3635
- }
3636
3640
  async function safeFetch(rawUrl, init2 = {}, maxRedirects = 5) {
3637
3641
  let current = rawUrl;
3638
3642
  for (let hop = 0; hop <= maxRedirects; hop += 1) {
3639
- await assertPublicUrl(current);
3643
+ await assertPublicUrl(current, { allowHttp: true });
3640
3644
  const res = await fetch(current, { ...init2, redirect: "manual" });
3641
3645
  if (res.status >= 300 && res.status < 400) {
3642
3646
  const location = res.headers.get("location");
@@ -4707,6 +4711,8 @@ function createThrottle(fn, intervalMs) {
4707
4711
 
4708
4712
  // src/thread-session.ts
4709
4713
  var PATCH_INTERVAL_MS = 300;
4714
+ var NO_ACTIVITY_TIMEOUT_MS = 6e4;
4715
+ var NO_RESPONSE_MESSAGE = "(no response from the model backend \u2014 it may be unavailable or its API auth expired; nothing was changed, please retry)";
4710
4716
  var ThreadSession = class {
4711
4717
  constructor(deps) {
4712
4718
  this.deps = deps;
@@ -4768,6 +4774,18 @@ var ThreadSession = class {
4768
4774
  };
4769
4775
  const { systemPrompt, tools } = this.deps.resolveConfig();
4770
4776
  await this.backfillHistoryOnce(replyToMessageId, body);
4777
+ let sawActivity = false;
4778
+ let turnSettled = false;
4779
+ const settleOnce = () => {
4780
+ if (turnSettled) return true;
4781
+ turnSettled = true;
4782
+ return false;
4783
+ };
4784
+ const watchdog = setTimeout(() => {
4785
+ if (sawActivity || this.active !== turn || settleOnce()) return;
4786
+ this.deps.log(`turn watchdog: no model activity in ${NO_ACTIVITY_TIMEOUT_MS}ms \u2014 failing turn (room=${this.deps.roomId} thread=${this.deps.threadId})`);
4787
+ void this.failTurn(NO_RESPONSE_MESSAGE);
4788
+ }, NO_ACTIVITY_TIMEOUT_MS);
4771
4789
  try {
4772
4790
  const result = await runLoop({
4773
4791
  config: this.deps.runtimeConfig,
@@ -4778,11 +4796,13 @@ var ThreadSession = class {
4778
4796
  history: this.history,
4779
4797
  handlers: {
4780
4798
  onTextDelta: (delta) => {
4799
+ sawActivity = true;
4781
4800
  if (!this.active) return;
4782
4801
  this.active.accumulated += delta;
4783
4802
  this.active.throttle.schedule();
4784
4803
  },
4785
4804
  onToolCall: ({ name }) => {
4805
+ sawActivity = true;
4786
4806
  this.deps.log(`[${this.deps.roomId}/${this.deps.threadId.slice(0, 8)}] tool_call: ${name}`);
4787
4807
  void setStatus(`\u{1F527} ${name}`);
4788
4808
  },
@@ -4796,15 +4816,23 @@ var ThreadSession = class {
4796
4816
  }
4797
4817
  }
4798
4818
  });
4819
+ clearTimeout(watchdog);
4820
+ if (settleOnce()) return;
4799
4821
  this.history.push({ role: "user", content: body });
4800
4822
  if (result.finalMessage) {
4801
4823
  this.history.push({ role: "assistant", content: result.finalMessage });
4802
4824
  }
4803
4825
  if (result.status === "error") {
4804
4826
  this.deps.log(`runtime done with status=error (room=${this.deps.roomId} thread=${this.deps.threadId})`);
4827
+ if (!turn.accumulated) {
4828
+ await this.failTurn("(the model run ended with an error and produced no output \u2014 please retry)");
4829
+ return;
4830
+ }
4805
4831
  }
4806
4832
  await this.endTurn();
4807
4833
  } catch (err) {
4834
+ clearTimeout(watchdog);
4835
+ if (settleOnce()) return;
4808
4836
  const message = err instanceof Error ? err.message : String(err);
4809
4837
  this.deps.log(`runtime error (room=${this.deps.roomId} thread=${this.deps.threadId}): ${message}`);
4810
4838
  await this.failTurn(`(runtime error: ${message})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/ape-agent",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "OpenApe agent runtime: per-agent process that connects to chat.openape.ai, runs the LLM loop with tools + cron tasks, and streams replies back to owners.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,9 +23,9 @@
23
23
  "ofetch": "^1.4.1",
24
24
  "ws": "^8.18.0",
25
25
  "yaml": "^2.8.0",
26
- "@openape/cli-auth": "0.5.0",
27
- "@openape/apes": "1.29.0",
28
- "@openape/prompt-injection-detector": "0.1.0"
26
+ "@openape/apes": "1.30.0",
27
+ "@openape/prompt-injection-detector": "0.1.0",
28
+ "@openape/cli-auth": "0.5.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@antfu/eslint-config": "^7.6.1",