@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/runtime.js CHANGED
@@ -1489,6 +1489,9 @@ function normalizeToolCallMessageSequence(messages) {
1489
1489
  for (const message of messages) {
1490
1490
  const isAssistantToolCallMessage = message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1491
1491
  if (!pending) {
1492
+ if (message.role === "tool") {
1493
+ continue;
1494
+ }
1492
1495
  if (!isAssistantToolCallMessage) {
1493
1496
  normalized.push(message);
1494
1497
  continue;
@@ -7635,28 +7638,12 @@ This usually points to something I can't fix from here \u2014 for example the wo
7635
7638
  state.promptPath = [sideAPrompt.name];
7636
7639
  state.pendingImmediateAfterHandoff = true;
7637
7640
  if (userMessageContent || userMessageAttachments) {
7638
- const handoffMessageId = crypto.randomUUID();
7639
- const handoffTimestamp = Date.now() * 1e3;
7640
- const handoffMessage = {
7641
- id: handoffMessageId,
7642
- role: "user",
7643
- content: userMessageContent || "",
7644
- created_at: handoffTimestamp,
7645
- attachments: userMessageAttachments
7646
- };
7647
- await state.storage.sql.exec(
7648
- `INSERT INTO messages (id, role, content, attachments, created_at, prompt) VALUES (?, ?, ?, ?, ?, ?)`,
7649
- handoffMessage.id,
7650
- handoffMessage.role,
7651
- handoffMessage.content,
7652
- handoffMessage.attachments || null,
7653
- handoffMessage.created_at,
7641
+ await this.persistHandoffUserMessage(
7642
+ state,
7643
+ userMessageContent || "",
7644
+ userMessageAttachments,
7654
7645
  sideAPrompt.name
7655
7646
  );
7656
- state.messageHistory.push(handoffMessage);
7657
- if (state.emitMessage) {
7658
- state.emitMessage(handoffMessage);
7659
- }
7660
7647
  }
7661
7648
  state.pendingHandoff = void 0;
7662
7649
  state.emitTelemetry?.({
@@ -7671,6 +7658,37 @@ This usually points to something I can't fix from here \u2014 for example the wo
7671
7658
  throw error;
7672
7659
  }
7673
7660
  }
7661
+ /**
7662
+ * Persist the user message that rides an agent handoff, applying the
7663
+ * create-message lifecycle hooks like every other insertion path.
7664
+ */
7665
+ static async persistHandoffUserMessage(state, content, attachments, promptName) {
7666
+ let handoffMessage = {
7667
+ id: crypto.randomUUID(),
7668
+ role: "user",
7669
+ content,
7670
+ created_at: Date.now() * 1e3,
7671
+ status: "completed",
7672
+ attachments: attachments ?? null
7673
+ };
7674
+ handoffMessage = await this.runBeforeCreateMessageHook(state, handoffMessage);
7675
+ await state.storage.sql.exec(
7676
+ `INSERT INTO messages (id, role, content, attachments, created_at, status, silent, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
7677
+ handoffMessage.id,
7678
+ handoffMessage.role,
7679
+ handoffMessage.content,
7680
+ handoffMessage.attachments || null,
7681
+ handoffMessage.created_at,
7682
+ handoffMessage.status ?? "completed",
7683
+ handoffMessage.silent ? 1 : 0,
7684
+ promptName
7685
+ );
7686
+ state.messageHistory.push(handoffMessage);
7687
+ await this.runAfterCreateMessageHook(state, handoffMessage);
7688
+ if (state.emitMessage) {
7689
+ state.emitMessage(handoffMessage);
7690
+ }
7691
+ }
7674
7692
  static isEnabledEnvFlag(value) {
7675
7693
  if (value === null || value === void 0) return false;
7676
7694
  const normalized = value.trim().toLowerCase();
@@ -8568,7 +8586,16 @@ This usually points to something I can't fix from here \u2014 for example the wo
8568
8586
  role: "system",
8569
8587
  content: systemPromptText
8570
8588
  });
8571
- const completedMessages = state.messageHistory.filter(
8589
+ const historySnapshot = state.messageHistory.filter(
8590
+ (msg) => msg.status !== "pending"
8591
+ );
8592
+ const filteredHistory = await this.runFilterMessagesHook(
8593
+ state.thread.instance,
8594
+ state,
8595
+ historySnapshot
8596
+ );
8597
+ const contextHistory = this.filterOrphanedToolCalls(filteredHistory);
8598
+ const completedMessages = contextHistory.filter(
8572
8599
  (msg) => msg.status !== "pending"
8573
8600
  );
8574
8601
  const recentMessageThreshold = state.prompt.recentImageThreshold ?? 10;
@@ -9706,11 +9733,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9706
9733
  * @param message - Message object to be created
9707
9734
  * @returns Processed message (or original if hook doesn't exist/fails)
9708
9735
  */
9709
- static async runBeforeCreateMessageHook(state, message) {
9736
+ static async runBeforeCreateMessageHook(state, message, threadStateOverride, options) {
9710
9737
  const executors = await this.getHooksForType(state, "before_create_message");
9711
9738
  if (executors.length === 0) return message;
9712
9739
  let result = message;
9713
- const threadState = ThreadStateImpl.fromFlowState(state);
9740
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9714
9741
  for (const executor of executors) {
9715
9742
  try {
9716
9743
  const hooked = await executor(threadState, _FlowEngine.rowToHookMessage(result));
@@ -9720,6 +9747,9 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9720
9747
  attachments: hooked.attachments === void 0 ? result.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments)
9721
9748
  };
9722
9749
  } catch (error) {
9750
+ if (options?.propagateHookErrors) {
9751
+ throw error;
9752
+ }
9723
9753
  console.error("[Hooks] \u2717 Error running before_create_message hook:", error);
9724
9754
  }
9725
9755
  }
@@ -9732,10 +9762,10 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9732
9762
  * @param state - Flow state with execution context
9733
9763
  * @param message - The created message object
9734
9764
  */
9735
- static async runAfterCreateMessageHook(state, message) {
9765
+ static async runAfterCreateMessageHook(state, message, threadStateOverride) {
9736
9766
  const executors = await this.getHooksForType(state, "after_create_message");
9737
9767
  if (executors.length === 0) return;
9738
- const threadState = ThreadStateImpl.fromFlowState(state);
9768
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9739
9769
  for (const executor of executors) {
9740
9770
  try {
9741
9771
  await executor(threadState, _FlowEngine.rowToHookMessage(message));
@@ -10236,7 +10266,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10236
10266
  }
10237
10267
  existingPathMessages.add(content);
10238
10268
  const attachmentsJson = JSON.stringify([ref]);
10239
- const silentMessage = {
10269
+ let silentMessage = {
10240
10270
  id: crypto.randomUUID(),
10241
10271
  role: sourceMessage.role,
10242
10272
  content,
@@ -10247,6 +10277,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10247
10277
  parent_id: parentId,
10248
10278
  depth
10249
10279
  };
10280
+ silentMessage = await this.runBeforeCreateMessageHook(state, silentMessage);
10250
10281
  await state.storage.sql.exec(
10251
10282
  `
10252
10283
  INSERT INTO messages (
@@ -10257,15 +10288,16 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10257
10288
  silentMessage.id,
10258
10289
  silentMessage.role,
10259
10290
  silentMessage.content,
10260
- attachmentsJson,
10261
- 1,
10291
+ silentMessage.attachments ?? null,
10292
+ silentMessage.silent ? 1 : 0,
10262
10293
  silentMessage.created_at,
10263
- silentMessage.status,
10294
+ silentMessage.status ?? "completed",
10264
10295
  silentMessage.parent_id,
10265
10296
  silentMessage.depth,
10266
10297
  currentPrompt
10267
10298
  );
10268
10299
  state.messageHistory.push(silentMessage);
10300
+ await this.runAfterCreateMessageHook(state, silentMessage);
10269
10301
  if (state.emitMessage) {
10270
10302
  state.emitMessage(silentMessage);
10271
10303
  }
@@ -10804,7 +10836,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10804
10836
  */
10805
10837
  static async loadMessageHistory(state) {
10806
10838
  const storage = state.storage;
10807
- const thread = state.thread;
10839
+ state.thread;
10808
10840
  const includeParentHistory = state.prompt.include_chat ?? true;
10809
10841
  const includeToolCalls = state.prompt.include_past_tools ?? true;
10810
10842
  try {
@@ -10840,8 +10872,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10840
10872
  rows = cursor.toArray();
10841
10873
  }
10842
10874
  if (state.extraMessages && state.extraMessages.length > 0) {
10843
- rows.push(...state.extraMessages);
10844
- rows.sort((a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
10875
+ const persistedIds = new Set(rows.map((row) => row.id));
10876
+ rows.push(
10877
+ ...state.extraMessages.filter((entry) => !persistedIds.has(entry.id))
10878
+ );
10879
+ rows.sort((a, b) => a.created_at - b.created_at);
10845
10880
  }
10846
10881
  const messages = rows.map((row) => ({
10847
10882
  id: row.id,
@@ -10864,24 +10899,15 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10864
10899
  reasoning_content: row.reasoning_content,
10865
10900
  reasoning_details: row.reasoning_details
10866
10901
  }));
10867
- return this.runFilterMessagesHook(
10868
- thread.instance,
10869
- state,
10870
- this.filterOrphanedToolCalls(messages)
10871
- );
10902
+ return messages;
10872
10903
  } catch (error) {
10873
10904
  console.error("Error loading message history:", error);
10874
10905
  return [];
10875
10906
  }
10876
10907
  }
10877
10908
  /**
10878
- * Run the filter_messages hook if it exists
10879
- * This hook allows developers to modify SQL row data before transformation
10880
- *
10881
- * @param thread - Thread instance with hooks registry
10882
- * @param state - Flow state for hook context
10883
- * @param rows - SQL rows from messages table
10884
- * @returns Processed rows (or original if hook doesn't exist/fails)
10909
+ * Build request-context rows from persisted history.
10910
+ * Mutations and synthetic messages stay in the request context.
10885
10911
  */
10886
10912
  static async runFilterMessagesHook(thread, state, messages) {
10887
10913
  const executors = await this.getHooksForType(state, "filter_messages");
@@ -10894,13 +10920,56 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10894
10920
  for (const row of result) if (row.id) byId.set(row.id, row);
10895
10921
  const hookView = result.map((row) => _FlowEngine.rowToHookMessage(row));
10896
10922
  const kept = await executor(threadState, hookView);
10897
- result = kept.map((m) => m.id && byId.get(m.id) || null).filter((row) => row !== null);
10923
+ result = kept.map((hookedMessage) => {
10924
+ const original = hookedMessage.id ? byId.get(hookedMessage.id) : void 0;
10925
+ if (original) {
10926
+ return _FlowEngine.applyHookMessageToRow(original, hookedMessage);
10927
+ }
10928
+ return _FlowEngine.hookMessageToContextRow(hookedMessage);
10929
+ });
10898
10930
  } catch (error) {
10899
10931
  console.error("[Hooks] \u2717 Error running filter_messages hook:", error);
10900
10932
  }
10901
10933
  }
10902
10934
  return result;
10903
10935
  }
10936
+ /**
10937
+ * Copy a persisted row and apply hook edits. The original live-history
10938
+ * object is left unchanged.
10939
+ */
10940
+ static applyHookMessageToRow(original, hooked) {
10941
+ const attachments = hooked.attachments === void 0 ? original.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10942
+ return {
10943
+ ...original,
10944
+ role: hooked.role ?? original.role,
10945
+ content: hooked.content !== void 0 ? hooked.content : original.content,
10946
+ name: hooked.name !== void 0 ? hooked.name : original.name,
10947
+ tool_calls: hooked.tool_calls !== void 0 ? hooked.tool_calls : original.tool_calls,
10948
+ tool_call_id: hooked.tool_call_id !== void 0 ? hooked.tool_call_id : original.tool_call_id,
10949
+ silent: hooked.silent !== void 0 ? hooked.silent : original.silent,
10950
+ attachments
10951
+ };
10952
+ }
10953
+ /**
10954
+ * Convert a synthetic hook message into a request-context-only builder row.
10955
+ */
10956
+ static hookMessageToContextRow(hooked) {
10957
+ const attachments = hooked.attachments === void 0 || hooked.attachments === null ? hooked.attachments ?? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10958
+ return {
10959
+ id: hooked.id || crypto.randomUUID(),
10960
+ role: hooked.role,
10961
+ content: hooked.content ?? null,
10962
+ name: hooked.name ?? null,
10963
+ tool_calls: typeof hooked.tool_calls === "string" ? hooked.tool_calls : null,
10964
+ tool_call_id: hooked.tool_call_id ?? null,
10965
+ created_at: hooked.created_at ?? Date.now() * TIMESTAMP_MULTIPLIER,
10966
+ parent_id: hooked.parent_id ?? null,
10967
+ depth: hooked.depth ?? 0,
10968
+ silent: hooked.silent ?? false,
10969
+ status: "completed",
10970
+ attachments
10971
+ };
10972
+ }
10904
10973
  /**
10905
10974
  * Log an error to the logs table (best-effort)
10906
10975
  * Creates a log entry with error details but no associated message_id
@@ -11044,6 +11113,35 @@ ${errorStack}` : ""}`;
11044
11113
  });
11045
11114
 
11046
11115
  // src/agents/utilities.ts
11116
+ var utilities_exports = {};
11117
+ __export(utilities_exports, {
11118
+ cat: () => cat,
11119
+ emitThreadEvent: () => emitThreadEvent,
11120
+ exists: () => exists,
11121
+ find: () => find,
11122
+ forceTurn: () => forceTurn,
11123
+ getFileStats: () => getFileStats,
11124
+ getMessages: () => getMessages,
11125
+ getThumbnail: () => getThumbnail,
11126
+ grep: () => grep,
11127
+ head: () => head,
11128
+ injectMessage: () => injectMessage,
11129
+ linkFile: () => linkFile,
11130
+ mkdir: () => mkdir,
11131
+ move: () => move,
11132
+ queueTool: () => queueTool,
11133
+ readFile: () => readFile,
11134
+ readdir: () => readdir,
11135
+ reloadHistory: () => reloadHistory,
11136
+ rename: () => rename,
11137
+ rmdir: () => rmdir,
11138
+ stat: () => stat,
11139
+ tail: () => tail,
11140
+ unlink: () => unlink,
11141
+ updateThread: () => updateThread,
11142
+ writeFile: () => writeFile,
11143
+ writeImage: () => writeImage
11144
+ });
11047
11145
  function queueTool(flow, toolName, args = {}) {
11048
11146
  if (flow.currentToolUses !== void 0) {
11049
11147
  if (!flow.currentToolUses.includes(toolName)) {
@@ -11071,7 +11169,7 @@ async function injectMessage(flow, options) {
11071
11169
  const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
11072
11170
  const silent = options.silent ?? false;
11073
11171
  const forceTopLevel = options.forceTopLevel ?? false;
11074
- const message = {
11172
+ let message = {
11075
11173
  id: messageId,
11076
11174
  role: options.role,
11077
11175
  content: options.content,
@@ -11085,41 +11183,52 @@ async function injectMessage(flow, options) {
11085
11183
  metadata: options.metadata
11086
11184
  };
11087
11185
  if (options.beforeMessageId) {
11088
- const cursor = await flow.storage.sql.exec(
11089
- `SELECT created_at FROM messages WHERE id = ?`,
11090
- options.beforeMessageId
11186
+ const liveTarget = flow.messageHistory.find(
11187
+ (entry) => entry.id === options.beforeMessageId
11091
11188
  );
11092
- const rows = cursor.toArray();
11093
- const targetMessage = rows[0];
11094
- if (!targetMessage) {
11095
- throw new Error(
11096
- `Message with id ${options.beforeMessageId} not found`
11189
+ let targetCreatedAt = liveTarget?.created_at;
11190
+ if (targetCreatedAt === void 0) {
11191
+ const cursor = await flow.storage.sql.exec(
11192
+ `SELECT created_at FROM messages WHERE id = ?`,
11193
+ options.beforeMessageId
11097
11194
  );
11195
+ const rows = cursor.toArray();
11196
+ const targetMessage = rows[0];
11197
+ if (!targetMessage) {
11198
+ throw new Error(
11199
+ `Message with id ${options.beforeMessageId} not found`
11200
+ );
11201
+ }
11202
+ targetCreatedAt = targetMessage.created_at;
11098
11203
  }
11099
- message.created_at = targetMessage.created_at - 1;
11204
+ message.created_at = targetCreatedAt - 1;
11100
11205
  }
11101
11206
  const currentPrompt = flow.promptPath.length > 0 ? flow.promptPath[flow.promptPath.length - 1] : null;
11102
- const metadataJson = options.metadata ? JSON.stringify(options.metadata) : null;
11207
+ const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
11208
+ message = await FlowEngine2.runBeforeCreateMessageHook(flow, message, void 0, {
11209
+ propagateHookErrors: true
11210
+ });
11211
+ const metadataJson = message.metadata === void 0 ? null : JSON.stringify(message.metadata);
11103
11212
  await flow.storage.sql.exec(
11104
11213
  `INSERT INTO messages (id, role, content, name, tool_call_id, created_at, status, silent, parent_id, depth, metadata, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11105
- messageId,
11214
+ message.id,
11106
11215
  message.role,
11107
11216
  message.content,
11108
11217
  message.name,
11109
11218
  message.tool_call_id,
11110
11219
  message.created_at,
11111
11220
  message.status,
11112
- silent ? 1 : 0,
11221
+ message.silent ? 1 : 0,
11113
11222
  message.parent_id,
11114
11223
  message.depth,
11115
11224
  metadataJson,
11116
11225
  currentPrompt
11117
11226
  );
11118
- if (!silent) {
11119
- flow.messageHistory.push(message);
11120
- } else {
11121
- flow.messageHistory = await reloadHistory(flow);
11227
+ appendLiveExecutionHistory(flow, message, options.beforeMessageId);
11228
+ if (flow.prompt.include_chat === false || message.parent_id !== null || (message.depth ?? 0) > 0) {
11229
+ appendInjectedContextMessage(flow.extraMessages, message);
11122
11230
  }
11231
+ await FlowEngine2.runAfterCreateMessageHook(flow, message);
11123
11232
  if (flow.emitMessage) {
11124
11233
  flow.emitMessage(message);
11125
11234
  }
@@ -11128,6 +11237,42 @@ async function injectMessage(flow, options) {
11128
11237
  }
11129
11238
  return message;
11130
11239
  }
11240
+ function appendLiveExecutionHistory(flow, message, beforeMessageId) {
11241
+ const history = flow.messageHistory;
11242
+ const existing = history.findIndex((entry) => entry.id === message.id);
11243
+ if (existing >= 0) {
11244
+ history.splice(existing, 1);
11245
+ }
11246
+ if (beforeMessageId) {
11247
+ const targetIndex = history.findIndex((entry) => entry.id === beforeMessageId);
11248
+ if (targetIndex >= 0) {
11249
+ history.splice(targetIndex, 0, message);
11250
+ return;
11251
+ }
11252
+ }
11253
+ const insertAt = history.findIndex(
11254
+ (entry) => entry.created_at > message.created_at
11255
+ );
11256
+ if (insertAt === -1) {
11257
+ history.push(message);
11258
+ return;
11259
+ }
11260
+ history.splice(insertAt, 0, message);
11261
+ }
11262
+ function appendInjectedContextMessage(extraMessages, message) {
11263
+ const existing = extraMessages.findIndex((entry) => entry.id === message.id);
11264
+ if (existing >= 0) {
11265
+ extraMessages.splice(existing, 1);
11266
+ }
11267
+ const insertAt = extraMessages.findIndex(
11268
+ (entry) => entry.created_at > message.created_at
11269
+ );
11270
+ if (insertAt === -1) {
11271
+ extraMessages.push(message);
11272
+ return;
11273
+ }
11274
+ extraMessages.splice(insertAt, 0, message);
11275
+ }
11131
11276
  async function getMessages(flow, limit = 100, offset = 0, order = "asc") {
11132
11277
  const orderClause = order === "desc" ? "DESC" : "ASC";
11133
11278
  const cursor = await flow.storage.sql.exec(
@@ -21555,6 +21700,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
21555
21700
  // src/durable-objects/DurableThread.ts
21556
21701
  init_StreamManager();
21557
21702
  init_LLMRequest();
21703
+ init_ThreadStateImpl();
21558
21704
 
21559
21705
  // src/durable-objects/logDetailStream.ts
21560
21706
  var LOG_DETAIL_HEAVY_FIELDS = [
@@ -22257,7 +22403,8 @@ var DurableThread = class _DurableThread extends DurableObject {
22257
22403
  side_b_session_fail_attachments_property: sideBSession.fail.attachmentsProperty,
22258
22404
  side_b_session_status_tool: sideBSession.status.toolName,
22259
22405
  side_b_session_status_message_property: sideBSession.status.messageProperty,
22260
- side_b_session_status_attachments_property: sideBSession.status.attachmentsProperty
22406
+ side_b_session_status_attachments_property: sideBSession.status.attachmentsProperty,
22407
+ hooks: Array.isArray(agentDef.hooks) ? agentDef.hooks : void 0
22261
22408
  };
22262
22409
  }
22263
22410
  /**
@@ -22927,6 +23074,29 @@ var DurableThread = class _DurableThread extends DurableObject {
22927
23074
  }
22928
23075
  return projections;
22929
23076
  }
23077
+ /**
23078
+ * Build hook resolution context and a genuine at-rest ThreadState.
23079
+ * Resolution failures abort insertion so lifecycle hooks cannot be bypassed.
23080
+ */
23081
+ async buildAtRestHookState(threadId) {
23082
+ try {
23083
+ const metadata = await this.getThreadMetadata(threadId);
23084
+ const agentConfig = await this.loadAgentConfig(metadata.agent_id);
23085
+ const threadInstance = this;
23086
+ return {
23087
+ hookState: {
23088
+ agentConfig,
23089
+ thread: { instance: threadInstance, metadata }
23090
+ },
23091
+ threadState: ThreadStateImpl.fromThreadInstance(threadInstance, metadata)
23092
+ };
23093
+ } catch (error) {
23094
+ throw new Error(
23095
+ `Unable to resolve create-message hooks for ${threadId}`,
23096
+ { cause: error }
23097
+ );
23098
+ }
23099
+ }
22930
23100
  /**
22931
23101
  * Inject a top-level message without starting a new execution.
22932
23102
  *
@@ -22939,22 +23109,7 @@ var DurableThread = class _DurableThread extends DurableObject {
22939
23109
  const id = crypto.randomUUID();
22940
23110
  const createdAt = Date.now() * TIMESTAMP_MULTIPLIER;
22941
23111
  const silent = message.silent === true;
22942
- const metadataJson = message.metadata ? JSON.stringify(message.metadata) : null;
22943
- await this.ctx.storage.sql.exec(
22944
- `INSERT INTO messages (id, role, content, created_at, status, silent, parent_id, depth, metadata, prompt)
22945
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)`,
22946
- id,
22947
- message.role,
22948
- message.content,
22949
- createdAt,
22950
- "completed",
22951
- silent ? 1 : 0,
22952
- null,
22953
- 0,
22954
- metadataJson,
22955
- null
22956
- );
22957
- const inserted = {
23112
+ let inserted = {
22958
23113
  id,
22959
23114
  role: message.role,
22960
23115
  content: message.content,
@@ -22965,9 +23120,37 @@ var DurableThread = class _DurableThread extends DurableObject {
22965
23120
  depth: 0,
22966
23121
  metadata: message.metadata
22967
23122
  };
23123
+ const { hookState, threadState } = await this.buildAtRestHookState(threadId);
23124
+ const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
23125
+ inserted = await FlowEngine2.runBeforeCreateMessageHook(
23126
+ hookState,
23127
+ inserted,
23128
+ threadState,
23129
+ { propagateHookErrors: true }
23130
+ );
23131
+ const metadataJson = inserted.metadata ? JSON.stringify(inserted.metadata) : null;
23132
+ await this.ctx.storage.sql.exec(
23133
+ `INSERT INTO messages (id, role, content, created_at, status, silent, parent_id, depth, metadata, prompt)
23134
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)`,
23135
+ inserted.id,
23136
+ inserted.role,
23137
+ inserted.content,
23138
+ inserted.created_at,
23139
+ inserted.status ?? "completed",
23140
+ inserted.silent ? 1 : 0,
23141
+ inserted.parent_id ?? null,
23142
+ inserted.depth ?? 0,
23143
+ metadataJson,
23144
+ null
23145
+ );
23146
+ await FlowEngine2.runAfterCreateMessageHook(
23147
+ hookState,
23148
+ inserted,
23149
+ threadState
23150
+ );
22968
23151
  this.broadcastMessage(inserted);
22969
23152
  const record = inserted;
22970
- const visibleUserInjection = message.role === "user" && !silent && typeof message.content === "string" && message.content.trim().length > 0 && !isRuntimeSubagentEnvelope(message.content);
23153
+ const visibleUserInjection = inserted.role === "user" && !inserted.silent && typeof inserted.content === "string" && inserted.content.trim().length > 0 && !isRuntimeSubagentEnvelope(inserted.content);
22971
23154
  if (visibleUserInjection || isVisibleTerminalAssistantMessage(record)) {
22972
23155
  const stamp = this.recordRegistryActivity(
22973
23156
  threadId,
@@ -29462,8 +29645,7 @@ var DurableUserHub = class extends DurableObject {
29462
29645
  init_utilities();
29463
29646
 
29464
29647
  // src/agents/FlowStateSdk.ts
29465
- init_types();
29466
- var FlowStateSdk = class _FlowStateSdk {
29648
+ var FlowStateSdk = class {
29467
29649
  /**
29468
29650
  * Queue a new tool call to be executed in the current flow
29469
29651
  *
@@ -29530,63 +29712,8 @@ var FlowStateSdk = class _FlowStateSdk {
29530
29712
  * ```
29531
29713
  */
29532
29714
  static async injectMessage(flow, options) {
29533
- const messageId = options.id ?? crypto.randomUUID();
29534
- const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
29535
- const silent = options.silent ?? false;
29536
- const forceTopLevel = options.forceTopLevel ?? false;
29537
- const message = {
29538
- id: messageId,
29539
- role: options.role,
29540
- content: options.content,
29541
- name: options.name ?? null,
29542
- tool_call_id: options.toolCallId ?? null,
29543
- created_at: timestamp,
29544
- status: "completed",
29545
- silent,
29546
- parent_id: forceTopLevel ? null : flow.parentMessageId ?? null,
29547
- depth: forceTopLevel ? 0 : flow.depth
29548
- };
29549
- if (options.beforeMessageId) {
29550
- const cursor = await flow.storage.sql.exec(
29551
- `SELECT created_at FROM messages WHERE id = ?`,
29552
- options.beforeMessageId
29553
- );
29554
- const rows = cursor.toArray();
29555
- const targetMessage = rows[0];
29556
- if (!targetMessage) {
29557
- throw new Error(
29558
- `Message with id ${options.beforeMessageId} not found`
29559
- );
29560
- }
29561
- message.created_at = targetMessage.created_at - 1;
29562
- }
29563
- const currentPrompt = flow.promptPath.length > 0 ? flow.promptPath[flow.promptPath.length - 1] : null;
29564
- await flow.storage.sql.exec(
29565
- `INSERT INTO messages (id, role, content, name, tool_call_id, created_at, status, silent, parent_id, depth, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
29566
- messageId,
29567
- message.role,
29568
- message.content,
29569
- message.name,
29570
- message.tool_call_id,
29571
- message.created_at,
29572
- message.status,
29573
- silent ? 1 : 0,
29574
- message.parent_id,
29575
- message.depth,
29576
- currentPrompt
29577
- );
29578
- if (!silent) {
29579
- flow.messageHistory.push(message);
29580
- } else {
29581
- flow.messageHistory = await _FlowStateSdk.reloadHistory(flow);
29582
- }
29583
- if (flow.emitMessage) {
29584
- flow.emitMessage(message);
29585
- }
29586
- if (options.forceTurn) {
29587
- _FlowStateSdk.forceTurn(flow, options.forceTurn);
29588
- }
29589
- return message;
29715
+ const { injectMessage: injectMessage2 } = await Promise.resolve().then(() => (init_utilities(), utilities_exports));
29716
+ return injectMessage2(flow, options);
29590
29717
  }
29591
29718
  /**
29592
29719
  * Get messages from the thread history
@@ -29675,9 +29802,9 @@ var FlowStateSdk = class _FlowStateSdk {
29675
29802
  /**
29676
29803
  * Reload message history from storage
29677
29804
  *
29678
- * Re-loads the complete message history from the thread's SQLite storage,
29679
- * applying any filter_messages hooks. This is useful when you need to refresh
29680
- * the message history after making changes (e.g., injecting silent messages).
29805
+ * Re-loads the complete message history from the thread's SQLite storage.
29806
+ * This is useful when you need to refresh the message history after making
29807
+ * changes (e.g., injecting messages).
29681
29808
  *
29682
29809
  * @param flow - The current FlowState
29683
29810
  * @returns Array of messages (reloaded from storage)