@code-yeongyu/senpi-agent-core 2026.10.21-2 → 2026.10.21-3

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.
@@ -79,6 +79,9 @@ export const DEFAULT_COMPACTION_SETTINGS = {
79
79
  };
80
80
  /** Calculate total context tokens from provider usage. */
81
81
  export function calculateContextTokens(usage) {
82
+ if (usage.contextUsage !== undefined) {
83
+ return usage.contextUsage === null ? 0 : usage.contextUsage.input + usage.contextUsage.output;
84
+ }
82
85
  return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
83
86
  }
84
87
  function getAssistantUsage(msg) {
@@ -96,7 +96,11 @@ export declare class Harness<Ks extends readonly AnyKind[] = []> {
96
96
  private reconcileOrphans;
97
97
  quiescent(): boolean;
98
98
  hold(): () => void;
99
- /** Cancel and join in-process invocations, clear transient waits, then close without terminalizing tasks. */
99
+ /**
100
+ * Cancel and join in-process invocations, clear transient waits, then close without terminalizing tasks.
101
+ * The views and the session are closed even when joining or the waiting-state commit fails (a storage
102
+ * that refuses writes), so a failed suspend never leaks the session files; the first error is rethrown.
103
+ */
100
104
  suspend(ctx: Context): Promise<void>;
101
105
  registerTaskKind<K extends AnyKind>(kind: K): () => void;
102
106
  namespace<T extends JsonObject>(id: string, defaults: NamespaceDefaults<T>, opts?: {
@@ -266,26 +266,43 @@ export class Harness {
266
266
  throw new Error("cannot hold a non-quiescent harness; suspend it instead");
267
267
  return this.scheduler.hold();
268
268
  }
269
- /** Cancel and join in-process invocations, clear transient waits, then close without terminalizing tasks. */
269
+ /**
270
+ * Cancel and join in-process invocations, clear transient waits, then close without terminalizing tasks.
271
+ * The views and the session are closed even when joining or the waiting-state commit fails (a storage
272
+ * that refuses writes), so a failed suspend never leaks the session files; the first error is rethrown.
273
+ */
270
274
  async suspend(ctx) {
271
275
  if (this.suspended)
272
276
  return;
273
277
  this.suspended = true;
274
- await this.scheduler.joinAll();
275
- const tools = [...this.session.liveTasks.values()].filter((task) => task.kind === "pi.tool");
276
- if (tools.length > 0) {
277
- const docs = [...new Set(tools.map((task) => task.conversationId))].map((conversationId) => ({ doc: "sticky", conversationId }));
278
- await this.session.commit(KERNEL, (tx) => {
279
- for (const task of tools) {
280
- const index = task.input.index;
281
- const slot = tx.sticky(task.conversationId).turn.tools[index];
282
- if (slot?.waitingOn !== undefined)
283
- delete slot.waitingOn;
284
- }
285
- }, ctx, { docs });
278
+ let failure;
279
+ try {
280
+ await this.scheduler.joinAll();
281
+ const tools = [...this.session.liveTasks.values()].filter((task) => task.kind === "pi.tool");
282
+ if (tools.length > 0) {
283
+ const docs = [...new Set(tools.map((task) => task.conversationId))].map((conversationId) => ({ doc: "sticky", conversationId }));
284
+ await this.session.commit(KERNEL, (tx) => {
285
+ for (const task of tools) {
286
+ const index = task.input.index;
287
+ const slot = tx.sticky(task.conversationId).turn.tools[index];
288
+ if (slot?.waitingOn !== undefined)
289
+ delete slot.waitingOn;
290
+ }
291
+ }, ctx, { docs });
292
+ }
293
+ }
294
+ catch (error) {
295
+ failure = { error };
286
296
  }
287
297
  this.views.close();
288
- await this.session.close(ctx);
298
+ try {
299
+ await this.session.close(ctx);
300
+ }
301
+ catch (error) {
302
+ failure ??= { error };
303
+ }
304
+ if (failure !== undefined)
305
+ throw failure.error;
289
306
  }
290
307
  // --- registries --------------------------------------------------------------
291
308
  registerTaskKind(kind) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-agent-core",
3
- "version": "2026.10.21-2",
3
+ "version": "2026.10.21-3",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -62,8 +62,8 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@earendil-works/chord": "0.99.1",
65
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.21-2",
66
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.21-2",
65
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.21-3",
66
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.21-3",
67
67
  "diff": "9.0.0",
68
68
  "ignore": "7.0.9",
69
69
  "typebox": "1.3.34",