@rien7/agent-center-host 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -184,6 +184,13 @@ plus the runtime's file tools for memory (pi `read/edit/write`; Claude Code
184
184
  run again are kept in `<workdir>/resumed-turns.json`).
185
185
  - Core runs one turn per agent at a time, so a second message waits for the first (the
186
186
  driver also handles oar's steer/queue landings for when that changes).
187
+ - **Inactive history.** When the session compacts (oar `compaction_ended`, completed),
188
+ the host calls `agent.history.mark_inactive` with `beforeTurnId` (the tool calls of every
189
+ earlier turn of the agent) and the calls the current turn made so far; the messages stay
190
+ (the summary keeps them). When oar drops an accepted input (`input_dropped`; oar 0.86
191
+ has no other rollback record), that turn fails and its user message, reply and tool calls
192
+ are marked inactive. The mark waits for the turn's queued events to reach Center.
193
+ `search.query` and `message.list` leave inactive records out unless `includeInactive`.
187
194
 
188
195
  `Ctrl-C` flushes queued events, marks the host's agents offline and leaves any running
189
196
  turn running. The next `run` resumes it.
@@ -293,6 +300,8 @@ bun test packages/agent-host # core in-process over real HTTP + the host loo
293
300
  # oar-driver.test.ts: pi + the mock model, end to end
294
301
  # subagents.test.ts: issued tokens, memory notes,
295
302
  # sub-agents, presence, interrupted turns
303
+ # inactive.test.ts: compaction / dropped input →
304
+ # inactive history (fake oar session)
296
305
  bun x tsc -p packages/agent-host # typecheck against Node types
297
306
  ```
298
307
 
@@ -63,6 +63,9 @@ export class HttpCoreClient {
63
63
  openTurn(input) {
64
64
  return this.op("turn.open", input, `open-${input.id}`);
65
65
  }
66
+ markInactive(input) {
67
+ return this.op("history.mark_inactive", input);
68
+ }
66
69
  async claim(input) {
67
70
  return (await this.op("turn.claim", input)).items;
68
71
  }
package/dist/host.js CHANGED
@@ -397,6 +397,7 @@ export class AgentHost {
397
397
  terminal = true;
398
398
  batcher.push(event);
399
399
  },
400
+ flushed: () => batcher.flush(),
400
401
  };
401
402
  active.settled = (async () => {
402
403
  try {
@@ -90,6 +90,10 @@ function textSink(sink, holdMs = 100) {
90
90
  flush();
91
91
  sink.emit(event);
92
92
  },
93
+ flushed() {
94
+ flush();
95
+ return sink.flushed?.() ?? Promise.resolve();
96
+ },
93
97
  };
94
98
  }
95
99
  const clip = (text, max) => {
@@ -146,6 +150,12 @@ class AgentRun {
146
150
  pending = [];
147
151
  /** The owner of the oar turn that ended last (its report follows). */
148
152
  lastEnded = null;
153
+ /** The owner the runtime's events went to last (a compaction while idle covers it). */
154
+ lastOwner = null;
155
+ /** Core turns this run delivered (input ids), newest last; bounded. */
156
+ turnIds = new Set();
157
+ /** history.mark_inactive calls in flight, in order. */
158
+ marking = Promise.resolve();
149
159
  /** Its conversation with its parent. */
150
160
  conversationId = null;
151
161
  // A parent:
@@ -422,7 +432,11 @@ export class OarDriver {
422
432
  finished: false,
423
433
  chain: Promise.resolve(),
424
434
  resolve: () => { },
435
+ calls: [],
425
436
  };
437
+ run.turnIds.add(owner.turnId);
438
+ if (run.turnIds.size > 1000)
439
+ run.turnIds.delete(run.turnIds.values().next().value);
426
440
  const ended = new Promise((resolve) => {
427
441
  owner.resolve = resolve;
428
442
  });
@@ -1130,11 +1144,82 @@ export class OarDriver {
1130
1144
  };
1131
1145
  }
1132
1146
  call.announced = owner;
1147
+ if (!owner.calls.includes(id))
1148
+ owner.calls.push(id);
1133
1149
  this.emit(owner, event);
1134
1150
  }
1151
+ /**
1152
+ * Tell Center which history the runtime no longer has, once the events it names were
1153
+ * appended (behind `owner`'s chain and its sink's batches).
1154
+ */
1155
+ markInactive(run, owner, input, why) {
1156
+ const client = this.client;
1157
+ if (!client?.markInactive)
1158
+ return;
1159
+ const ready = owner
1160
+ ? owner.chain.then(() => owner.sink.flushed?.())
1161
+ : Promise.resolve();
1162
+ run.marking = run.marking
1163
+ .then(() => ready)
1164
+ .then(() => client.markInactive?.(input))
1165
+ .then((marked) => {
1166
+ if (marked && (marked.toolCalls || marked.messages))
1167
+ this.log(`${run.agent.name}: ${why}: ${marked.toolCalls} tool calls, ${marked.messages} messages marked inactive`);
1168
+ })
1169
+ .catch((error) => this.log(`${run.agent.name}: marking ${why} history inactive failed (${error.message})`));
1170
+ }
1171
+ /**
1172
+ * oar `compaction_ended` (completed): the runtime replaced its context with a summary.
1173
+ * Every earlier turn's tool calls and the calls of the turn it happened in are gone
1174
+ * from it.
1175
+ */
1176
+ compacted(run, live) {
1177
+ const owner = live.owners[0] ?? run.lastOwner;
1178
+ if (!owner) {
1179
+ this.log(`${run.agent.name}: the session compacted before any turn here; nothing to mark`);
1180
+ return;
1181
+ }
1182
+ this.markInactive(run, owner, {
1183
+ agentId: run.agent.id,
1184
+ beforeTurnId: owner.turnId,
1185
+ toolCalls: owner.calls.map((callId) => ({
1186
+ turnId: owner.turnId,
1187
+ callId,
1188
+ })),
1189
+ }, "compaction");
1190
+ }
1191
+ /**
1192
+ * oar `input_dropped`: the runtime discarded an input it had accepted, so that turn's
1193
+ * exchange is not in the session's history (oar 0.86 has no rollback record). Its core
1194
+ * turn fails and its messages and tool calls become inactive.
1195
+ */
1196
+ rolledBack(run, live, event) {
1197
+ const inputId = event.inputId;
1198
+ if (!run.turnIds.has(inputId))
1199
+ return;
1200
+ const waiting = live.owners.find((o) => o.turnId === inputId) ?? null;
1201
+ const owner = waiting ?? (run.lastOwner?.turnId === inputId ? run.lastOwner : null);
1202
+ if (waiting)
1203
+ this.finish(live.owners, waiting, {
1204
+ type: "failed",
1205
+ reason: `${run.runtimeId} 丢弃了这条消息${event.reason === "runtime_refused" && event.message
1206
+ ? `:${event.message}`
1207
+ : ""}`,
1208
+ });
1209
+ this.markInactive(run, owner, { agentId: run.agent.id, turnIds: [inputId] }, "rollback");
1210
+ }
1135
1211
  onEvent(run, live, event) {
1136
1212
  if (event.agentPath.length)
1137
1213
  return; // the runtime's own sub-agents (Task is off)
1214
+ if (event.kind === "compaction_ended") {
1215
+ if (event.outcome === "completed")
1216
+ this.compacted(run, live);
1217
+ return;
1218
+ }
1219
+ if (event.kind === "input_dropped") {
1220
+ this.rolledBack(run, live, event);
1221
+ return;
1222
+ }
1138
1223
  if (event.kind === "exited") {
1139
1224
  for (const owner of [...live.owners])
1140
1225
  this.finish(live.owners, owner, {
@@ -1158,6 +1243,7 @@ export class OarDriver {
1158
1243
  const owner = live.owners[0];
1159
1244
  if (!owner)
1160
1245
  return;
1246
+ run.lastOwner = owner;
1161
1247
  switch (event.kind) {
1162
1248
  case "text_delta":
1163
1249
  if (event.text)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rien7/agent-center-host",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "description": "Agent host for Agent Center: claims queued turns and answers them through a driver (an oar runtime: Claude Code, pi; or echo). Node 24+.",
6
6
  "keywords": [