@springbrand/agent-runtime 0.3.0-alpha.20 → 0.3.0-alpha.22

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.
@@ -46,8 +46,6 @@ export interface StoredSubmission {
46
46
  error: string | null;
47
47
  createdAt: number;
48
48
  completedAt: number | null;
49
- assemblyRevision: string;
50
- assemblyDescriptor: string;
51
49
  runtimeKey: string;
52
50
  assistantMessageId: string;
53
51
  abortReason: string | null;
@@ -63,6 +61,17 @@ export interface StoredSubmission {
63
61
  retryable?: boolean;
64
62
  }
65
63
 
64
+ /** Saved key and whether its Submission can still execute. */
65
+ export interface StoredRuntimeKey {
66
+ readonly runtimeKey: string;
67
+ readonly unfinished: boolean;
68
+ }
69
+
70
+ export interface RuntimeKeyRewrite {
71
+ readonly from: string;
72
+ readonly to: string;
73
+ }
74
+
66
75
  /** Reuse durable stop intent when transferring work out of the source Submission. */
67
76
  export function submissionAbortReason(submission: StoredSubmission | null): string | undefined {
68
77
  return submission?.abortReason ||
@@ -74,8 +83,6 @@ export interface NewSubmission {
74
83
  requestId: string;
75
84
  idempotencyKey: string | null;
76
85
  createdAt: number;
77
- assemblyRevision: string;
78
- assemblyDescriptor: string;
79
86
  runtimeKey?: string;
80
87
  assistantMessageId: string;
81
88
  queuedInputJson?: string | null;
@@ -97,8 +104,6 @@ type SubmissionRow = {
97
104
  error: string | null;
98
105
  created_at: number;
99
106
  completed_at: number | null;
100
- assembly_revision: string;
101
- assembly_descriptor: string;
102
107
  runtime_key: string;
103
108
  assistant_message_id: string;
104
109
  abort_reason: string | null;
@@ -129,8 +134,6 @@ function mapRow(row: SubmissionRow): StoredSubmission {
129
134
  error: row.error,
130
135
  createdAt: row.created_at,
131
136
  completedAt: row.completed_at,
132
- assemblyRevision: row.assembly_revision,
133
- assemblyDescriptor: row.assembly_descriptor,
134
137
  runtimeKey: row.runtime_key,
135
138
  assistantMessageId: row.assistant_message_id,
136
139
  abortReason: row.abort_reason,
@@ -166,14 +169,36 @@ export class SubmissionRepository {
166
169
  return this.transact(run);
167
170
  }
168
171
 
172
+ /** Return saved identities for a one-time Host-owned key migration. */
173
+ listRuntimeKeys(): StoredRuntimeKey[] {
174
+ return this.sql<{ runtime_key: string; unfinished: number }>`
175
+ SELECT runtime_key,
176
+ MAX(CASE WHEN status IN ('pending', 'running') THEN 1 ELSE 0 END) AS unfinished
177
+ FROM pi_submissions
178
+ WHERE runtime_key <> ''
179
+ GROUP BY runtime_key
180
+ ORDER BY runtime_key
181
+ `.map((row) => ({ runtimeKey: row.runtime_key, unfinished: row.unfinished === 1 }));
182
+ }
183
+
184
+ /** Rewrite each saved identity atomically after the Host validates active work. */
185
+ rewriteRuntimeKeys(rewrites: readonly RuntimeKeyRewrite[]): void {
186
+ this.transact(() => {
187
+ for (const { from, to } of rewrites) {
188
+ if (from === to) continue;
189
+ this.sql`UPDATE pi_submissions SET runtime_key = ${to} WHERE runtime_key = ${from}`;
190
+ }
191
+ });
192
+ }
193
+
169
194
  // 按 submissionId 读取一条完整 Submission,不存在时返回 null。
170
195
  // Runtime 和 SubmissionLifecycle 在准入、执行、取消、结束与恢复时调用,调用方必须处理 null。
171
196
  // 所有列在这里一次投影并统一映射,不能让不同调用方各自拼出不完整的 Submission。
172
197
  find(id: string): StoredSubmission | null {
173
198
  const row = this.sql<SubmissionRow>`
174
199
  SELECT submission_id, request_id, idempotency_key, status, accepted,
175
- error, created_at, completed_at, assembly_revision,
176
- assembly_descriptor, runtime_key, assistant_message_id, abort_reason,
200
+ error, created_at, completed_at,
201
+ runtime_key, assistant_message_id, abort_reason,
177
202
  queued_input_json, queued_ui_message_json, user_message_id,
178
203
  regenerate_message_id,
179
204
  run_id,
@@ -279,7 +304,7 @@ export class SubmissionRepository {
279
304
  this.sql`
280
305
  INSERT INTO pi_submissions (
281
306
  submission_id, request_id, idempotency_key, status, accepted,
282
- created_at, assembly_revision, assembly_descriptor, runtime_key,
307
+ created_at, runtime_key,
283
308
  assistant_message_id, queued_input_json, queued_ui_message_json,
284
309
  user_message_id, regenerate_message_id,
285
310
  run_id, origin, scheduled_max_execution_level, unattended_execution_grant_json
@@ -290,8 +315,6 @@ export class SubmissionRepository {
290
315
  'pending',
291
316
  1,
292
317
  ${s.createdAt},
293
- ${s.assemblyRevision},
294
- ${s.assemblyDescriptor},
295
318
  ${s.runtimeKey ?? ""},
296
319
  ${s.assistantMessageId},
297
320
  ${s.queuedInputJson ?? null},
@@ -336,31 +359,17 @@ export class SubmissionRepository {
336
359
  this.sql`
337
360
  INSERT INTO pi_submissions (
338
361
  submission_id, request_id, idempotency_key, status, accepted,
339
- error, created_at, completed_at, assembly_revision,
340
- assembly_descriptor, runtime_key, assistant_message_id, abort_reason,
362
+ error, created_at, completed_at,
363
+ runtime_key, assistant_message_id, abort_reason,
341
364
  run_id, admission_retryable, user_message_id
342
365
  ) VALUES (
343
366
  ${s.submissionId}, ${s.requestId}, ${s.idempotencyKey}, 'skipped', 0,
344
- ${s.code}, ${s.createdAt}, ${s.createdAt}, '', '', '', ${s.assistantMessageId ?? s.submissionId}, NULL,
367
+ ${s.code}, ${s.createdAt}, ${s.createdAt}, '', ${s.assistantMessageId ?? s.submissionId}, NULL,
345
368
  ${s.submissionId}, ${s.retryable ? 1 : 0}, ${s.userMessageId ?? null}
346
369
  )
347
370
  `;
348
371
  }
349
372
 
350
- pinAssembly(id: string, revision: string, descriptor: string): boolean {
351
- const updated = this.sql<{ submission_id: string }>`
352
- UPDATE pi_submissions
353
- SET assembly_revision = ${revision},
354
- assembly_descriptor = ${descriptor}
355
- WHERE submission_id = ${id}
356
- AND assembly_revision = ''
357
- AND assembly_descriptor = ''
358
- AND status IN ('pending', 'running')
359
- RETURNING submission_id
360
- `;
361
- return updated.length === 1;
362
- }
363
-
364
373
  recordUnattendedFailure(id: string, reason: string): void {
365
374
  this.sql`
366
375
  UPDATE pi_submissions
@@ -56,7 +56,6 @@ export interface TelemetryPayloadByType {
56
56
  readonly idempotencyKey?: string;
57
57
  };
58
58
  readonly "turn.started": PayloadBase & {
59
- readonly assemblyRevision?: string;
60
59
  readonly input?: TelemetryValue;
61
60
  };
62
61
  readonly "turn.steered": PayloadBase & {
@@ -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,
@@ -71,7 +71,6 @@ export class RuntimeTelemetry {
71
71
 
72
72
  turnStarted(input: Readonly<{
73
73
  submissionId: string;
74
- assemblyRevision?: string;
75
74
  input?: unknown;
76
75
  occurredAt?: number;
77
76
  }>): TelemetryRecordResult {
@@ -80,9 +79,6 @@ export class RuntimeTelemetry {
80
79
  input.submissionId,
81
80
  );
82
81
  return this.record("turn.started", operationId, {
83
- ...(input.assemblyRevision
84
- ? { assemblyRevision: input.assemblyRevision }
85
- : {}),
86
82
  ...(input.input === undefined ? {} : { input: input.input as never }),
87
83
  }, input);
88
84
  }
@@ -3,6 +3,8 @@ import { modelAttachmentText } from "../../conversation/message/conversion";
3
3
  import type { PiToolCandidate } from "../runtime/contracts";
4
4
  import { candidate } from "./candidate";
5
5
 
6
+ export const ASK_USER_TOOL_NAME = "ask_user";
7
+
6
8
  const askUserResponseTypes = [
7
9
  "text",
8
10
  "single_select",
@@ -240,7 +242,7 @@ function isAskUserResponse(input: unknown, value: unknown): boolean {
240
242
  export function askUserPiToolCandidate(): PiToolCandidate {
241
243
  return {
242
244
  ...candidate({
243
- name: "ask_user",
245
+ name: ASK_USER_TOOL_NAME,
244
246
  label: "Ask user",
245
247
  description:
246
248
  "Ask all 1-10 user questions needed to continue in one call. Required input for every question: question and responseType; required defaults to false, so set it to true only when progress cannot continue without an answer. Use responseType text for free text, single_select or multi_select with 2-6 distinct options for choices, and attachment for files. At most one attachment question is allowed and it accepts multiple files. Do not add file-specific fields such as accept or tell the user to use the normal chat composer. The answers return as this tool's result, so continue the same turn after calling it.",
@@ -12,7 +12,7 @@ const noParameters = Type.Object({}, { additionalProperties: false });
12
12
  *
13
13
  * Runtime 组装层在 Extension 装载完成后调用,模型需要发现当前可用扩展时使用该工具。
14
14
  *
15
- * 候选项捕获准备阶段的只读列表,这与 pinned Runtime 组装保持一致,不能在执行时重新扫描出另一份 Extension 集合。
15
+ * 候选项捕获当前配置准备阶段的只读列表,执行时不重新扫描 Extension 集合。
16
16
  */
17
17
  export function listExtensionsPiToolCandidate(
18
18
  extensions: readonly PiLoadedExtension[],
@@ -22,7 +22,7 @@ export function listExtensionsPiToolCandidate(
22
22
  label: "List extensions",
23
23
  description: "List all currently loaded extensions and their tools.",
24
24
  parameters: noParameters,
25
- // 返回这个 pinned Runtime 已加载的 Extension 快照。
25
+ // 返回当前 Runtime 已加载的 Extension 快照。
26
26
  // Pi 工具循环在模型选择 `list_extensions` 时调用,调用前允许 Turn 取消。
27
27
  // 同时返回 count 和原列表,使模型既能快速判空又不丢失 Extension 细节。
28
28
  async execute(_toolCallId, _input, signal) {
@@ -1115,7 +1115,7 @@ export function applyCitations(text: string, groundingMetadata: any): { text: st
1115
1115
  const PROVIDER_ERROR_MAX_LENGTH = 2_048;
1116
1116
  const NATIVE_TIMEOUT_MS = 60_000;
1117
1117
  const OPENROUTER_MAX_OUTPUT_TOKENS = 4_096;
1118
- export const WEB_SEARCH_MODEL = "deepseek/deepseek-v4-flash";
1118
+ export const WEB_SEARCH_MODEL = "deepseek/deepseek-v4.1-flash";
1119
1119
 
1120
1120
  export interface WebSearchOptions {
1121
1121
  endpoint: RuntimeModelEndpoint;