@springbrand/agent-runtime 0.3.0-alpha.21 → 0.3.0-alpha.23

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/src/index.ts CHANGED
@@ -51,6 +51,7 @@ export type { RuntimeAssemblyView } from "./config/assembly-view";
51
51
  export * from "./adapter/cloudflare/define-agent";
52
52
  export type { ToolInteractionResponsePreparation } from "./adapter/cloudflare/agent";
53
53
  export * from "./conversation/state";
54
+ export type { ModelProtocolResponse } from "./conversation/canonical-context";
54
55
  export * from "./tools/workspace/versioning";
55
56
  export * from "./lib/execution-level";
56
57
  export type {
@@ -150,6 +151,5 @@ export {
150
151
  } from "./model-protocol/openrouter";
151
152
  export type { OpenRouterRequestOptions } from "./model-protocol/openrouter";
152
153
  export type {
153
- ModelProtocolResponse,
154
154
  OpenRouterProtocol,
155
155
  } from "./models/openrouter/protocol-response";
@@ -9,20 +9,10 @@ import {
9
9
  } from "@earendil-works/pi-ai";
10
10
  import type { RuntimeModelProtocol } from "../../config/ports";
11
11
  import { overlayProviderStreams } from "../provider-streams";
12
+ import type { ModelProtocolResponse } from "../../conversation/canonical-context";
12
13
 
13
14
  export type OpenRouterProtocol = Extract<RuntimeModelProtocol, `openrouter-${string}`>;
14
15
 
15
- /** Native output required by OpenRouter's stateless continuation. */
16
- export interface ModelProtocolResponse {
17
- readonly output: readonly Readonly<Record<string, unknown>>[];
18
- }
19
-
20
- declare module "@earendil-works/pi-ai" {
21
- interface AssistantMessage {
22
- protocolResponse?: ModelProtocolResponse;
23
- }
24
- }
25
-
26
16
  function record(value: unknown): Record<string, unknown> | undefined {
27
17
  return value !== null && typeof value === "object" && !Array.isArray(value)
28
18
  ? value as Record<string, unknown>
@@ -597,7 +597,16 @@ export class SubmissionLifecycle<
597
597
  activate(submission: TSubmission, active: TActive): () => void {
598
598
  this.activeBySubmission.set(submission.submissionId, active);
599
599
  const latest = this.options.store.find(submission.submissionId);
600
- if (latest?.abortReason) this.options.abortActive(active);
600
+ if (latest?.abortReason) {
601
+ // The cancel is already committed; this abort only stops the executor and may lose to it settling.
602
+ void (async () => this.options.abortActive(active))().catch((error: unknown) => {
603
+ console.warn(
604
+ "[runtime-early-abort:failed]",
605
+ submission.submissionId,
606
+ error instanceof Error ? error.message : String(error),
607
+ );
608
+ });
609
+ }
601
610
  return () => {
602
611
  if (this.activeBySubmission.get(submission.submissionId) === active) {
603
612
  this.activeBySubmission.delete(submission.submissionId);
@@ -6,6 +6,7 @@ import type { SubmitMessageOptions } from "./admission";
6
6
  import type { SubmissionHandle } from "./lifecycle";
7
7
  import { submissionAbortReason, type StoredSubmission, type SubmissionRepository } from "./repository";
8
8
  import type { PiCanonicalUserInput } from "../conversation/message/contract";
9
+ import { captureUIUserSidecar } from "../conversation/message/conversion";
9
10
  import type { MessageUiRepository } from "../conversation/repository";
10
11
  import type { PendingSteer, SteerRepository } from "./steer-repository";
11
12
 
@@ -70,6 +71,7 @@ export async function finishPendingSteers(options: {
70
71
  const userMessage = steer.uiMessageJson
71
72
  ? JSON.parse(steer.uiMessageJson) as UIMessage & { role: "user" }
72
73
  : undefined;
74
+ const userSidecar = userMessage ? { id: userMessage.id, ...captureUIUserSidecar(userMessage) } : undefined;
73
75
  const requestId = `${options.submissionId}:steer:${steer.steerId}`;
74
76
  if (options.aborted) {
75
77
  const owner = options.transaction(() => {
@@ -81,7 +83,7 @@ export async function finishPendingSteers(options: {
81
83
  }
82
84
  return handoff?.status === "aborted" || handoff?.status === "skipped" ? handoff.submissionId : options.submissionId;
83
85
  });
84
- await options.conversation.append(steer.messageId, message, { submissionId: owner, ...(userMessage ? { userMessage } : {}) });
86
+ await options.conversation.append(steer.messageId, message, { submissionId: owner, ...(userSidecar ? { userSidecar } : {}) });
85
87
  options.transaction(() => {
86
88
  if (steer.harnessEntryId) options.messageUi.delete(steer.harnessEntryId);
87
89
  options.steers.deleteByMessageId(steer.messageId);
@@ -117,7 +119,7 @@ export async function finishPendingSteers(options: {
117
119
  }
118
120
  return current;
119
121
  });
120
- if (handoff.status === "skipped" || handoff.status === "aborted") await options.conversation.append(steer.messageId, message, { submissionId: handoff.submissionId, ...(userMessage ? { userMessage } : {}) });
122
+ if (handoff.status === "skipped" || handoff.status === "aborted") await options.conversation.append(steer.messageId, message, { submissionId: handoff.submissionId, ...(userSidecar ? { userSidecar } : {}) });
121
123
  options.transaction(() => options.steers.deleteByMessageId(steer.messageId));
122
124
  }
123
125
  }
@@ -53,6 +53,7 @@ export class RuntimeTelemetryCoordinator {
53
53
  private delivery?: TelemetryDelivery;
54
54
  private configured = false;
55
55
  private draining?: Promise<void>;
56
+ private drainRequested = false;
56
57
  private readonly coldBuffer: BufferedTelemetryFact[] = [];
57
58
 
58
59
  constructor(
@@ -182,11 +183,19 @@ export class RuntimeTelemetryCoordinator {
182
183
  }
183
184
 
184
185
  private queueDrain(): void {
185
- if (!this.delivery || this.draining) return;
186
+ if (!this.delivery) return;
187
+ // A drain reads its batch before publishing; facts written after that wait for the next one.
188
+ if (this.draining) {
189
+ this.drainRequested = true;
190
+ return;
191
+ }
192
+ this.drainRequested = false;
186
193
  const pending = this.drain();
187
194
  let tracked: Promise<void>;
188
195
  tracked = pending.finally(() => {
189
- if (this.draining === tracked) this.draining = undefined;
196
+ if (this.draining !== tracked) return;
197
+ this.draining = undefined;
198
+ if (this.drainRequested) this.queueDrain();
190
199
  });
191
200
  this.draining = tracked;
192
201
  this.options.waitUntil(tracked);
@@ -196,7 +205,8 @@ export class RuntimeTelemetryCoordinator {
196
205
  if (!this.delivery) return;
197
206
  try {
198
207
  const result = await this.delivery.drain();
199
- if (!result.hasPending) return;
208
+ // A queued follow-up drain schedules its own retry if it still leaves work.
209
+ if (!result.hasPending || this.drainRequested) return;
200
210
  await this.options.scheduleRetry(
201
211
  TELEMETRY_RETRY_DELAY_MS / 1_000,
202
212
  idempotentRetry,