@openape/ape-agent 2.8.12 → 2.8.14

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 +379 -178
  2. package/package.json +3 -3
package/dist/bridge.mjs CHANGED
@@ -1053,6 +1053,7 @@ import { ofetch as ofetch2 } from "ofetch";
1053
1053
  import { Buffer as Buffer3 } from "buffer";
1054
1054
  import { createPrivateKey } from "crypto";
1055
1055
  import { ofetch as ofetch4 } from "ofetch";
1056
+ import { ofetch as ofetch5 } from "ofetch";
1056
1057
  function getConfigDir() {
1057
1058
  const override = process.env.OPENAPE_CLI_AUTH_HOME;
1058
1059
  if (override) return override;
@@ -1398,7 +1399,7 @@ import { decodeJwt } from "jose";
1398
1399
  import WebSocket from "ws";
1399
1400
 
1400
1401
  // src/chat-api.ts
1401
- import { ofetch as ofetch5 } from "ofetch";
1402
+ import { ofetch as ofetch6 } from "ofetch";
1402
1403
  var MAX_BODY = 1e4;
1403
1404
  var ChatApi = class {
1404
1405
  constructor(endpoint, bearer) {
@@ -1414,7 +1415,7 @@ var ChatApi = class {
1414
1415
  if (opts.replyTo) payload.reply_to = opts.replyTo;
1415
1416
  if (opts.threadId) payload.thread_id = opts.threadId;
1416
1417
  if (opts.streaming) payload.streaming = true;
1417
- const result = await ofetch5(url, {
1418
+ const result = await ofetch6(url, {
1418
1419
  method: "POST",
1419
1420
  headers: { Authorization: await this.bearer() },
1420
1421
  body: payload
@@ -1429,14 +1430,14 @@ var ChatApi = class {
1429
1430
  */
1430
1431
  async listMessages(roomId, threadId, limit = 50) {
1431
1432
  const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/messages?thread_id=${encodeURIComponent(threadId)}&limit=${limit}`;
1432
- return await ofetch5(url, {
1433
+ return await ofetch6(url, {
1433
1434
  method: "GET",
1434
1435
  headers: { Authorization: await this.bearer() }
1435
1436
  });
1436
1437
  }
1437
1438
  async requestContact(peerEmail) {
1438
1439
  const url = `${this.endpoint}/api/contacts`;
1439
- return await ofetch5(url, {
1440
+ return await ofetch6(url, {
1440
1441
  method: "POST",
1441
1442
  headers: { Authorization: await this.bearer() },
1442
1443
  body: { email: peerEmail }
@@ -1444,14 +1445,14 @@ var ChatApi = class {
1444
1445
  }
1445
1446
  async listContacts() {
1446
1447
  const url = `${this.endpoint}/api/contacts`;
1447
- return await ofetch5(url, {
1448
+ return await ofetch6(url, {
1448
1449
  method: "GET",
1449
1450
  headers: { Authorization: await this.bearer() }
1450
1451
  });
1451
1452
  }
1452
1453
  async acceptContact(peerEmail) {
1453
1454
  const url = `${this.endpoint}/api/contacts/${encodeURIComponent(peerEmail)}/accept`;
1454
- return await ofetch5(url, {
1455
+ return await ofetch6(url, {
1455
1456
  method: "POST",
1456
1457
  headers: { Authorization: await this.bearer() }
1457
1458
  });
@@ -1464,7 +1465,7 @@ var ChatApi = class {
1464
1465
  */
1465
1466
  async createThread(roomId, name) {
1466
1467
  const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/threads`;
1467
- return await ofetch5(url, {
1468
+ return await ofetch6(url, {
1468
1469
  method: "POST",
1469
1470
  headers: { Authorization: await this.bearer() },
1470
1471
  body: { name: name.slice(0, 100) }
@@ -1492,7 +1493,7 @@ var ChatApi = class {
1492
1493
  if (opts.streaming !== void 0) payload.streaming = opts.streaming;
1493
1494
  if (opts.streamingStatus !== void 0) payload.streaming_status = opts.streamingStatus;
1494
1495
  if (Object.keys(payload).length === 0) return;
1495
- await ofetch5(url, {
1496
+ await ofetch6(url, {
1496
1497
  method: "PATCH",
1497
1498
  headers: { Authorization: await this.bearer() },
1498
1499
  body: payload
@@ -1505,12 +1506,133 @@ function clamp(s2, max) {
1505
1506
  return `${s2.slice(0, max - 1)}\u2026`;
1506
1507
  }
1507
1508
 
1509
+ // src/troop-chat-api.ts
1510
+ import { ofetch as ofetch7 } from "ofetch";
1511
+ var MAX_BODY2 = 64 * 1024;
1512
+ var SYNTHETIC_THREAD_ID = "main";
1513
+ function asHistory(msg, agentEmail, ownerEmail) {
1514
+ return {
1515
+ id: msg.id,
1516
+ roomId: msg.chatId,
1517
+ threadId: SYNTHETIC_THREAD_ID,
1518
+ senderEmail: msg.role === "agent" ? agentEmail : ownerEmail,
1519
+ senderAct: msg.role,
1520
+ body: msg.body,
1521
+ replyTo: msg.replyTo,
1522
+ createdAt: msg.createdAt
1523
+ };
1524
+ }
1525
+ function asPosted(msg) {
1526
+ return {
1527
+ id: msg.id,
1528
+ roomId: msg.chatId,
1529
+ threadId: SYNTHETIC_THREAD_ID,
1530
+ body: msg.body,
1531
+ createdAt: msg.createdAt
1532
+ };
1533
+ }
1534
+ var TroopChatApi = class {
1535
+ constructor(endpoint, bearer) {
1536
+ this.endpoint = endpoint;
1537
+ this.bearer = bearer;
1538
+ }
1539
+ endpoint;
1540
+ bearer;
1541
+ bootstrap = null;
1542
+ /** Resolve + cache the agent's chat row (lazy fetch on first use). */
1543
+ async getBootstrap() {
1544
+ if (this.bootstrap) return this.bootstrap;
1545
+ this.bootstrap = await ofetch7(`${this.endpoint}/api/agents/me/chat`, {
1546
+ method: "GET",
1547
+ headers: { Authorization: await this.bearer() }
1548
+ });
1549
+ return this.bootstrap;
1550
+ }
1551
+ /** chat.id + (lazy-fetched) ownerEmail for the bridge's frame-translation path. */
1552
+ async getChatContext() {
1553
+ const b2 = await this.getBootstrap();
1554
+ return { chatId: b2.chat.id, ownerEmail: b2.chat.ownerEmail, agentEmail: b2.chat.agentEmail };
1555
+ }
1556
+ async postMessage(roomId, body, opts = {}) {
1557
+ void roomId;
1558
+ void opts.threadId;
1559
+ const payload = {
1560
+ body: body.length > MAX_BODY2 ? `${body.slice(0, MAX_BODY2 - 1)}\u2026` : body
1561
+ };
1562
+ if (opts.replyTo) payload.reply_to = opts.replyTo;
1563
+ if (opts.streaming) payload.streaming = true;
1564
+ const msg = await ofetch7(`${this.endpoint}/api/agents/me/chat/messages`, {
1565
+ method: "POST",
1566
+ headers: { Authorization: await this.bearer() },
1567
+ body: payload
1568
+ });
1569
+ return asPosted(msg);
1570
+ }
1571
+ async listMessages(roomId, threadId, limit = 50) {
1572
+ void roomId;
1573
+ void threadId;
1574
+ void limit;
1575
+ const fresh = await ofetch7(`${this.endpoint}/api/agents/me/chat`, {
1576
+ method: "GET",
1577
+ headers: { Authorization: await this.bearer() }
1578
+ });
1579
+ this.bootstrap = fresh;
1580
+ return fresh.messages.map((m2) => asHistory(m2, fresh.chat.agentEmail, fresh.chat.ownerEmail));
1581
+ }
1582
+ async patchMessage(messageId, opts = {}) {
1583
+ const payload = {};
1584
+ if (opts.body !== void 0) {
1585
+ payload.body = opts.body.length > MAX_BODY2 ? `${opts.body.slice(0, MAX_BODY2 - 1)}\u2026` : opts.body;
1586
+ }
1587
+ if (opts.streaming !== void 0) payload.streaming = opts.streaming;
1588
+ if (opts.streamingStatus !== void 0) payload.streaming_status = opts.streamingStatus;
1589
+ if (Object.keys(payload).length === 0) return;
1590
+ await ofetch7(`${this.endpoint}/api/agents/me/chat/messages/${encodeURIComponent(messageId)}`, {
1591
+ method: "PATCH",
1592
+ headers: { Authorization: await this.bearer() },
1593
+ body: payload
1594
+ });
1595
+ }
1596
+ /**
1597
+ * Troop's chat doesn't have contacts — synthesize a single
1598
+ * always-connected entry pointing at the owner so the bridge's
1599
+ * initial-contact + allowlist flows are no-ops.
1600
+ */
1601
+ async listContacts() {
1602
+ const b2 = await this.getBootstrap();
1603
+ return [{
1604
+ peerEmail: b2.chat.ownerEmail,
1605
+ myStatus: "accepted",
1606
+ theirStatus: "accepted",
1607
+ connected: true,
1608
+ roomId: b2.chat.id
1609
+ }];
1610
+ }
1611
+ async requestContact(peerEmail) {
1612
+ void peerEmail;
1613
+ return (await this.listContacts())[0];
1614
+ }
1615
+ async acceptContact(peerEmail) {
1616
+ void peerEmail;
1617
+ return (await this.listContacts())[0];
1618
+ }
1619
+ /**
1620
+ * Troop has no threads — return a synthetic one. The bridge's
1621
+ * cron-runner falls back to the main thread on createThread
1622
+ * failure already, so a stable "main" stand-in is the right shape.
1623
+ */
1624
+ async createThread(roomId, name) {
1625
+ void roomId;
1626
+ return { id: SYNTHETIC_THREAD_ID, name: name.slice(0, 100) };
1627
+ }
1628
+ };
1629
+
1508
1630
  // src/cron-runner.ts
1509
1631
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1510
1632
  import { homedir as homedir6 } from "os";
1511
1633
  import { join as join6 } from "path";
1512
1634
 
1513
- // ../../packages/apes/dist/chunk-OOKB2IL2.js
1635
+ // ../../packages/apes/dist/chunk-ZEUSCNCH.js
1514
1636
  import { spawn } from "child_process";
1515
1637
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1516
1638
  import { homedir as homedir3 } from "os";
@@ -1530,8 +1652,10 @@ function capStdio(s2) {
1530
1652
  [truncated to ${MAX_STDIO_BYTES} bytes]`;
1531
1653
  }
1532
1654
  function runApeShell(cmd, timeoutMs = DEFAULT_TIMEOUT_MS) {
1655
+ const bypass = process.env.OPENAPE_BYPASS_APE_SHELL === "1";
1656
+ const [execBin, execArgs] = bypass ? ["/bin/bash", ["-c", cmd]] : [BIN, ["-c", cmd]];
1533
1657
  return new Promise((resolveResult) => {
1534
- const child = spawn(BIN, ["-c", cmd], {
1658
+ const child = spawn(execBin, execArgs, {
1535
1659
  env: { ...process.env, APE_WAIT: "1" },
1536
1660
  stdio: ["ignore", "pipe", "pipe"]
1537
1661
  });
@@ -1566,7 +1690,7 @@ function runApeShell(cmd, timeoutMs = DEFAULT_TIMEOUT_MS) {
1566
1690
  stderr: "",
1567
1691
  exit_code: -1,
1568
1692
  error: spawnError.message,
1569
- hint: `Could not exec '${BIN}'. The agent host needs @openape/apes installed globally so ape-shell is on PATH.`
1693
+ hint: `Could not exec '${execBin}'. The agent host needs @openape/apes installed globally so ape-shell is on PATH (or set OPENAPE_BYPASS_APE_SHELL=1 to skip the gated shell entirely \u2014 meant for the OpenApe pod where the container IS the sandbox).`
1570
1694
  });
1571
1695
  return;
1572
1696
  }
@@ -2324,6 +2448,52 @@ function previewJson(value, max = 500) {
2324
2448
  }
2325
2449
  return s2.length > max ? `${s2.slice(0, max)}\u2026` : s2;
2326
2450
  }
2451
+ async function aggregateChatStream(res) {
2452
+ if (!res.body) throw new Error("LiteLLM streaming response had no body");
2453
+ const reader = res.body.getReader();
2454
+ const decoder = new TextDecoder();
2455
+ let buf = "";
2456
+ let content = "";
2457
+ const toolCalls = /* @__PURE__ */ new Map();
2458
+ let finishReason;
2459
+ while (true) {
2460
+ const { value, done } = await reader.read();
2461
+ if (done) break;
2462
+ buf += decoder.decode(value, { stream: true });
2463
+ while (true) {
2464
+ const nl = buf.indexOf("\n");
2465
+ if (nl === -1) break;
2466
+ const line = buf.slice(0, nl).trim();
2467
+ buf = buf.slice(nl + 1);
2468
+ if (!line.startsWith("data:")) continue;
2469
+ const payload = line.slice(5).trim();
2470
+ if (!payload || payload === "[DONE]") continue;
2471
+ let chunk;
2472
+ try {
2473
+ chunk = JSON.parse(payload);
2474
+ } catch {
2475
+ continue;
2476
+ }
2477
+ const ch0 = chunk.choices?.[0];
2478
+ const delta = ch0?.delta;
2479
+ if (delta?.content) content += delta.content;
2480
+ if (delta?.tool_calls) {
2481
+ for (const tc of delta.tool_calls) {
2482
+ const idx = tc.index ?? 0;
2483
+ const existing = toolCalls.get(idx) ?? { id: "", type: "function", function: { name: "", arguments: "" } };
2484
+ if (tc.id) existing.id = tc.id;
2485
+ if (tc.function?.name) existing.function.name = tc.function.name;
2486
+ if (tc.function?.arguments) existing.function.arguments += tc.function.arguments;
2487
+ toolCalls.set(idx, existing);
2488
+ }
2489
+ }
2490
+ if (ch0?.finish_reason) finishReason = ch0.finish_reason;
2491
+ }
2492
+ }
2493
+ const message = { role: "assistant", content: content || null };
2494
+ if (toolCalls.size > 0) message.tool_calls = Array.from(toolCalls.values());
2495
+ return { choices: [{ message, finish_reason: finishReason }] };
2496
+ }
2327
2497
  async function runLoop(opts) {
2328
2498
  const fetchFn = opts.fetchImpl ?? fetch;
2329
2499
  const trace = [];
@@ -2334,23 +2504,25 @@ async function runLoop(opts) {
2334
2504
  ];
2335
2505
  const tools = asOpenAiTools(opts.tools);
2336
2506
  for (let step = 1; step <= opts.maxSteps; step++) {
2507
+ const requestBody = {
2508
+ model: opts.config.model,
2509
+ messages,
2510
+ ...tools.length > 0 ? { tools, tool_choice: "auto" } : {},
2511
+ ...opts.streamAggregate ? { stream: true } : {}
2512
+ };
2337
2513
  const res = await fetchFn(`${opts.config.apiBase}/chat/completions`, {
2338
2514
  method: "POST",
2339
2515
  headers: {
2340
2516
  "authorization": `Bearer ${opts.config.apiKey}`,
2341
2517
  "content-type": "application/json"
2342
2518
  },
2343
- body: JSON.stringify({
2344
- model: opts.config.model,
2345
- messages,
2346
- ...tools.length > 0 ? { tools, tool_choice: "auto" } : {}
2347
- })
2519
+ body: JSON.stringify(requestBody)
2348
2520
  });
2349
2521
  if (!res.ok) {
2350
2522
  const text = await res.text().catch(() => "");
2351
2523
  throw new Error(`LiteLLM ${res.status}: ${text.slice(0, 500)}`);
2352
2524
  }
2353
- const data = await res.json();
2525
+ const data = opts.streamAggregate ? await aggregateChatStream(res) : await res.json();
2354
2526
  const choice = data.choices?.[0];
2355
2527
  if (!choice) throw new Error("LiteLLM response had no choices");
2356
2528
  const assistant = choice.message;
@@ -2422,7 +2594,7 @@ async function runLoop(opts) {
2422
2594
  }
2423
2595
  var RPC_SESSION_TTL_MS = 60 * 60 * 1e3;
2424
2596
 
2425
- // ../../packages/apes/dist/chunk-DYSFQ26B.js
2597
+ // ../../packages/apes/dist/chunk-PEA2RDWK.js
2426
2598
  init_chunk_OBF7IMQ2();
2427
2599
  import { createHash } from "crypto";
2428
2600
  import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
@@ -2460,6 +2632,149 @@ async function computeArgvHash(argv2) {
2460
2632
  const digest2 = Array.from(hashArray).map((b2) => b2.toString(16).padStart(2, "0")).join("");
2461
2633
  return `SHA-256:${digest2}`;
2462
2634
  }
2635
+ function parseOptionArgs(tokens, valueOptions) {
2636
+ const options = {};
2637
+ const positionals = [];
2638
+ const takesValue = new Set(valueOptions ?? []);
2639
+ for (let index = 0; index < tokens.length; index += 1) {
2640
+ const token = tokens[index];
2641
+ if (token.startsWith("--")) {
2642
+ const stripped = token.slice(2);
2643
+ const eqIndex = stripped.indexOf("=");
2644
+ if (eqIndex >= 0) {
2645
+ options[stripped.slice(0, eqIndex)] = stripped.slice(eqIndex + 1);
2646
+ continue;
2647
+ }
2648
+ const next = tokens[index + 1];
2649
+ if (next && !next.startsWith("-")) {
2650
+ options[stripped] = next;
2651
+ index += 1;
2652
+ continue;
2653
+ }
2654
+ options[stripped] = "true";
2655
+ } else if (token.startsWith("-") && token.length > 1 && !/^-\d/.test(token)) {
2656
+ const key = token.slice(1);
2657
+ if (key.length === 1 && !takesValue.has(key)) {
2658
+ options[key] = "true";
2659
+ } else {
2660
+ const next = tokens[index + 1];
2661
+ if (next && !next.startsWith("-")) {
2662
+ options[key] = next;
2663
+ index += 1;
2664
+ } else {
2665
+ options[key] = "true";
2666
+ }
2667
+ }
2668
+ } else {
2669
+ positionals.push(token);
2670
+ }
2671
+ }
2672
+ return { options, positionals };
2673
+ }
2674
+ function resolveBindingToken(binding, bindings) {
2675
+ const match = binding.match(/^\{([^}|]+)(?:\|([^}]+))?\}$/);
2676
+ if (!match) return binding;
2677
+ const [, name, transform] = match;
2678
+ const value = bindings[name];
2679
+ if (!value) throw new Error(`Missing binding: ${name}`);
2680
+ if (!transform) return value;
2681
+ if (transform === "owner" || transform === "name") {
2682
+ const [owner, repo] = value.split("/");
2683
+ if (!owner || !repo) throw new Error(`Binding ${name} must be in owner/name form`);
2684
+ return transform === "owner" ? owner : repo;
2685
+ }
2686
+ throw new Error(`Unsupported binding transform: ${transform}`);
2687
+ }
2688
+ function renderTemplate(template, bindings) {
2689
+ return template.replace(/\{([^}]+)\}/g, (_3, expression) => resolveBindingToken(`{${expression}}`, bindings));
2690
+ }
2691
+ function parseResourceChain(chain, bindings) {
2692
+ return chain.map((entry) => {
2693
+ const [resource, selectorSpec = "*"] = entry.split(":", 2);
2694
+ if (!resource) throw new Error(`Invalid resource chain entry: ${entry}`);
2695
+ if (selectorSpec === "*") return { resource };
2696
+ const selector = Object.fromEntries(
2697
+ selectorSpec.split(",").map((segment) => {
2698
+ const [key, rawValue] = segment.split("=", 2);
2699
+ if (!key || !rawValue) throw new Error(`Invalid selector segment: ${segment}`);
2700
+ return [key, renderTemplate(rawValue, bindings)];
2701
+ })
2702
+ );
2703
+ return { resource, selector };
2704
+ });
2705
+ }
2706
+ function matchOperation(operation, argv2) {
2707
+ if (argv2.length < operation.command.length) return null;
2708
+ const prefix = argv2.slice(0, operation.command.length);
2709
+ if (prefix.join("\0") !== operation.command.join("\0")) return null;
2710
+ const remainder = argv2.slice(operation.command.length);
2711
+ const { options, positionals } = parseOptionArgs(remainder, operation.required_options);
2712
+ const expectedPositionals = operation.positionals ?? [];
2713
+ if (positionals.length !== expectedPositionals.length) return null;
2714
+ for (const option of operation.required_options ?? []) {
2715
+ if (!options[option]) return null;
2716
+ }
2717
+ const bindings = { ...options };
2718
+ for (let index = 0; index < expectedPositionals.length; index += 1) {
2719
+ const name = expectedPositionals[index];
2720
+ const value = positionals[index];
2721
+ if (name.startsWith("=")) {
2722
+ if (value !== name.slice(1)) return null;
2723
+ continue;
2724
+ }
2725
+ bindings[name] = value;
2726
+ }
2727
+ return bindings;
2728
+ }
2729
+ function expandCombinedFlags(argv2) {
2730
+ return argv2.flatMap((token) => {
2731
+ if (token.startsWith("-") && !token.startsWith("--") && token.length > 2 && /^-[a-z]+$/i.test(token)) {
2732
+ return Array.from(token.slice(1), (c3) => `-${c3}`);
2733
+ }
2734
+ return [token];
2735
+ });
2736
+ }
2737
+ function tryMatch(operations, argv2) {
2738
+ return operations.flatMap((operation) => {
2739
+ try {
2740
+ const bindings = matchOperation(operation, argv2);
2741
+ return bindings ? [{ operation, bindings }] : [];
2742
+ } catch {
2743
+ return [];
2744
+ }
2745
+ });
2746
+ }
2747
+ function matchArgvToOperation(operations, commandArgv) {
2748
+ let matches = tryMatch(operations, commandArgv);
2749
+ if (matches.length === 0) {
2750
+ const expanded = expandCombinedFlags(commandArgv);
2751
+ if (expanded.length !== commandArgv.length) {
2752
+ matches = tryMatch(operations, expanded);
2753
+ }
2754
+ }
2755
+ if (matches.length === 0) return null;
2756
+ if (matches.length > 1) {
2757
+ matches.sort((a2, b2) => b2.operation.command.length - a2.operation.command.length);
2758
+ matches = [matches[0]];
2759
+ }
2760
+ return matches[0];
2761
+ }
2762
+ function buildCliAuthDetail(cliId, operation, bindings) {
2763
+ const resource_chain = parseResourceChain(operation.resource_chain, bindings);
2764
+ const detail = {
2765
+ type: "openape_cli",
2766
+ cli_id: cliId,
2767
+ operation_id: operation.id,
2768
+ resource_chain,
2769
+ action: operation.action,
2770
+ permission: "",
2771
+ display: renderTemplate(operation.display, bindings),
2772
+ risk: operation.risk,
2773
+ ...operation.exact_command ? { constraints: { exact_command: true } } : {}
2774
+ };
2775
+ detail.permission = canonicalizeCliPermission(detail);
2776
+ return detail;
2777
+ }
2463
2778
 
2464
2779
  // ../../node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/core.mjs
2465
2780
  var LogLevels = {
@@ -3562,7 +3877,7 @@ function _getDefaultLogLevel() {
3562
3877
  }
3563
3878
  var consola = createConsola2();
3564
3879
 
3565
- // ../../packages/apes/dist/chunk-DYSFQ26B.js
3880
+ // ../../packages/apes/dist/chunk-PEA2RDWK.js
3566
3881
  var import_shell_quote = __toESM(require_shell_quote(), 1);
3567
3882
 
3568
3883
  // ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
@@ -3571,7 +3886,7 @@ function defineCommand(def) {
3571
3886
  return def;
3572
3887
  }
3573
3888
 
3574
- // ../../packages/apes/dist/chunk-DYSFQ26B.js
3889
+ // ../../packages/apes/dist/chunk-PEA2RDWK.js
3575
3890
  import { homedir as homedir52 } from "os";
3576
3891
  import { join as join5 } from "path";
3577
3892
  function parseKeyValue(line) {
@@ -3744,131 +4059,6 @@ function loadAdapter(cliId, explicitPath) {
3744
4059
  }
3745
4060
  var REGISTRY_URL = process.env.SHAPES_REGISTRY_URL ?? "https://raw.githubusercontent.com/openape-ai/shapes-registry/main/registry.json";
3746
4061
  var CACHE_TTL_MS = 60 * 60 * 1e3;
3747
- function parseOptionArgs(tokens, valueOptions) {
3748
- const options = {};
3749
- const positionals = [];
3750
- const takesValue = new Set(valueOptions ?? []);
3751
- for (let index = 0; index < tokens.length; index += 1) {
3752
- const token = tokens[index];
3753
- if (token.startsWith("--")) {
3754
- const stripped = token.slice(2);
3755
- const eqIndex = stripped.indexOf("=");
3756
- if (eqIndex >= 0) {
3757
- options[stripped.slice(0, eqIndex)] = stripped.slice(eqIndex + 1);
3758
- continue;
3759
- }
3760
- const next = tokens[index + 1];
3761
- if (next && !next.startsWith("-")) {
3762
- options[stripped] = next;
3763
- index += 1;
3764
- continue;
3765
- }
3766
- options[stripped] = "true";
3767
- } else if (token.startsWith("-") && token.length > 1 && !/^-\d/.test(token)) {
3768
- const key = token.slice(1);
3769
- if (key.length === 1 && !takesValue.has(key)) {
3770
- options[key] = "true";
3771
- } else {
3772
- const next = tokens[index + 1];
3773
- if (next && !next.startsWith("-")) {
3774
- options[key] = next;
3775
- index += 1;
3776
- } else {
3777
- options[key] = "true";
3778
- }
3779
- }
3780
- } else {
3781
- positionals.push(token);
3782
- }
3783
- }
3784
- return { options, positionals };
3785
- }
3786
- function resolveBindingToken(binding, bindings) {
3787
- const match = binding.match(/^\{([^}|]+)(?:\|([^}]+))?\}$/);
3788
- if (!match)
3789
- return binding;
3790
- const [, name, transform] = match;
3791
- const value = bindings[name];
3792
- if (!value)
3793
- throw new Error(`Missing binding: ${name}`);
3794
- if (!transform)
3795
- return value;
3796
- if (transform === "owner" || transform === "name") {
3797
- const [owner, repo] = value.split("/");
3798
- if (!owner || !repo)
3799
- throw new Error(`Binding ${name} must be in owner/name form`);
3800
- return transform === "owner" ? owner : repo;
3801
- }
3802
- throw new Error(`Unsupported binding transform: ${transform}`);
3803
- }
3804
- function renderTemplate(template, bindings) {
3805
- return template.replace(/\{([^}]+)\}/g, (_3, expression) => resolveBindingToken(`{${expression}}`, bindings));
3806
- }
3807
- function parseResourceChain(chain, bindings) {
3808
- return chain.map((entry) => {
3809
- const [resource, selectorSpec = "*"] = entry.split(":", 2);
3810
- if (!resource)
3811
- throw new Error(`Invalid resource chain entry: ${entry}`);
3812
- if (selectorSpec === "*") {
3813
- return { resource };
3814
- }
3815
- const selector = Object.fromEntries(
3816
- selectorSpec.split(",").map((segment) => {
3817
- const [key, rawValue] = segment.split("=", 2);
3818
- if (!key || !rawValue)
3819
- throw new Error(`Invalid selector segment: ${segment}`);
3820
- return [key, renderTemplate(rawValue, bindings)];
3821
- })
3822
- );
3823
- return { resource, selector };
3824
- });
3825
- }
3826
- function matchOperation(operation, argv2) {
3827
- if (argv2.length < operation.command.length)
3828
- return null;
3829
- const prefix = argv2.slice(0, operation.command.length);
3830
- if (prefix.join("\0") !== operation.command.join("\0"))
3831
- return null;
3832
- const remainder = argv2.slice(operation.command.length);
3833
- const { options, positionals } = parseOptionArgs(remainder, operation.required_options);
3834
- const expectedPositionals = operation.positionals ?? [];
3835
- if (positionals.length !== expectedPositionals.length)
3836
- return null;
3837
- for (const option of operation.required_options ?? []) {
3838
- if (!options[option])
3839
- return null;
3840
- }
3841
- const bindings = { ...options };
3842
- for (let index = 0; index < expectedPositionals.length; index += 1) {
3843
- const name = expectedPositionals[index];
3844
- const value = positionals[index];
3845
- if (name.startsWith("=")) {
3846
- if (value !== name.slice(1))
3847
- return null;
3848
- continue;
3849
- }
3850
- bindings[name] = value;
3851
- }
3852
- return bindings;
3853
- }
3854
- function expandCombinedFlags(argv2) {
3855
- return argv2.flatMap((token) => {
3856
- if (token.startsWith("-") && !token.startsWith("--") && token.length > 2 && /^-[a-z]+$/i.test(token)) {
3857
- return Array.from(token.slice(1), (c3) => `-${c3}`);
3858
- }
3859
- return [token];
3860
- });
3861
- }
3862
- function tryMatch(operations, argv2) {
3863
- return operations.flatMap((operation) => {
3864
- try {
3865
- const bindings = matchOperation(operation, argv2);
3866
- return bindings ? [{ operation, bindings }] : [];
3867
- } catch {
3868
- return [];
3869
- }
3870
- });
3871
- }
3872
4062
  async function resolveCommand(loaded, fullArgv) {
3873
4063
  const [executable, ...commandArgv] = fullArgv;
3874
4064
  if (!executable) {
@@ -3877,34 +4067,12 @@ async function resolveCommand(loaded, fullArgv) {
3877
4067
  if (executable !== loaded.adapter.cli.executable) {
3878
4068
  throw new Error(`Adapter ${loaded.adapter.cli.id} expects executable ${loaded.adapter.cli.executable}, got ${executable}`);
3879
4069
  }
3880
- let matches = tryMatch(loaded.adapter.operations, commandArgv);
3881
- if (matches.length === 0) {
3882
- const expanded = expandCombinedFlags(commandArgv);
3883
- if (expanded.length !== commandArgv.length) {
3884
- matches = tryMatch(loaded.adapter.operations, expanded);
3885
- }
3886
- }
3887
- if (matches.length === 0) {
4070
+ const match = matchArgvToOperation(loaded.adapter.operations, commandArgv);
4071
+ if (!match) {
3888
4072
  throw new Error(`No adapter operation matched: ${fullArgv.join(" ")}`);
3889
4073
  }
3890
- if (matches.length > 1) {
3891
- matches.sort((a2, b2) => b2.operation.command.length - a2.operation.command.length);
3892
- matches = [matches[0]];
3893
- }
3894
- const { operation, bindings } = matches[0];
3895
- const resource_chain = parseResourceChain(operation.resource_chain, bindings);
3896
- const detail = {
3897
- type: "openape_cli",
3898
- cli_id: loaded.adapter.cli.id,
3899
- operation_id: operation.id,
3900
- resource_chain,
3901
- action: operation.action,
3902
- permission: "",
3903
- display: renderTemplate(operation.display, bindings),
3904
- risk: operation.risk,
3905
- ...operation.exact_command ? { constraints: { exact_command: true } } : {}
3906
- };
3907
- detail.permission = canonicalizeCliPermission(detail);
4074
+ const { operation, bindings } = match;
4075
+ const detail = buildCliAuthDetail(loaded.adapter.cli.id, operation, bindings);
3908
4076
  return {
3909
4077
  adapter: loaded.adapter,
3910
4078
  source: loaded.source,
@@ -3975,7 +4143,7 @@ function extractOption(args, name) {
3975
4143
  }
3976
4144
  var AUTH_FILE2 = join5(homedir52(), ".config", "apes", "auth.json");
3977
4145
 
3978
- // ../../packages/apes/dist/chunk-4KPKANZT.js
4146
+ // ../../packages/apes/dist/chunk-NYJSBFLG.js
3979
4147
  init_chunk_OBF7IMQ2();
3980
4148
  var debug = process.argv.includes("--debug");
3981
4149
 
@@ -4294,7 +4462,7 @@ function readAgentIdentity() {
4294
4462
  const ownerEmail = parsed.owner_email ?? process.env.OPENAPE_OWNER_EMAIL;
4295
4463
  if (!ownerEmail) {
4296
4464
  throw new Error(
4297
- `auth.json at ${path} missing 'owner_email' and no OPENAPE_OWNER_EMAIL env var set \u2014 re-spawn the agent with @openape/apes >= 0.28 or add OPENAPE_OWNER_EMAIL to the launchd plist`
4465
+ `auth.json at ${path} missing 'owner_email' and no OPENAPE_OWNER_EMAIL env var set \u2014 re-spawn the agent with @openape/apes >= 0.28 or set OPENAPE_OWNER_EMAIL in the container env`
4298
4466
  );
4299
4467
  }
4300
4468
  return { email: parsed.email, ownerEmail, idp: parsed.idp };
@@ -4794,6 +4962,8 @@ function readConfig() {
4794
4962
  "APE_CHAT_BRIDGE_MODEL is not set. Set it in the bridge .env (usually `~/Library/Application Support/openape/bridge/.env` on macOS) or globally in `~/litellm/.env` so resolveBridgeConfig picks it up at spawn time. Common values: `gpt-5.4` (ChatGPT-only LiteLLM proxy), `claude-haiku-4-5` (Anthropic-only)."
4795
4963
  );
4796
4964
  }
4965
+ const targetRaw = (process3.env.OPENAPE_BRIDGE_TARGET ?? "chat").toLowerCase();
4966
+ const target = targetRaw === "troop" ? "troop" : "chat";
4797
4967
  return {
4798
4968
  endpoint: (process3.env.APE_CHAT_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/$/, ""),
4799
4969
  apesBin: process3.env.APE_CHAT_BRIDGE_APES_BIN ?? DEFAULT_APES_BIN,
@@ -4801,7 +4971,8 @@ function readConfig() {
4801
4971
  systemPrompt: process3.env.APE_CHAT_BRIDGE_SYSTEM_PROMPT ?? DEFAULT_SYSTEM_PROMPT,
4802
4972
  tools,
4803
4973
  maxSteps: Number.isFinite(maxSteps) && maxSteps > 0 ? maxSteps : DEFAULT_MAX_STEPS,
4804
- roomFilter: process3.env.APE_CHAT_BRIDGE_ROOM
4974
+ roomFilter: process3.env.APE_CHAT_BRIDGE_ROOM,
4975
+ target
4805
4976
  };
4806
4977
  }
4807
4978
  async function getIdentity() {
@@ -4837,7 +5008,7 @@ var Bridge = class {
4837
5008
  const idp = await ensureFreshIdpAuth();
4838
5009
  return `Bearer ${idp.access_token}`;
4839
5010
  };
4840
- this.chat = new ChatApi(this.cfg.endpoint, this.bearer);
5011
+ this.chat = this.cfg.target === "troop" ? new TroopChatApi(this.cfg.endpoint, this.bearer) : new ChatApi(this.cfg.endpoint, this.bearer);
4841
5012
  this.cron = new CronRunner({
4842
5013
  runtimeConfig: this.runtimeConfig(),
4843
5014
  chat: this.chat,
@@ -4855,6 +5026,11 @@ var Bridge = class {
4855
5026
  // its own message history and calls @openape/apes' runLoop directly
4856
5027
  // (no stdio JSON-RPC subprocess — see thread-session.ts).
4857
5028
  threads = /* @__PURE__ */ new Map();
5029
+ // ChatApi and TroopChatApi expose the same surface (postMessage /
5030
+ // listMessages / patchMessage / listContacts / requestContact /
5031
+ // acceptContact / createThread) so the rest of the bridge calls
5032
+ // through a structurally-typed reference without caring which
5033
+ // backend is in play. Picked at construction time from cfg.target.
4858
5034
  chat;
4859
5035
  bearer;
4860
5036
  cron;
@@ -4912,6 +5088,29 @@ var Bridge = class {
4912
5088
  if (accepted.length > 0) log(`accepted: ${accepted.join(", ")}`);
4913
5089
  if (skipped.length > 0) log(`skipped (not on allowlist): ${skipped.join(", ")}`);
4914
5090
  }
5091
+ /**
5092
+ * Translate troop's chat-frame payload shape into the
5093
+ * chat.openape.ai-style Message the rest of this bridge expects.
5094
+ * Troop's payload uses `role` (human|agent) + `chatId` + no
5095
+ * senderEmail; the bridge's handleInbound checks
5096
+ * `senderEmail === selfEmail` to skip its own echoes, so we
5097
+ * synthesize the email from role (agent → self, human → owner).
5098
+ * threadId is the synthetic 'main' because troop has no threads.
5099
+ */
5100
+ translateTroopPayload(chatId, payload) {
5101
+ const role = payload.role === "agent" ? "agent" : "human";
5102
+ return {
5103
+ id: String(payload.id ?? ""),
5104
+ roomId: chatId || String(payload.chatId ?? ""),
5105
+ threadId: "main",
5106
+ senderEmail: role === "agent" ? this.selfEmail : this.ownerEmail,
5107
+ senderAct: role,
5108
+ body: typeof payload.body === "string" ? payload.body : "",
5109
+ replyTo: typeof payload.replyTo === "string" ? payload.replyTo : null,
5110
+ createdAt: typeof payload.createdAt === "number" ? payload.createdAt : Math.floor(Date.now() / 1e3),
5111
+ editedAt: typeof payload.editedAt === "number" ? payload.editedAt : null
5112
+ };
5113
+ }
4915
5114
  async handleInbound(msg) {
4916
5115
  if (msg.senderEmail === this.selfEmail) return;
4917
5116
  if (!msg.body.trim()) return;
@@ -4977,7 +5176,8 @@ var Bridge = class {
4977
5176
  }
4978
5177
  async pumpOnce() {
4979
5178
  const bearer = await this.bearer();
4980
- const wsUrl = `${this.cfg.endpoint.replace(/^http/, "ws")}/api/ws?token=${encodeURIComponent(bearer.replace(/^Bearer\s+/i, ""))}`;
5179
+ const wsPath = this.cfg.target === "troop" ? "/_ws/chat" : "/api/ws";
5180
+ const wsUrl = `${this.cfg.endpoint.replace(/^http/, "ws")}${wsPath}?token=${encodeURIComponent(bearer.replace(/^Bearer\s+/i, ""))}`;
4981
5181
  const ws = new WebSocket(wsUrl);
4982
5182
  return new Promise((resolve4, reject) => {
4983
5183
  let pingTimer;
@@ -5006,8 +5206,9 @@ var Bridge = class {
5006
5206
  } catch {
5007
5207
  return;
5008
5208
  }
5009
- if (frame.type !== "message") return;
5010
- void this.handleInbound(frame.payload);
5209
+ if (frame.type !== "message" || !frame.payload) return;
5210
+ const msg = this.cfg.target === "troop" ? this.translateTroopPayload(frame.chat_id ?? "", frame.payload) : frame.payload;
5211
+ void this.handleInbound(msg);
5011
5212
  });
5012
5213
  ws.on("close", () => {
5013
5214
  if (pingTimer) clearInterval(pingTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/ape-agent",
3
- "version": "2.8.12",
3
+ "version": "2.8.14",
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/apes": "1.28.11",
26
+ "@openape/apes": "1.28.13",
27
27
  "@openape/prompt-injection-detector": "0.1.0",
28
- "@openape/cli-auth": "0.4.1"
28
+ "@openape/cli-auth": "0.5.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@antfu/eslint-config": "^7.6.1",