@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.
@@ -1462,6 +1462,9 @@ function normalizeToolCallMessageSequence(messages) {
1462
1462
  for (const message of messages) {
1463
1463
  const isAssistantToolCallMessage = message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1464
1464
  if (!pending) {
1465
+ if (message.role === "tool") {
1466
+ continue;
1467
+ }
1465
1468
  if (!isAssistantToolCallMessage) {
1466
1469
  normalized.push(message);
1467
1470
  continue;
@@ -7515,28 +7518,12 @@ This usually points to something I can't fix from here \u2014 for example the wo
7515
7518
  state.promptPath = [sideAPrompt.name];
7516
7519
  state.pendingImmediateAfterHandoff = true;
7517
7520
  if (userMessageContent || userMessageAttachments) {
7518
- const handoffMessageId = crypto.randomUUID();
7519
- const handoffTimestamp = Date.now() * 1e3;
7520
- const handoffMessage = {
7521
- id: handoffMessageId,
7522
- role: "user",
7523
- content: userMessageContent || "",
7524
- created_at: handoffTimestamp,
7525
- attachments: userMessageAttachments
7526
- };
7527
- await state.storage.sql.exec(
7528
- `INSERT INTO messages (id, role, content, attachments, created_at, prompt) VALUES (?, ?, ?, ?, ?, ?)`,
7529
- handoffMessage.id,
7530
- handoffMessage.role,
7531
- handoffMessage.content,
7532
- handoffMessage.attachments || null,
7533
- handoffMessage.created_at,
7521
+ await this.persistHandoffUserMessage(
7522
+ state,
7523
+ userMessageContent || "",
7524
+ userMessageAttachments,
7534
7525
  sideAPrompt.name
7535
7526
  );
7536
- state.messageHistory.push(handoffMessage);
7537
- if (state.emitMessage) {
7538
- state.emitMessage(handoffMessage);
7539
- }
7540
7527
  }
7541
7528
  state.pendingHandoff = void 0;
7542
7529
  state.emitTelemetry?.({
@@ -7551,6 +7538,37 @@ This usually points to something I can't fix from here \u2014 for example the wo
7551
7538
  throw error;
7552
7539
  }
7553
7540
  }
7541
+ /**
7542
+ * Persist the user message that rides an agent handoff, applying the
7543
+ * create-message lifecycle hooks like every other insertion path.
7544
+ */
7545
+ static async persistHandoffUserMessage(state, content, attachments, promptName) {
7546
+ let handoffMessage = {
7547
+ id: crypto.randomUUID(),
7548
+ role: "user",
7549
+ content,
7550
+ created_at: Date.now() * 1e3,
7551
+ status: "completed",
7552
+ attachments: attachments ?? null
7553
+ };
7554
+ handoffMessage = await this.runBeforeCreateMessageHook(state, handoffMessage);
7555
+ await state.storage.sql.exec(
7556
+ `INSERT INTO messages (id, role, content, attachments, created_at, status, silent, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
7557
+ handoffMessage.id,
7558
+ handoffMessage.role,
7559
+ handoffMessage.content,
7560
+ handoffMessage.attachments || null,
7561
+ handoffMessage.created_at,
7562
+ handoffMessage.status ?? "completed",
7563
+ handoffMessage.silent ? 1 : 0,
7564
+ promptName
7565
+ );
7566
+ state.messageHistory.push(handoffMessage);
7567
+ await this.runAfterCreateMessageHook(state, handoffMessage);
7568
+ if (state.emitMessage) {
7569
+ state.emitMessage(handoffMessage);
7570
+ }
7571
+ }
7554
7572
  static isEnabledEnvFlag(value) {
7555
7573
  if (value === null || value === void 0) return false;
7556
7574
  const normalized = value.trim().toLowerCase();
@@ -8448,7 +8466,16 @@ This usually points to something I can't fix from here \u2014 for example the wo
8448
8466
  role: "system",
8449
8467
  content: systemPromptText
8450
8468
  });
8451
- const completedMessages = state.messageHistory.filter(
8469
+ const historySnapshot = state.messageHistory.filter(
8470
+ (msg) => msg.status !== "pending"
8471
+ );
8472
+ const filteredHistory = await this.runFilterMessagesHook(
8473
+ state.thread.instance,
8474
+ state,
8475
+ historySnapshot
8476
+ );
8477
+ const contextHistory = this.filterOrphanedToolCalls(filteredHistory);
8478
+ const completedMessages = contextHistory.filter(
8452
8479
  (msg) => msg.status !== "pending"
8453
8480
  );
8454
8481
  const recentMessageThreshold = state.prompt.recentImageThreshold ?? 10;
@@ -9586,11 +9613,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9586
9613
  * @param message - Message object to be created
9587
9614
  * @returns Processed message (or original if hook doesn't exist/fails)
9588
9615
  */
9589
- static async runBeforeCreateMessageHook(state, message) {
9616
+ static async runBeforeCreateMessageHook(state, message, threadStateOverride, options) {
9590
9617
  const executors = await this.getHooksForType(state, "before_create_message");
9591
9618
  if (executors.length === 0) return message;
9592
9619
  let result = message;
9593
- const threadState = ThreadStateImpl.fromFlowState(state);
9620
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9594
9621
  for (const executor of executors) {
9595
9622
  try {
9596
9623
  const hooked = await executor(threadState, _FlowEngine.rowToHookMessage(result));
@@ -9600,6 +9627,9 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9600
9627
  attachments: hooked.attachments === void 0 ? result.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments)
9601
9628
  };
9602
9629
  } catch (error) {
9630
+ if (options?.propagateHookErrors) {
9631
+ throw error;
9632
+ }
9603
9633
  console.error("[Hooks] \u2717 Error running before_create_message hook:", error);
9604
9634
  }
9605
9635
  }
@@ -9612,10 +9642,10 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
9612
9642
  * @param state - Flow state with execution context
9613
9643
  * @param message - The created message object
9614
9644
  */
9615
- static async runAfterCreateMessageHook(state, message) {
9645
+ static async runAfterCreateMessageHook(state, message, threadStateOverride) {
9616
9646
  const executors = await this.getHooksForType(state, "after_create_message");
9617
9647
  if (executors.length === 0) return;
9618
- const threadState = ThreadStateImpl.fromFlowState(state);
9648
+ const threadState = threadStateOverride ?? ThreadStateImpl.fromFlowState(state);
9619
9649
  for (const executor of executors) {
9620
9650
  try {
9621
9651
  await executor(threadState, _FlowEngine.rowToHookMessage(message));
@@ -10116,7 +10146,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10116
10146
  }
10117
10147
  existingPathMessages.add(content);
10118
10148
  const attachmentsJson = JSON.stringify([ref]);
10119
- const silentMessage = {
10149
+ let silentMessage = {
10120
10150
  id: crypto.randomUUID(),
10121
10151
  role: sourceMessage.role,
10122
10152
  content,
@@ -10127,6 +10157,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10127
10157
  parent_id: parentId,
10128
10158
  depth
10129
10159
  };
10160
+ silentMessage = await this.runBeforeCreateMessageHook(state, silentMessage);
10130
10161
  await state.storage.sql.exec(
10131
10162
  `
10132
10163
  INSERT INTO messages (
@@ -10137,15 +10168,16 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10137
10168
  silentMessage.id,
10138
10169
  silentMessage.role,
10139
10170
  silentMessage.content,
10140
- attachmentsJson,
10141
- 1,
10171
+ silentMessage.attachments ?? null,
10172
+ silentMessage.silent ? 1 : 0,
10142
10173
  silentMessage.created_at,
10143
- silentMessage.status,
10174
+ silentMessage.status ?? "completed",
10144
10175
  silentMessage.parent_id,
10145
10176
  silentMessage.depth,
10146
10177
  currentPrompt
10147
10178
  );
10148
10179
  state.messageHistory.push(silentMessage);
10180
+ await this.runAfterCreateMessageHook(state, silentMessage);
10149
10181
  if (state.emitMessage) {
10150
10182
  state.emitMessage(silentMessage);
10151
10183
  }
@@ -10684,7 +10716,7 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10684
10716
  */
10685
10717
  static async loadMessageHistory(state) {
10686
10718
  const storage = state.storage;
10687
- const thread = state.thread;
10719
+ state.thread;
10688
10720
  const includeParentHistory = state.prompt.include_chat ?? true;
10689
10721
  const includeToolCalls = state.prompt.include_past_tools ?? true;
10690
10722
  try {
@@ -10720,8 +10752,11 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10720
10752
  rows = cursor.toArray();
10721
10753
  }
10722
10754
  if (state.extraMessages && state.extraMessages.length > 0) {
10723
- rows.push(...state.extraMessages);
10724
- rows.sort((a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
10755
+ const persistedIds = new Set(rows.map((row) => row.id));
10756
+ rows.push(
10757
+ ...state.extraMessages.filter((entry) => !persistedIds.has(entry.id))
10758
+ );
10759
+ rows.sort((a, b) => a.created_at - b.created_at);
10725
10760
  }
10726
10761
  const messages = rows.map((row) => ({
10727
10762
  id: row.id,
@@ -10744,24 +10779,15 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10744
10779
  reasoning_content: row.reasoning_content,
10745
10780
  reasoning_details: row.reasoning_details
10746
10781
  }));
10747
- return this.runFilterMessagesHook(
10748
- thread.instance,
10749
- state,
10750
- this.filterOrphanedToolCalls(messages)
10751
- );
10782
+ return messages;
10752
10783
  } catch (error) {
10753
10784
  console.error("Error loading message history:", error);
10754
10785
  return [];
10755
10786
  }
10756
10787
  }
10757
10788
  /**
10758
- * Run the filter_messages hook if it exists
10759
- * This hook allows developers to modify SQL row data before transformation
10760
- *
10761
- * @param thread - Thread instance with hooks registry
10762
- * @param state - Flow state for hook context
10763
- * @param rows - SQL rows from messages table
10764
- * @returns Processed rows (or original if hook doesn't exist/fails)
10789
+ * Build request-context rows from persisted history.
10790
+ * Mutations and synthetic messages stay in the request context.
10765
10791
  */
10766
10792
  static async runFilterMessagesHook(thread, state, messages) {
10767
10793
  const executors = await this.getHooksForType(state, "filter_messages");
@@ -10774,13 +10800,56 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10774
10800
  for (const row of result) if (row.id) byId.set(row.id, row);
10775
10801
  const hookView = result.map((row) => _FlowEngine.rowToHookMessage(row));
10776
10802
  const kept = await executor(threadState, hookView);
10777
- result = kept.map((m) => m.id && byId.get(m.id) || null).filter((row) => row !== null);
10803
+ result = kept.map((hookedMessage) => {
10804
+ const original = hookedMessage.id ? byId.get(hookedMessage.id) : void 0;
10805
+ if (original) {
10806
+ return _FlowEngine.applyHookMessageToRow(original, hookedMessage);
10807
+ }
10808
+ return _FlowEngine.hookMessageToContextRow(hookedMessage);
10809
+ });
10778
10810
  } catch (error) {
10779
10811
  console.error("[Hooks] \u2717 Error running filter_messages hook:", error);
10780
10812
  }
10781
10813
  }
10782
10814
  return result;
10783
10815
  }
10816
+ /**
10817
+ * Copy a persisted row and apply hook edits. The original live-history
10818
+ * object is left unchanged.
10819
+ */
10820
+ static applyHookMessageToRow(original, hooked) {
10821
+ const attachments = hooked.attachments === void 0 ? original.attachments : hooked.attachments === null ? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10822
+ return {
10823
+ ...original,
10824
+ role: hooked.role ?? original.role,
10825
+ content: hooked.content !== void 0 ? hooked.content : original.content,
10826
+ name: hooked.name !== void 0 ? hooked.name : original.name,
10827
+ tool_calls: hooked.tool_calls !== void 0 ? hooked.tool_calls : original.tool_calls,
10828
+ tool_call_id: hooked.tool_call_id !== void 0 ? hooked.tool_call_id : original.tool_call_id,
10829
+ silent: hooked.silent !== void 0 ? hooked.silent : original.silent,
10830
+ attachments
10831
+ };
10832
+ }
10833
+ /**
10834
+ * Convert a synthetic hook message into a request-context-only builder row.
10835
+ */
10836
+ static hookMessageToContextRow(hooked) {
10837
+ const attachments = hooked.attachments === void 0 || hooked.attachments === null ? hooked.attachments ?? null : typeof hooked.attachments === "string" ? hooked.attachments : JSON.stringify(hooked.attachments);
10838
+ return {
10839
+ id: hooked.id || crypto.randomUUID(),
10840
+ role: hooked.role,
10841
+ content: hooked.content ?? null,
10842
+ name: hooked.name ?? null,
10843
+ tool_calls: typeof hooked.tool_calls === "string" ? hooked.tool_calls : null,
10844
+ tool_call_id: hooked.tool_call_id ?? null,
10845
+ created_at: hooked.created_at ?? Date.now() * TIMESTAMP_MULTIPLIER,
10846
+ parent_id: hooked.parent_id ?? null,
10847
+ depth: hooked.depth ?? 0,
10848
+ silent: hooked.silent ?? false,
10849
+ status: "completed",
10850
+ attachments
10851
+ };
10852
+ }
10784
10853
  /**
10785
10854
  * Log an error to the logs table (best-effort)
10786
10855
  * Creates a log entry with error details but no associated message_id
@@ -10951,7 +11020,7 @@ async function injectMessage(flow, options) {
10951
11020
  const timestamp = Date.now() * TIMESTAMP_MULTIPLIER;
10952
11021
  const silent = options.silent ?? false;
10953
11022
  const forceTopLevel = options.forceTopLevel ?? false;
10954
- const message = {
11023
+ let message = {
10955
11024
  id: messageId,
10956
11025
  role: options.role,
10957
11026
  content: options.content,
@@ -10965,41 +11034,52 @@ async function injectMessage(flow, options) {
10965
11034
  metadata: options.metadata
10966
11035
  };
10967
11036
  if (options.beforeMessageId) {
10968
- const cursor = await flow.storage.sql.exec(
10969
- `SELECT created_at FROM messages WHERE id = ?`,
10970
- options.beforeMessageId
11037
+ const liveTarget = flow.messageHistory.find(
11038
+ (entry) => entry.id === options.beforeMessageId
10971
11039
  );
10972
- const rows = cursor.toArray();
10973
- const targetMessage = rows[0];
10974
- if (!targetMessage) {
10975
- throw new Error(
10976
- `Message with id ${options.beforeMessageId} not found`
11040
+ let targetCreatedAt = liveTarget?.created_at;
11041
+ if (targetCreatedAt === void 0) {
11042
+ const cursor = await flow.storage.sql.exec(
11043
+ `SELECT created_at FROM messages WHERE id = ?`,
11044
+ options.beforeMessageId
10977
11045
  );
11046
+ const rows = cursor.toArray();
11047
+ const targetMessage = rows[0];
11048
+ if (!targetMessage) {
11049
+ throw new Error(
11050
+ `Message with id ${options.beforeMessageId} not found`
11051
+ );
11052
+ }
11053
+ targetCreatedAt = targetMessage.created_at;
10978
11054
  }
10979
- message.created_at = targetMessage.created_at - 1;
11055
+ message.created_at = targetCreatedAt - 1;
10980
11056
  }
10981
11057
  const currentPrompt = flow.promptPath.length > 0 ? flow.promptPath[flow.promptPath.length - 1] : null;
10982
- const metadataJson = options.metadata ? JSON.stringify(options.metadata) : null;
11058
+ const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
11059
+ message = await FlowEngine2.runBeforeCreateMessageHook(flow, message, void 0, {
11060
+ propagateHookErrors: true
11061
+ });
11062
+ const metadataJson = message.metadata === void 0 ? null : JSON.stringify(message.metadata);
10983
11063
  await flow.storage.sql.exec(
10984
11064
  `INSERT INTO messages (id, role, content, name, tool_call_id, created_at, status, silent, parent_id, depth, metadata, prompt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10985
- messageId,
11065
+ message.id,
10986
11066
  message.role,
10987
11067
  message.content,
10988
11068
  message.name,
10989
11069
  message.tool_call_id,
10990
11070
  message.created_at,
10991
11071
  message.status,
10992
- silent ? 1 : 0,
11072
+ message.silent ? 1 : 0,
10993
11073
  message.parent_id,
10994
11074
  message.depth,
10995
11075
  metadataJson,
10996
11076
  currentPrompt
10997
11077
  );
10998
- if (!silent) {
10999
- flow.messageHistory.push(message);
11000
- } else {
11001
- flow.messageHistory = await reloadHistory(flow);
11078
+ appendLiveExecutionHistory(flow, message, options.beforeMessageId);
11079
+ if (flow.prompt.include_chat === false || message.parent_id !== null || (message.depth ?? 0) > 0) {
11080
+ appendInjectedContextMessage(flow.extraMessages, message);
11002
11081
  }
11082
+ await FlowEngine2.runAfterCreateMessageHook(flow, message);
11003
11083
  if (flow.emitMessage) {
11004
11084
  flow.emitMessage(message);
11005
11085
  }
@@ -11008,10 +11088,41 @@ async function injectMessage(flow, options) {
11008
11088
  }
11009
11089
  return message;
11010
11090
  }
11011
- async function reloadHistory(state) {
11012
- const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
11013
- state.messageHistory = await FlowEngine2.loadMessageHistory(state);
11014
- return state.messageHistory;
11091
+ function appendLiveExecutionHistory(flow, message, beforeMessageId) {
11092
+ const history = flow.messageHistory;
11093
+ const existing = history.findIndex((entry) => entry.id === message.id);
11094
+ if (existing >= 0) {
11095
+ history.splice(existing, 1);
11096
+ }
11097
+ if (beforeMessageId) {
11098
+ const targetIndex = history.findIndex((entry) => entry.id === beforeMessageId);
11099
+ if (targetIndex >= 0) {
11100
+ history.splice(targetIndex, 0, message);
11101
+ return;
11102
+ }
11103
+ }
11104
+ const insertAt = history.findIndex(
11105
+ (entry) => entry.created_at > message.created_at
11106
+ );
11107
+ if (insertAt === -1) {
11108
+ history.push(message);
11109
+ return;
11110
+ }
11111
+ history.splice(insertAt, 0, message);
11112
+ }
11113
+ function appendInjectedContextMessage(extraMessages, message) {
11114
+ const existing = extraMessages.findIndex((entry) => entry.id === message.id);
11115
+ if (existing >= 0) {
11116
+ extraMessages.splice(existing, 1);
11117
+ }
11118
+ const insertAt = extraMessages.findIndex(
11119
+ (entry) => entry.created_at > message.created_at
11120
+ );
11121
+ if (insertAt === -1) {
11122
+ extraMessages.push(message);
11123
+ return;
11124
+ }
11125
+ extraMessages.splice(insertAt, 0, message);
11015
11126
  }
11016
11127
  function forceTurn(flow, side) {
11017
11128
  if (flow.sequence.isHandling) {
@@ -68370,7 +68481,7 @@ var config_get_default = defineController2(async ({ config, env: env2, req }) =>
68370
68481
  });
68371
68482
 
68372
68483
  // src/version.ts
68373
- var BUILDER_VERSION = "0.39.0".length > 0 ? "0.39.0" : "dev";
68484
+ var BUILDER_VERSION = "0.39.1".length > 0 ? "0.39.1" : "dev";
68374
68485
 
68375
68486
  // src/api/diagnostics.get.ts
68376
68487
  init_LLMRequest();