omoclaw 3.1.1 → 3.2.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.
Files changed (2) hide show
  1. package/dist/index.js +92 -101
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -995,6 +995,7 @@ function handleSessionStatusEvent(event, dedup, state, timers, webhookFn, notify
995
995
 
996
996
  // src/handlers/user-events.ts
997
997
  var PROMPT_PREVIEW_MAX = 200;
998
+ var PROMPT_FLUSH_DELAY_MS = 2000;
998
999
  function shortSessionID3(sessionID) {
999
1000
  return sessionID.slice(0, 12);
1000
1001
  }
@@ -1007,86 +1008,43 @@ function asObject3(value) {
1007
1008
  function asString(value) {
1008
1009
  return typeof value === "string" ? value : "";
1009
1010
  }
1010
- function collectContentText(value, output) {
1011
- if (typeof value === "string") {
1012
- output.push(value);
1013
- return;
1014
- }
1015
- if (Array.isArray(value)) {
1016
- for (const item of value) {
1017
- collectContentText(item, output);
1018
- }
1019
- return;
1020
- }
1021
- const record = asObject3(value);
1022
- if (!record) {
1023
- return;
1024
- }
1025
- if (typeof record.text === "string") {
1026
- output.push(record.text);
1027
- }
1028
- if (typeof record.content === "string") {
1029
- output.push(record.content);
1030
- } else if (Array.isArray(record.content)) {
1031
- collectContentText(record.content, output);
1032
- }
1033
- }
1034
- function collectPartsText(value, output) {
1035
- if (!Array.isArray(value)) {
1036
- return;
1037
- }
1038
- for (const item of value) {
1039
- const part = asObject3(item);
1040
- if (!part) {
1041
- continue;
1042
- }
1043
- const partType = asString(part.type);
1044
- if (partType === "text" && typeof part.text === "string") {
1045
- output.push(part.text);
1046
- continue;
1047
- }
1048
- if (typeof part.text === "string") {
1049
- output.push(part.text);
1050
- }
1051
- if (typeof part.content === "string") {
1052
- output.push(part.content);
1053
- } else if (Array.isArray(part.content)) {
1054
- collectContentText(part.content, output);
1055
- }
1056
- }
1057
- }
1058
1011
  function normalizePreviewText(input) {
1059
1012
  return input.replace(/\s+/g, " ").trim();
1060
1013
  }
1061
- function extractPromptPreview(info) {
1062
- const candidates = [];
1063
- const summary = asObject3(info.summary);
1064
- if (summary) {
1065
- if (typeof summary.title === "string") {
1066
- candidates.push(summary.title);
1067
- }
1068
- if (typeof summary.body === "string") {
1069
- candidates.push(summary.body);
1070
- }
1071
- }
1072
- if (typeof info.text === "string") {
1073
- candidates.push(info.text);
1014
+ var seenMessageIDs = new Set;
1015
+ var SEEN_MAX = 5000;
1016
+ var pendingPrompts = new Map;
1017
+ function evictOldestSeen() {
1018
+ if (seenMessageIDs.size <= SEEN_MAX)
1019
+ return;
1020
+ const first = seenMessageIDs.values().next().value;
1021
+ if (first !== undefined)
1022
+ seenMessageIDs.delete(first);
1023
+ }
1024
+ function flushPendingPrompt(messageID, notify, debugLog) {
1025
+ const pending = pendingPrompts.get(messageID);
1026
+ if (!pending)
1027
+ return;
1028
+ clearTimeout(pending.timer);
1029
+ pendingPrompts.delete(messageID);
1030
+ seenMessageIDs.add(messageID);
1031
+ evictOldestSeen();
1032
+ const preview = normalizePreviewText(pending.text).slice(0, PROMPT_PREVIEW_MAX);
1033
+ if (!preview) {
1034
+ debugLog?.(`User prompt SKIPPED (no text after flush): session=${shortSessionID3(pending.sessionID)} msg=${messageID}`);
1035
+ return;
1074
1036
  }
1075
- collectContentText(info.content, candidates);
1076
- collectPartsText(info.parts, candidates);
1077
- const merged = normalizePreviewText(candidates.join(" "));
1078
- return merged.slice(0, PROMPT_PREVIEW_MAX);
1037
+ debugLog?.(`User prompt FLUSHED: session=${shortSessionID3(pending.sessionID)} msg=${messageID} text="${preview.slice(0, 60)}..."`);
1038
+ notify("user-prompt", `[OpenCode] User prompt submitted (${shortSessionID3(pending.sessionID)}): ${preview}`, pending.agent, { promptText: preview });
1079
1039
  }
1080
1040
  function handleUserEvent(event, dedup, state, notify, debugLog) {
1081
1041
  if (event.type === "session.created" || event.type === "session.updated") {
1082
1042
  const info2 = asObject3(event.properties.info);
1083
- if (!info2) {
1043
+ if (!info2)
1084
1044
  return;
1085
- }
1086
1045
  const sessionID2 = asString(info2.id);
1087
- if (!sessionID2) {
1046
+ if (!sessionID2)
1088
1047
  return;
1089
- }
1090
1048
  const parentID = asString(info2.parentID);
1091
1049
  if (parentID) {
1092
1050
  state.setParentID(sessionID2, parentID);
@@ -1096,61 +1054,94 @@ function handleUserEvent(event, dedup, state, notify, debugLog) {
1096
1054
  }
1097
1055
  if (event.type === "tui.command.execute") {
1098
1056
  const command = event.properties.command;
1099
- if (command !== "session.interrupt") {
1057
+ if (command !== "session.interrupt")
1100
1058
  return;
1101
- }
1102
- const dedupKey2 = "user-interrupt:tui-command";
1103
- if (!dedup.shouldSend(dedupKey2)) {
1059
+ const dedupKey = "user-interrupt:tui-command";
1060
+ if (!dedup.shouldSend(dedupKey))
1104
1061
  return;
1105
- }
1106
1062
  notify("user-interrupt", "[OpenCode] User interrupt requested");
1107
1063
  return;
1108
1064
  }
1109
1065
  if (event.type === "session.error") {
1110
1066
  const sessionID2 = event.properties.sessionID;
1111
- const errorName = event.properties.error?.name;
1112
- if (!sessionID2 || errorName !== "MessageAbortedError") {
1067
+ const errorObj = event.properties.error;
1068
+ if (!sessionID2 || errorObj?.name !== "MessageAbortedError")
1113
1069
  return;
1114
- }
1115
- const dedupKey2 = `user-interrupt:session-error:${sessionID2}`;
1116
- if (!dedup.shouldSend(dedupKey2)) {
1070
+ const dedupKey = `user-interrupt:session-error:${sessionID2}`;
1071
+ if (!dedup.shouldSend(dedupKey))
1117
1072
  return;
1118
- }
1119
1073
  notify("user-interrupt", `[OpenCode] User interrupt (${shortSessionID3(sessionID2)})`, state.getAgent(sessionID2));
1120
1074
  return;
1121
1075
  }
1122
- if (event.type !== "message.updated") {
1076
+ if (event.type === "message.part.updated") {
1077
+ const part = asObject3(event.properties.part);
1078
+ if (!part)
1079
+ return;
1080
+ const messageID2 = asString(part.messageID);
1081
+ if (!messageID2)
1082
+ return;
1083
+ const pending = pendingPrompts.get(messageID2);
1084
+ if (!pending)
1085
+ return;
1086
+ if (asString(part.type) !== "text")
1087
+ return;
1088
+ if (part.synthetic === true) {
1089
+ debugLog?.(`User prompt part SKIPPED (synthetic): msg=${messageID2}`);
1090
+ seenMessageIDs.add(messageID2);
1091
+ evictOldestSeen();
1092
+ clearTimeout(pending.timer);
1093
+ pendingPrompts.delete(messageID2);
1094
+ return;
1095
+ }
1096
+ const partText = asString(part.text);
1097
+ if (!partText)
1098
+ return;
1099
+ const trimmed = partText.trimStart();
1100
+ if (trimmed.startsWith("<system-reminder>") || trimmed.startsWith("<system")) {
1101
+ debugLog?.(`User prompt part SKIPPED (system-injected): msg=${messageID2} text="${trimmed.slice(0, 60)}"`);
1102
+ seenMessageIDs.add(messageID2);
1103
+ evictOldestSeen();
1104
+ clearTimeout(pending.timer);
1105
+ pendingPrompts.delete(messageID2);
1106
+ return;
1107
+ }
1108
+ pending.text += (pending.text ? " " : "") + partText;
1109
+ flushPendingPrompt(messageID2, notify, debugLog);
1123
1110
  return;
1124
1111
  }
1112
+ if (event.type !== "message.updated")
1113
+ return;
1125
1114
  const info = asObject3(event.properties.info);
1126
- if (!info) {
1115
+ if (!info)
1127
1116
  return;
1128
- }
1129
- if (asString(info.role) !== "user") {
1117
+ if (asString(info.role) !== "user")
1130
1118
  return;
1131
- }
1132
1119
  const sessionID = asString(info.sessionID);
1133
- if (!sessionID) {
1120
+ if (!sessionID)
1134
1121
  return;
1135
- }
1136
- if (state.hasParentID(sessionID)) {
1137
- debugLog?.(`User prompt SKIPPED (sub-agent): session=${shortSessionID3(sessionID)} parent=${state.getState(sessionID)?.parentID ?? "?"}`);
1122
+ const messageID = asString(info.id);
1123
+ if (!messageID)
1138
1124
  return;
1139
- }
1140
- const promptPreview = extractPromptPreview(info);
1141
- if (!promptPreview) {
1142
- debugLog?.(`User prompt SKIPPED (empty preview): session=${shortSessionID3(sessionID)}`);
1125
+ if (seenMessageIDs.has(messageID))
1143
1126
  return;
1144
- }
1145
- const messageID = asString(info.id);
1146
- const dedupKey = messageID ? `user-prompt:message:${messageID}` : `user-prompt:session:${sessionID}`;
1147
- if (!dedup.shouldSend(dedupKey)) {
1148
- debugLog?.(`User prompt SKIPPED (dedup): session=${shortSessionID3(sessionID)} key=${dedupKey}`);
1127
+ if (pendingPrompts.has(messageID))
1128
+ return;
1129
+ if (state.hasParentID(sessionID)) {
1130
+ debugLog?.(`User prompt SKIPPED (sub-agent): session=${shortSessionID3(sessionID)} msg=${messageID}`);
1131
+ seenMessageIDs.add(messageID);
1132
+ evictOldestSeen();
1149
1133
  return;
1150
1134
  }
1151
- debugLog?.(`User prompt DETECTED: session=${shortSessionID3(sessionID)} preview="${promptPreview.slice(0, 60)}..."`);
1152
- notify("user-prompt", `[OpenCode] User prompt submitted (${shortSessionID3(sessionID)}): ${promptPreview}`, state.getAgent(sessionID), {
1153
- promptText: promptPreview
1135
+ debugLog?.(`User prompt PENDING: session=${shortSessionID3(sessionID)} msg=${messageID} (waiting for text parts)`);
1136
+ const timer = setTimeout(() => {
1137
+ flushPendingPrompt(messageID, notify, debugLog);
1138
+ }, PROMPT_FLUSH_DELAY_MS);
1139
+ pendingPrompts.set(messageID, {
1140
+ sessionID,
1141
+ messageID,
1142
+ agent: state.getAgent(sessionID),
1143
+ text: "",
1144
+ timer
1154
1145
  });
1155
1146
  }
1156
1147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omoclaw",
3
- "version": "3.1.1",
3
+ "version": "3.2.1",
4
4
  "description": "OpenCode session monitor plugin — native event-driven webhook notifications for session state changes",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",