@openape/ape-agent 2.3.0 → 2.5.0

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 +72 -16
  2. package/package.json +1 -1
package/dist/bridge.mjs CHANGED
@@ -1366,6 +1366,19 @@ var ChatApi = class {
1366
1366
  });
1367
1367
  return result;
1368
1368
  }
1369
+ /**
1370
+ * Fetch the most recent `limit` messages in a thread, oldest-first.
1371
+ * Used by ThreadSession to seed `history` so the agent has the
1372
+ * full conversation context after a bridge restart — otherwise it
1373
+ * only sees messages that arrived via WS since the process boot.
1374
+ */
1375
+ async listMessages(roomId, threadId, limit = 50) {
1376
+ const url = `${this.endpoint}/api/rooms/${encodeURIComponent(roomId)}/messages?thread_id=${encodeURIComponent(threadId)}&limit=${limit}`;
1377
+ return await ofetch5(url, {
1378
+ method: "GET",
1379
+ headers: { Authorization: await this.bearer() }
1380
+ });
1381
+ }
1369
1382
  async requestContact(peerEmail) {
1370
1383
  const url = `${this.endpoint}/api/contacts`;
1371
1384
  return await ofetch5(url, {
@@ -4041,14 +4054,22 @@ var PATCH_INTERVAL_MS = 300;
4041
4054
  var ThreadSession = class {
4042
4055
  constructor(deps) {
4043
4056
  this.deps = deps;
4044
- this.resolvedTools = taskTools(deps.tools);
4045
4057
  }
4046
4058
  active;
4047
4059
  queue = [];
4048
4060
  history = [];
4049
- resolvedTools;
4050
- /** No-op placeholder kept for API compatibility with the previous
4051
- * RPC-listener model where dispose() detached the listener. */
4061
+ /**
4062
+ * Whether we've already backfilled history from the chat server.
4063
+ * Done lazily on the first turn so a freshly-created ThreadSession
4064
+ * (e.g. after a bridge restart) sees the full conversation context,
4065
+ * not just the message that woke it up. We skip the message that
4066
+ * triggered the turn — runLoop adds it via `userMessage`.
4067
+ */
4068
+ backfilled = false;
4069
+ /**
4070
+ * No-op placeholder kept for API compatibility with the previous
4071
+ * RPC-listener model where dispose() detached the listener.
4072
+ */
4052
4073
  dispose() {
4053
4074
  }
4054
4075
  /** Forward an inbound chat message to the runtime. Queues if a turn is in flight. */
@@ -4088,12 +4109,14 @@ var ThreadSession = class {
4088
4109
  this.deps.log(`status patch failed: ${err instanceof Error ? err.message : String(err)}`);
4089
4110
  }
4090
4111
  };
4112
+ const { systemPrompt, tools } = this.deps.resolveConfig();
4113
+ await this.backfillHistoryOnce(replyToMessageId, body);
4091
4114
  try {
4092
4115
  const result = await runLoop({
4093
4116
  config: this.deps.runtimeConfig,
4094
- systemPrompt: this.deps.systemPrompt,
4117
+ systemPrompt,
4095
4118
  userMessage: body,
4096
- tools: this.resolvedTools,
4119
+ tools: taskTools(tools),
4097
4120
  maxSteps: this.deps.maxSteps,
4098
4121
  history: this.history,
4099
4122
  handlers: {
@@ -4130,6 +4153,33 @@ var ThreadSession = class {
4130
4153
  await this.failTurn(`(runtime error: ${message})`);
4131
4154
  }
4132
4155
  }
4156
+ /**
4157
+ * Fetch recent chat history for this thread and seed `this.history`.
4158
+ * Idempotent — only runs once per ThreadSession instance. Skips the
4159
+ * placeholder we just posted plus the inbound message that triggered
4160
+ * this turn (runLoop's `userMessage` handles that one).
4161
+ *
4162
+ * Failures are non-fatal: we log and continue with empty history.
4163
+ * That preserves the pre-backfill behaviour rather than failing the
4164
+ * turn over a transient chat-server hiccup.
4165
+ */
4166
+ async backfillHistoryOnce(currentMessageId, currentBody) {
4167
+ if (this.backfilled) return;
4168
+ this.backfilled = true;
4169
+ try {
4170
+ const rows = await this.deps.chat.listMessages(this.deps.roomId, this.deps.threadId, 50);
4171
+ for (const row of rows) {
4172
+ if (row.id === currentMessageId) continue;
4173
+ if (!row.body || row.body.length === 0) continue;
4174
+ if (row.body === currentBody && row.senderEmail !== this.deps.selfEmail) continue;
4175
+ const role = row.senderEmail === this.deps.selfEmail ? "assistant" : "user";
4176
+ this.history.push({ role, content: row.body });
4177
+ }
4178
+ this.deps.log(`[${this.deps.roomId}/${this.deps.threadId.slice(0, 8)}] backfilled ${this.history.length} message(s) from chat history`);
4179
+ } catch (err) {
4180
+ this.deps.log(`[${this.deps.roomId}/${this.deps.threadId.slice(0, 8)}] backfill failed (continuing with empty history): ${err instanceof Error ? err.message : String(err)}`);
4181
+ }
4182
+ }
4133
4183
  /**
4134
4184
  * Stream-end: flush any pending throttled body PATCH, then mark the
4135
4185
  * message as no-longer-streaming. The combined call also triggers
@@ -4365,16 +4415,22 @@ var Bridge = class {
4365
4415
  threadId,
4366
4416
  chat: this.chat,
4367
4417
  runtimeConfig: this.runtimeConfig(),
4368
- // Tools resolve from agent.json (latest sync from troop) on
4369
- // every new thread, so owner edits in the troop UI take
4370
- // effect after the next sync without a bridge restart.
4371
- // SOUL.md + skills are merged into the system prompt the same
4372
- // way picked up per-thread without restart.
4373
- tools: resolveTools(this.cfg.tools),
4374
- systemPrompt: composeSystemPrompt({
4375
- base: resolveSystemPrompt(this.cfg.systemPrompt),
4376
- enabledTools: resolveTools(this.cfg.tools)
4377
- }),
4418
+ // Resolve tools + systemPrompt on every turn from agent.json
4419
+ // (latest sync from troop). Owner edits in the troop UI thus
4420
+ // take effect on the very next message in an existing thread —
4421
+ // not just on a freshly-opened one. SOUL.md + skills get merged
4422
+ // into the system prompt the same way.
4423
+ resolveConfig: () => {
4424
+ const tools = resolveTools(this.cfg.tools);
4425
+ return {
4426
+ tools,
4427
+ systemPrompt: composeSystemPrompt({
4428
+ base: resolveSystemPrompt(this.cfg.systemPrompt),
4429
+ enabledTools: tools
4430
+ })
4431
+ };
4432
+ },
4433
+ selfEmail: this.selfEmail,
4378
4434
  maxSteps: this.cfg.maxSteps,
4379
4435
  log
4380
4436
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/ape-agent",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
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",