@standardagents/builder 0.39.0 → 0.39.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/dist/index.js CHANGED
@@ -1498,6 +1498,9 @@ function normalizeToolCallMessageSequence(messages) {
1498
1498
  for (const message of messages) {
1499
1499
  const isAssistantToolCallMessage = message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1500
1500
  if (!pending) {
1501
+ if (message.role === "tool") {
1502
+ continue;
1503
+ }
1501
1504
  if (!isAssistantToolCallMessage) {
1502
1505
  normalized.push(message);
1503
1506
  continue;
@@ -7644,28 +7647,12 @@ This usually points to something I can't fix from here \u2014 for example the wo
7644
7647
  state.promptPath = [sideAPrompt.name];
7645
7648
  state.pendingImmediateAfterHandoff = true;
7646
7649
  if (userMessageContent || userMessageAttachments) {
7647
- const handoffMessageId = crypto.randomUUID();
7648
- const handoffTimestamp = Date.now() * 1e3;
7649
- const handoffMessage = {
7650
- id: handoffMessageId,
7651
- role: "user",
7652
- content: userMessageContent || "",
7653
- created_at: handoffTimestamp,
7654
- attachments: userMessageAttachments
7655
- };
7656
- await state.storage.sql.exec(
7657
- `INSERT INTO messages (id, role, content, attachments, created_at, prompt) VALUES (?, ?, ?, ?, ?, ?)`,
7658
- handoffMessage.id,
7659
- handoffMessage.role,
7660
- handoffMessage.content,
7661
- handoffMessage.attachments || null,
7662
- handoffMessage.created_at,
7650
+ await this.persistHandoffUserMessage(
7651
+ state,
7652
+ userMessageContent || "",
7653
+ userMessageAttachments,
7663
7654
  sideAPrompt.name
7664
7655
  );
7665
- state.messageHistory.push(handoffMessage);
7666
- if (state.emitMessage) {
7667
- state.emitMessage(handoffMessage);
7668
- }
7669
7656
  }
7670
7657
  state.pendingHandoff = void 0;
7671
7658
  state.emitTelemetry?.({
@@ -7680,6 +7667,37 @@ This usually points to something I can't fix from here \u2014 for example the wo
7680
7667
  throw error;
7681
7668
  }
7682
7669
  }
7670
+ /**
7671
+ * Persist the user message that rides an agent handoff, applying the
7672
+ * create-message lifecycle hooks like every other insertion path.
7673
+ */
7674
+ static async persistHandoffUserMessage(state, content, attachments, promptName) {
7675
+ let handoffMessage = {
7676
+ id: crypto.randomUUID(),
7677
+ role: "user",
7678
+ content,
7679
+ created_at: Date.now() * 1e3,
7680
+ status: "completed",
7681
+ attachments: attachments ?? null
7682
+ };
7683
+ handoffMessage = await this.runBeforeCreateMessageHook(state, handoffMessage);
7684
+ await state.storage.sql.exec(
7685
+ `INSERT INTO messages (id, role, content, attachments, created_at, status, silent, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
7686
+ handoffMessage.id,
7687
+ handoffMessage.role,
7688
+ handoffMessage.content,
7689
+ handoffMessage.attachments || null,
7690
+ handoffMessage.created_at,
7691
+ handoffMessage.status ?? "completed",
7692
+ handoffMessage.silent ? 1 : 0,
7693
+ promptName
7694
+ );
7695
+ state.messageHistory.push(handoffMessage);
7696
+ await this.runAfterCreateMessageHook(state, handoffMessage);
7697
+ if (state.emitMessage) {
7698
+ state.emitMessage(handoffMessage);
7699
+ }
7700
+ }
7683
7701
  static isEnabledEnvFlag(value) {
7684
7702
  if (value === null || value === void 0) return false;
7685
7703
  const normalized = value.trim().toLowerCase();
@@ -8577,7 +8595,16 @@ This usually points to something I can't fix from here \u2014 for example the wo
8577
8595
  role: "system",
8578
8596
  content: systemPromptText
8579
8597
  });
8580
- const completedMessages = state.messageHistory.filter(
8598
+ const historySnapshot = state.messageHistory.filter(
8599
+ (msg) => msg.status !== "pending"
8600
+ );
8601
+ const filteredHistory = await this.runFilterMessagesHook(
8602
+ state.thread.instance,
8603
+ state,
8604
+ historySnapshot
8605
+ );
8606
+ const contextHistory = this.filterOrphanedToolCalls(filteredHistory);
8607
+ const completedMessages = contextHistory.filter(
8581
8608
  (msg) => msg.status !== "pending"
8582
8609
  );
8583
8610
  const recentMessageThreshold = state.prompt.recentImageThreshold ?? 10;
@@ -9715,11 +9742,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9715
9742
  * @param message - Message object to be created
9716
9743
  * @returns Processed message (or original if hook doesn't exist/fails)
9717
9744
  */
9718
- static async runBeforeCreateMessageHook(state, message) {
9745
+ static async runBeforeCreateMessageHook(state, message, threadStateOverride, options) {
9719
9746
  const executors = await this.getHooksForType(state, "before_create_message");
9720
9747
  if (executors.length === 0) return message;
9721
9748
  let result = message;
9722
- const threadState = ThreadStateImpl.fromFlowState(state);
9749
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9723
9750
  for (const executor of executors) {
9724
9751
  try {
9725
9752
  const hooked = await executor(threadState, _FlowEngine.rowToHookMessage(result));
@@ -9729,6 +9756,9 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9729
9756
  attachments: hooked.attachments === void 0 ? result.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments)
9730
9757
  };
9731
9758
  } catch (error) {
9759
+ if (options?.propagateHookErrors) {
9760
+ throw error;
9761
+ }
9732
9762
  console.error("[Hooks] \u2717 Error running before_create_message hook:", error);
9733
9763
  }
9734
9764
  }
@@ -9741,10 +9771,10 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9741
9771
  * @param state - Flow state with execution context
9742
9772
  * @param message - The created message object
9743
9773
  */
9744
- static async runAfterCreateMessageHook(state, message) {
9774
+ static async runAfterCreateMessageHook(state, message, threadStateOverride) {
9745
9775
  const executors = await this.getHooksForType(state, "after_create_message");
9746
9776
  if (executors.length === 0) return;
9747
- const threadState = ThreadStateImpl.fromFlowState(state);
9777
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9748
9778
  for (const executor of executors) {
9749
9779
  try {
9750
9780
  await executor(threadState, _FlowEngine.rowToHookMessage(message));
@@ -10245,7 +10275,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10245
10275
  }
10246
10276
  existingPathMessages.add(content);
10247
10277
  const attachmentsJson = JSON.stringify([ref]);
10248
- const silentMessage = {
10278
+ let silentMessage = {
10249
10279
  id: crypto.randomUUID(),
10250
10280
  role: sourceMessage.role,
10251
10281
  content,
@@ -10256,6 +10286,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10256
10286
  parent_id: parentId,
10257
10287
  depth
10258
10288
  };
10289
+ silentMessage = await this.runBeforeCreateMessageHook(state, silentMessage);
10259
10290
  await state.storage.sql.exec(
10260
10291
  `
10261
10292
  INSERT INTO messages (
@@ -10266,15 +10297,16 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10266
10297
  silentMessage.id,
10267
10298
  silentMessage.role,
10268
10299
  silentMessage.content,
10269
- attachmentsJson,
10270
- 1,
10300
+ silentMessage.attachments ?? null,
10301
+ silentMessage.silent ? 1 : 0,
10271
10302
  silentMessage.created_at,
10272
- silentMessage.status,
10303
+ silentMessage.status ?? "completed",
10273
10304
  silentMessage.parent_id,
10274
10305
  silentMessage.depth,
10275
10306
  currentPrompt
10276
10307
  );
10277
10308
  state.messageHistory.push(silentMessage);
10309
+ await this.runAfterCreateMessageHook(state, silentMessage);
10278
10310
  if (state.emitMessage) {
10279
10311
  state.emitMessage(silentMessage);
10280
10312
  }
@@ -10813,7 +10845,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10813
10845
  */
10814
10846
  static async loadMessageHistory(state) {
10815
10847
  const storage = state.storage;
10816
- const thread = state.thread;
10848
+ state.thread;
10817
10849
  const includeParentHistory = state.prompt.include_chat ?? true;
10818
10850
  const includeToolCalls = state.prompt.include_past_tools ?? true;
10819
10851
  try {
@@ -10849,8 +10881,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10849
10881
  rows = cursor.toArray();
10850
10882
  }
10851
10883
  if (state.extraMessages && state.extraMessages.length > 0) {
10852
- rows.push(...state.extraMessages);
10853
- rows.sort((a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
10884
+ const persistedIds = new Set(rows.map((row) => row.id));
10885
+ rows.push(
10886
+ ...state.extraMessages.filter((entry) => !persistedIds.has(entry.id))
10887
+ );
10888
+ rows.sort((a, b) => a.created_at - b.created_at);
10854
10889
  }
10855
10890
  const messages = rows.map((row) => ({
10856
10891
  id: row.id,
@@ -10873,24 +10908,15 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10873
10908
  reasoning_content: row.reasoning_content,
10874
10909
  reasoning_details: row.reasoning_details
10875
10910
  }));
10876
- return this.runFilterMessagesHook(
10877
- thread.instance,
10878
- state,
10879
- this.filterOrphanedToolCalls(messages)
10880
- );
10911
+ return messages;
10881
10912
  } catch (error) {
10882
10913
  console.error("Error loading message history:", error);
10883
10914
  return [];
10884
10915
  }
10885
10916
  }
10886
10917
  /**
10887
- * Run the filter_messages hook if it exists
10888
- * This hook allows developers to modify SQL row data before transformation
10889
- *
10890
- * @param thread - Thread instance with hooks registry
10891
- * @param state - Flow state for hook context
10892
- * @param rows - SQL rows from messages table
10893
- * @returns Processed rows (or original if hook doesn't exist/fails)
10918
+ * Build request-context rows from persisted history.
10919
+ * Mutations and synthetic messages stay in the request context.
10894
10920
  */
10895
10921
  static async runFilterMessagesHook(thread, state, messages) {
10896
10922
  const executors = await this.getHooksForType(state, "filter_messages");
@@ -10903,13 +10929,56 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10903
10929
  for (const row of result) if (row.id) byId.set(row.id, row);
10904
10930
  const hookView = result.map((row) => _FlowEngine.rowToHookMessage(row));
10905
10931
  const kept = await executor(threadState, hookView);
10906
- result = kept.map((m) => m.id && byId.get(m.id) || null).filter((row) => row !== null);
10932
+ result = kept.map((hookedMessage) => {
10933
+ const original = hookedMessage.id ? byId.get(hookedMessage.id) : void 0;
10934
+ if (original) {
10935
+ return _FlowEngine.applyHookMessageToRow(original, hookedMessage);
10936
+ }
10937
+ return _FlowEngine.hookMessageToContextRow(hookedMessage);
10938
+ });
10907
10939
  } catch (error) {
10908
10940
  console.error("[Hooks] \u2717 Error running filter_messages hook:", error);
10909
10941
  }
10910
10942
  }
10911
10943
  return result;
10912
10944
  }
10945
+ /**
10946
+ * Copy a persisted row and apply hook edits. The original live-history
10947
+ * object is left unchanged.
10948
+ */
10949
+ static applyHookMessageToRow(original, hooked) {
10950
+ const attachments = hooked.attachments === void 0 ? original.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10951
+ return {
10952
+ ...original,
10953
+ role: hooked.role ?? original.role,
10954
+ content: hooked.content !== void 0 ? hooked.content : original.content,
10955
+ name: hooked.name !== void 0 ? hooked.name : original.name,
10956
+ tool_calls: hooked.tool_calls !== void 0 ? hooked.tool_calls : original.tool_calls,
10957
+ tool_call_id: hooked.tool_call_id !== void 0 ? hooked.tool_call_id : original.tool_call_id,
10958
+ silent: hooked.silent !== void 0 ? hooked.silent : original.silent,
10959
+ attachments
10960
+ };
10961
+ }
10962
+ /**
10963
+ * Convert a synthetic hook message into a request-context-only builder row.
10964
+ */
10965
+ static hookMessageToContextRow(hooked) {
10966
+ const attachments = hooked.attachments === void 0 || hooked.attachments === null ? hooked.attachments ?? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10967
+ return {
10968
+ id: hooked.id || crypto.randomUUID(),
10969
+ role: hooked.role,
10970
+ content: hooked.content ?? null,
10971
+ name: hooked.name ?? null,
10972
+ tool_calls: typeof hooked.tool_calls === "string" ? hooked.tool_calls : null,
10973
+ tool_call_id: hooked.tool_call_id ?? null,
10974
+ created_at: hooked.created_at ?? Date.now() * TIMESTAMP_MULTIPLIER,
10975
+ parent_id: hooked.parent_id ?? null,
10976
+ depth: hooked.depth ?? 0,
10977
+ silent: hooked.silent ?? false,
10978
+ status: "completed",
10979
+ attachments
10980
+ };
10981
+ }
10913
10982
  /**
10914
10983
  * Log an error to the logs table (best-effort)
10915
10984
  * Creates a log entry with error details but no associated message_id
@@ -11053,6 +11122,35 @@ ${errorStack}` : ""}`;
11053
11122
  });
11054
11123
 
11055
11124
  // src/agents/utilities.ts
11125
+ var utilities_exports = {};
11126
+ __export(utilities_exports, {
11127
+ cat: () => cat,
11128
+ emitThreadEvent: () => emitThreadEvent,
11129
+ exists: () => exists,
11130
+ find: () => find,
11131
+ forceTurn: () => forceTurn,
11132
+ getFileStats: () => getFileStats,
11133
+ getMessages: () => getMessages,
11134
+ getThumbnail: () => getThumbnail,
11135
+ grep: () => grep,
11136
+ head: () => head,
11137
+ injectMessage: () => injectMessage,
11138
+ linkFile: () => linkFile,
11139
+ mkdir: () => mkdir,
11140
+ move: () => move,
11141
+ queueTool: () => queueTool,
11142
+ readFile: () => readFile,
11143
+ readdir: () => readdir,
11144
+ reloadHistory: () => reloadHistory,
11145
+ rename: () => rename,
11146
+ rmdir: () => rmdir,
11147
+ stat: () => stat,
11148
+ tail: () => tail,
11149
+ unlink: () => unlink,
11150
+ updateThread: () => updateThread,
11151
+ writeFile: () => writeFile,
11152
+ writeImage: () => writeImage
11153
+ });
11056
11154
  function queueTool(flow, toolName, args = {}) {
11057
11155
  if (flow.currentToolUses !== void 0) {
11058
11156
  if (!flow.currentToolUses.includes(toolName)) {
@@ -11080,7 +11178,7 @@ async function injectMessage(flow, options) {
11080
11178
  const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
11081
11179
  const silent = options.silent ?? false;
11082
11180
  const forceTopLevel = options.forceTopLevel ?? false;
11083
- const message = {
11181
+ let message = {
11084
11182
  id: messageId,
11085
11183
  role: options.role,
11086
11184
  content: options.content,
@@ -11094,41 +11192,52 @@ async function injectMessage(flow, options) {
11094
11192
  metadata: options.metadata
11095
11193
  };
11096
11194
  if (options.beforeMessageId) {
11097
- const cursor = await flow.storage.sql.exec(
11098
- `SELECT created_at FROM messages WHERE id = ?`,
11099
- options.beforeMessageId
11195
+ const liveTarget = flow.messageHistory.find(
11196
+ (entry) => entry.id === options.beforeMessageId
11100
11197
  );
11101
- const rows = cursor.toArray();
11102
- const targetMessage = rows[0];
11103
- if (!targetMessage) {
11104
- throw new Error(
11105
- `Message with id ${options.beforeMessageId} not found`
11198
+ let targetCreatedAt = liveTarget?.created_at;
11199
+ if (targetCreatedAt === void 0) {
11200
+ const cursor = await flow.storage.sql.exec(
11201
+ `SELECT created_at FROM messages WHERE id = ?`,
11202
+ options.beforeMessageId
11106
11203
  );
11204
+ const rows = cursor.toArray();
11205
+ const targetMessage = rows[0];
11206
+ if (!targetMessage) {
11207
+ throw new Error(
11208
+ `Message with id ${options.beforeMessageId} not found`
11209
+ );
11210
+ }
11211
+ targetCreatedAt = targetMessage.created_at;
11107
11212
  }
11108
- message.created_at = targetMessage.created_at - 1;
11213
+ message.created_at = targetCreatedAt - 1;
11109
11214
  }
11110
11215
  const currentPrompt = flow.promptPath.length > 0 ? flow.promptPath[flow.promptPath.length - 1] : null;
11111
- const metadataJson = options.metadata ? JSON.stringify(options.metadata) : null;
11216
+ const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
11217
+ message = await FlowEngine2.runBeforeCreateMessageHook(flow, message, void 0, {
11218
+ propagateHookErrors: true
11219
+ });
11220
+ const metadataJson = message.metadata === void 0 ? null : JSON.stringify(message.metadata);
11112
11221
  await flow.storage.sql.exec(
11113
11222
  `INSERT INTO messages (id, role, content, name, tool_call_id, created_at, status, silent, parent_id, depth, metadata, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11114
- messageId,
11223
+ message.id,
11115
11224
  message.role,
11116
11225
  message.content,
11117
11226
  message.name,
11118
11227
  message.tool_call_id,
11119
11228
  message.created_at,
11120
11229
  message.status,
11121
- silent ? 1 : 0,
11230
+ message.silent ? 1 : 0,
11122
11231
  message.parent_id,
11123
11232
  message.depth,
11124
11233
  metadataJson,
11125
11234
  currentPrompt
11126
11235
  );
11127
- if (!silent) {
11128
- flow.messageHistory.push(message);
11129
- } else {
11130
- flow.messageHistory = await reloadHistory(flow);
11236
+ appendLiveExecutionHistory(flow, message, options.beforeMessageId);
11237
+ if (flow.prompt.include_chat === false || message.parent_id !== null || (message.depth ?? 0) > 0) {
11238
+ appendInjectedContextMessage(flow.extraMessages, message);
11131
11239
  }
11240
+ await FlowEngine2.runAfterCreateMessageHook(flow, message);
11132
11241
  if (flow.emitMessage) {
11133
11242
  flow.emitMessage(message);
11134
11243
  }
@@ -11137,6 +11246,42 @@ async function injectMessage(flow, options) {
11137
11246
  }
11138
11247
  return message;
11139
11248
  }
11249
+ function appendLiveExecutionHistory(flow, message, beforeMessageId) {
11250
+ const history = flow.messageHistory;
11251
+ const existing = history.findIndex((entry) => entry.id === message.id);
11252
+ if (existing >= 0) {
11253
+ history.splice(existing, 1);
11254
+ }
11255
+ if (beforeMessageId) {
11256
+ const targetIndex = history.findIndex((entry) => entry.id === beforeMessageId);
11257
+ if (targetIndex >= 0) {
11258
+ history.splice(targetIndex, 0, message);
11259
+ return;
11260
+ }
11261
+ }
11262
+ const insertAt = history.findIndex(
11263
+ (entry) => entry.created_at > message.created_at
11264
+ );
11265
+ if (insertAt === -1) {
11266
+ history.push(message);
11267
+ return;
11268
+ }
11269
+ history.splice(insertAt, 0, message);
11270
+ }
11271
+ function appendInjectedContextMessage(extraMessages, message) {
11272
+ const existing = extraMessages.findIndex((entry) => entry.id === message.id);
11273
+ if (existing >= 0) {
11274
+ extraMessages.splice(existing, 1);
11275
+ }
11276
+ const insertAt = extraMessages.findIndex(
11277
+ (entry) => entry.created_at > message.created_at
11278
+ );
11279
+ if (insertAt === -1) {
11280
+ extraMessages.push(message);
11281
+ return;
11282
+ }
11283
+ extraMessages.splice(insertAt, 0, message);
11284
+ }
11140
11285
  async function getMessages(flow, limit = 100, offset = 0, order = "asc") {
11141
11286
  const orderClause = order === "desc" ? "DESC" : "ASC";
11142
11287
  const cursor = await flow.storage.sql.exec(
@@ -30030,6 +30175,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
30030
30175
  // src/durable-objects/DurableThread.ts
30031
30176
  init_StreamManager();
30032
30177
  init_LLMRequest();
30178
+ init_ThreadStateImpl();
30033
30179
 
30034
30180
  // src/durable-objects/logDetailStream.ts
30035
30181
  var LOG_DETAIL_HEAVY_FIELDS = [
@@ -30732,7 +30878,8 @@ var DurableThread = class _DurableThread extends DurableObject {
30732
30878
  side_b_session_fail_attachments_property: sideBSession.fail.attachmentsProperty,
30733
30879
  side_b_session_status_tool: sideBSession.status.toolName,
30734
30880
  side_b_session_status_message_property: sideBSession.status.messageProperty,
30735
- side_b_session_status_attachments_property: sideBSession.status.attachmentsProperty
30881
+ side_b_session_status_attachments_property: sideBSession.status.attachmentsProperty,
30882
+ hooks: Array.isArray(agentDef.hooks) ? agentDef.hooks : void 0
30736
30883
  };
30737
30884
  }
30738
30885
  /**
@@ -31402,6 +31549,29 @@ var DurableThread = class _DurableThread extends DurableObject {
31402
31549
  }
31403
31550
  return projections;
31404
31551
  }
31552
+ /**
31553
+ * Build hook resolution context and a genuine at-rest ThreadState.
31554
+ * Resolution failures abort insertion so lifecycle hooks cannot be bypassed.
31555
+ */
31556
+ async buildAtRestHookState(threadId) {
31557
+ try {
31558
+ const metadata = await this.getThreadMetadata(threadId);
31559
+ const agentConfig = await this.loadAgentConfig(metadata.agent_id);
31560
+ const threadInstance = this;
31561
+ return {
31562
+ hookState: {
31563
+ agentConfig,
31564
+ thread: { instance: threadInstance, metadata }
31565
+ },
31566
+ threadState: ThreadStateImpl.fromThreadInstance(threadInstance, metadata)
31567
+ };
31568
+ } catch (error) {
31569
+ throw new Error(
31570
+ `Unable to resolve create-message hooks for ${threadId}`,
31571
+ { cause: error }
31572
+ );
31573
+ }
31574
+ }
31405
31575
  /**
31406
31576
  * Inject a top-level message without starting a new execution.
31407
31577
  *
@@ -31414,22 +31584,7 @@ var DurableThread = class _DurableThread extends DurableObject {
31414
31584
  const id = crypto.randomUUID();
31415
31585
  const createdAt = Date.now() * TIMESTAMP_MULTIPLIER;
31416
31586
  const silent = message.silent === true;
31417
- const metadataJson = message.metadata ? JSON.stringify(message.metadata) : null;
31418
- await this.ctx.storage.sql.exec(
31419
- `INSERT INTO messages (id, role, content, created_at, status, silent, parent_id, depth, metadata, prompt)
31420
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)`,
31421
- id,
31422
- message.role,
31423
- message.content,
31424
- createdAt,
31425
- "completed",
31426
- silent ? 1 : 0,
31427
- null,
31428
- 0,
31429
- metadataJson,
31430
- null
31431
- );
31432
- const inserted = {
31587
+ let inserted = {
31433
31588
  id,
31434
31589
  role: message.role,
31435
31590
  content: message.content,
@@ -31440,9 +31595,37 @@ var DurableThread = class _DurableThread extends DurableObject {
31440
31595
  depth: 0,
31441
31596
  metadata: message.metadata
31442
31597
  };
31598
+ const { hookState, threadState } = await this.buildAtRestHookState(threadId);
31599
+ const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
31600
+ inserted = await FlowEngine2.runBeforeCreateMessageHook(
31601
+ hookState,
31602
+ inserted,
31603
+ threadState,
31604
+ { propagateHookErrors: true }
31605
+ );
31606
+ const metadataJson = inserted.metadata ? JSON.stringify(inserted.metadata) : null;
31607
+ await this.ctx.storage.sql.exec(
31608
+ `INSERT INTO messages (id, role, content, created_at, status, silent, parent_id, depth, metadata, prompt)
31609
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)`,
31610
+ inserted.id,
31611
+ inserted.role,
31612
+ inserted.content,
31613
+ inserted.created_at,
31614
+ inserted.status ?? "completed",
31615
+ inserted.silent ? 1 : 0,
31616
+ inserted.parent_id ?? null,
31617
+ inserted.depth ?? 0,
31618
+ metadataJson,
31619
+ null
31620
+ );
31621
+ await FlowEngine2.runAfterCreateMessageHook(
31622
+ hookState,
31623
+ inserted,
31624
+ threadState
31625
+ );
31443
31626
  this.broadcastMessage(inserted);
31444
31627
  const record = inserted;
31445
- const visibleUserInjection = message.role === "user" && !silent && typeof message.content === "string" && message.content.trim().length > 0 && !isRuntimeSubagentEnvelope(message.content);
31628
+ const visibleUserInjection = inserted.role === "user" && !inserted.silent && typeof inserted.content === "string" && inserted.content.trim().length > 0 && !isRuntimeSubagentEnvelope(inserted.content);
31446
31629
  if (visibleUserInjection || isVisibleTerminalAssistantMessage(record)) {
31447
31630
  const stamp = this.recordRegistryActivity(
31448
31631
  threadId,
@@ -37937,8 +38120,7 @@ var DurableUserHub = class extends DurableObject {
37937
38120
  init_utilities();
37938
38121
 
37939
38122
  // src/agents/FlowStateSdk.ts
37940
- init_types();
37941
- var FlowStateSdk = class _FlowStateSdk {
38123
+ var FlowStateSdk = class {
37942
38124
  /**
37943
38125
  * Queue a new tool call to be executed in the current flow
37944
38126
  *
@@ -38005,63 +38187,8 @@ var FlowStateSdk = class _FlowStateSdk {
38005
38187
  * ```
38006
38188
  */
38007
38189
  static async injectMessage(flow, options) {
38008
- const messageId = options.id ?? crypto.randomUUID();
38009
- const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
38010
- const silent = options.silent ?? false;
38011
- const forceTopLevel = options.forceTopLevel ?? false;
38012
- const message = {
38013
- id: messageId,
38014
- role: options.role,
38015
- content: options.content,
38016
- name: options.name ?? null,
38017
- tool_call_id: options.toolCallId ?? null,
38018
- created_at: timestamp,
38019
- status: "completed",
38020
- silent,
38021
- parent_id: forceTopLevel ? null : flow.parentMessageId ?? null,
38022
- depth: forceTopLevel ? 0 : flow.depth
38023
- };
38024
- if (options.beforeMessageId) {
38025
- const cursor = await flow.storage.sql.exec(
38026
- `SELECT created_at FROM messages WHERE id = ?`,
38027
- options.beforeMessageId
38028
- );
38029
- const rows = cursor.toArray();
38030
- const targetMessage = rows[0];
38031
- if (!targetMessage) {
38032
- throw new Error(
38033
- `Message with id ${options.beforeMessageId} not found`
38034
- );
38035
- }
38036
- message.created_at = targetMessage.created_at - 1;
38037
- }
38038
- const currentPrompt = flow.promptPath.length > 0 ? flow.promptPath[flow.promptPath.length - 1] : null;
38039
- await flow.storage.sql.exec(
38040
- `INSERT INTO messages (id, role, content, name, tool_call_id, created_at, status, silent, parent_id, depth, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
38041
- messageId,
38042
- message.role,
38043
- message.content,
38044
- message.name,
38045
- message.tool_call_id,
38046
- message.created_at,
38047
- message.status,
38048
- silent ? 1 : 0,
38049
- message.parent_id,
38050
- message.depth,
38051
- currentPrompt
38052
- );
38053
- if (!silent) {
38054
- flow.messageHistory.push(message);
38055
- } else {
38056
- flow.messageHistory = await _FlowStateSdk.reloadHistory(flow);
38057
- }
38058
- if (flow.emitMessage) {
38059
- flow.emitMessage(message);
38060
- }
38061
- if (options.forceTurn) {
38062
- _FlowStateSdk.forceTurn(flow, options.forceTurn);
38063
- }
38064
- return message;
38190
+ const { injectMessage: injectMessage2 } = await Promise.resolve().then(() => (init_utilities(), utilities_exports));
38191
+ return injectMessage2(flow, options);
38065
38192
  }
38066
38193
  /**
38067
38194
  * Get messages from the thread history
@@ -38150,9 +38277,9 @@ var FlowStateSdk = class _FlowStateSdk {
38150
38277
  /**
38151
38278
  * Reload message history from storage
38152
38279
  *
38153
- * Re-loads the complete message history from the thread's SQLite storage,
38154
- * applying any filter_messages hooks. This is useful when you need to refresh
38155
- * the message history after making changes (e.g., injecting silent messages).
38280
+ * Re-loads the complete message history from the thread's SQLite storage.
38281
+ * This is useful when you need to refresh the message history after making
38282
+ * changes (e.g., injecting messages).
38156
38283
  *
38157
38284
  * @param flow - The current FlowState
38158
38285
  * @returns Array of messages (reloaded from storage)