@springbrand/agent-runtime 0.2.0-alpha.52 → 0.2.0-alpha.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.2.0-alpha.52",
3
+ "version": "0.2.0-alpha.54",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -20,7 +20,7 @@
20
20
  "@cloudflare/codemode": "0.5.1",
21
21
  "@cloudflare/sandbox": "0.12.4",
22
22
  "@cloudflare/shell": "0.4.3",
23
- "@cloudflare/think": "0.15.1",
23
+ "@cloudflare/think": "0.17.0",
24
24
  "@earendil-works/pi-agent-core": "0.83.0",
25
25
  "@earendil-works/pi-ai": "0.83.0",
26
26
  "@types/lodash-es": "^4.17.12",
@@ -59,6 +59,7 @@ export interface RuntimeQueuedSubmission {
59
59
  export interface RuntimeTurnState {
60
60
  activeSubmissionId?: string;
61
61
  activeRequestId?: string;
62
+ activeMessageId?: string;
62
63
  recoveryAttempt?: number;
63
64
  recoveryMax?: number;
64
65
  recoveryReason?: "no_meaningful_model_progress" | "transient_model_error";
package/src/lib/prompt.ts CHANGED
@@ -113,9 +113,9 @@ export const INTERACTION =
113
113
  "a bounded choice; for required free text, omit the options field entirely — never pass an empty or single-item options array; " +
114
114
  "continue the same turn when its result arrives. " +
115
115
  "Don't use it when a sensible default lets you proceed. " +
116
- "After completing a substantive task (report, analysis, multi-step job): write your full answer " +
117
- "FIRST, then call suggest_followups (2-4 directions) as the very last action and end the turn " +
118
- "write no text after it (the tool renders its own closing block). Never for small talk or while a " +
116
+ "After completing a substantive task (report, analysis, multi-step job), finish all work and other Tool calls, " +
117
+ "then call suggest_followups (2-4 directions) as the last Tool call before your final answer. " +
118
+ "After it returns, write your complete final answer and make no further Tool calls. Never for small talk or while a " +
119
119
  "task is still in progress; it is non-blocking, unlike ask_user.";
120
120
 
121
121
  // Separates short always-on memory from indexed long-tail workspace files.
@@ -300,7 +300,7 @@ export function basePiToolCandidates(
300
300
  name: "suggest_followups",
301
301
  label: "Suggest follow-ups",
302
302
  description:
303
- "Offer the user 2-4 optional follow-up directions after completing a substantive task (a report, an analysis, a multi-step job). Call this at most once, and it MUST be the very last thing you do: finish all of your prose FIRST, then call this tool and END the turn immediately do NOT write any text after calling it. Do NOT call it for small talk, quick answers, or while a task is still in progress.",
303
+ "Offer the user 2-4 optional follow-up directions after completing a substantive task (a report, an analysis, a multi-step job). Call this at most once, after all work and other Tool calls are complete. It MUST be the last Tool call before the final answer. After it returns, write the complete final answer and make no further Tool calls. Do NOT call it for small talk, quick answers, or while a task is still in progress.",
304
304
  parameters: suggestFollowupsParameters,
305
305
  async execute(_toolCallId, input) {
306
306
  return result({ noted: true, count: input.items.length });
@@ -80,13 +80,15 @@ export function workspaceStateBackend(
80
80
 
81
81
  const SPRINGBRAND_CODE_MODE_POLICY = `## SpringBrand Workspace and Asset Policy
82
82
 
83
+ Every execute script must explicitly return the data the model needs. Never rely on console.log: console output is deliberately not returned to the model.
84
+
83
85
  Use state.* for every Workspace filesystem operation. Workspace file methods do not exist under tools.*.
84
86
 
85
87
  state.writeFileBytes is not available. Use state.writeFile or state.appendFile for sandbox-created text.
86
88
 
87
89
  Bundled Skill resources are not Workspace files. Read text with tools.read_skill_resource, run bundled scripts with tools.run_skill_script, and copy templates or assets with tools.materialize_skill_resource.
88
90
 
89
- User Attachments are not Workspace files until copied with tools.materialize_attachment.
91
+ User Attachments are not Workspace files until copied with tools.copy_attachment_to_workspace({ id }).
90
92
 
91
93
  Never create or write data URLs, base64 image strings, or embedded binary payloads in Code Mode.
92
94
 
package/src/runtime.ts CHANGED
@@ -3424,15 +3424,15 @@ export abstract class AgentRuntimeKernel<
3424
3424
  * @remarks
3425
3425
  * Cloudflare Agents SDK 为每条新 WebSocket 连接调用。
3426
3426
  *
3427
- * 先发送 transcript,再让恢复基类通知正在续传的流,
3428
- * 使普通历史和恢复握手保持各自的协议帧。
3427
+ * 先发送已完成 transcript,再让恢复基类独自重建 active assistant,
3428
+ * 使持久化历史和 resumable stream 只有一个生产者。
3429
3429
  */
3430
3430
  async onConnect(connection: Connection): Promise<void> {
3431
3431
  sendIfOpen(
3432
3432
  connection,
3433
3433
  json({
3434
3434
  type: MessageType.CF_AGENT_CHAT_MESSAGES,
3435
- messages: await this.getMessages(),
3435
+ messages: await this.hydrationMessages(),
3436
3436
  }),
3437
3437
  );
3438
3438
  await super.onConnect(connection);
@@ -3448,8 +3448,13 @@ export abstract class AgentRuntimeKernel<
3448
3448
  * 避免在这里复制 Agents SDK 的默认协议行为。
3449
3449
  */
3450
3450
  async onRequest(request: Request): Promise<Response> {
3451
- if (new URL(request.url).pathname.endsWith("/get-messages")) {
3452
- return Response.json(await this.getMessages());
3451
+ const url = new URL(request.url);
3452
+ if (url.pathname.endsWith("/get-messages")) {
3453
+ return Response.json(
3454
+ url.searchParams.get("view") === "hydration"
3455
+ ? await this.hydrationMessages()
3456
+ : await this.getMessages(),
3457
+ );
3453
3458
  }
3454
3459
  return super.onRequest(request);
3455
3460
  }
@@ -3590,12 +3595,20 @@ export abstract class AgentRuntimeKernel<
3590
3595
  * @remarks
3591
3596
  * 新连接、HTTP 读取、Turn 完成投影和 RPC 调用方需要消息快照时调用。
3592
3597
  *
3593
- * 统一从 transcript 生成完整视图;活跃 assistant 也必须随首帧恢复,后续流按相同消息 ID 接管。
3598
+ * 统一从 transcript 生成完整视图;HTTP fallback、fork RPC 读取保留 active assistant。
3594
3599
  */
3595
3600
  async getMessages(): Promise<UIMessage[]> {
3596
3601
  return this.transcript.browserMessages();
3597
3602
  }
3598
3603
 
3604
+ private async hydrationMessages(): Promise<UIMessage[]> {
3605
+ const messages = await this.getMessages();
3606
+ const running = this.db.submissions.findRunning();
3607
+ return running?.assistantMessageId
3608
+ ? messages.filter(({ id }) => id !== running.assistantMessageId)
3609
+ : messages;
3610
+ }
3611
+
3599
3612
  /** 读取当前 canonical transcript 中最后一条助手文本。 */
3600
3613
  protected async latestAssistantText(): Promise<string | undefined> {
3601
3614
  const message = [...await this.transcript.canonicalMessages()]
@@ -4633,6 +4646,7 @@ export abstract class AgentRuntimeKernel<
4633
4646
  ...(current
4634
4647
  ? {
4635
4648
  activeSubmissionId: current.submissionId,
4649
+ activeMessageId: current.assistantMessageId,
4636
4650
  ...(current.requestId
4637
4651
  ? { activeRequestId: current.requestId }
4638
4652
  : {}),