omoclaw 3.1.0 → 3.2.0

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.
@@ -7,5 +7,6 @@ interface UserEventNotifier {
7
7
  promptText?: string;
8
8
  }): void;
9
9
  }
10
- export declare function handleUserEvent(event: Event, dedup: DedupEngine, state: SessionStateTracker, notify: UserEventNotifier): void;
10
+ type DebugLogger = ((msg: string) => void) | undefined;
11
+ export declare function handleUserEvent(event: Event, dedup: DedupEngine, state: SessionStateTracker, notify: UserEventNotifier, debugLog?: DebugLogger): void;
11
12
  export {};
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
  }
@@ -1010,77 +1011,129 @@ function asString(value) {
1010
1011
  function normalizePreviewText(input) {
1011
1012
  return input.replace(/\s+/g, " ").trim();
1012
1013
  }
1013
-
1014
- class PromptBuffer {
1015
- buffer = "";
1016
- append(text) {
1017
- this.buffer += text;
1018
- }
1019
- clear() {
1020
- this.buffer = "";
1021
- }
1022
- flush() {
1023
- const text = normalizePreviewText(this.buffer);
1024
- this.buffer = "";
1025
- return text.slice(0, PROMPT_PREVIEW_MAX);
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;
1026
1036
  }
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 });
1027
1039
  }
1028
- var promptBuffer = new PromptBuffer;
1029
- function handleUserEvent(event, dedup, state, notify) {
1040
+ function handleUserEvent(event, dedup, state, notify, debugLog) {
1030
1041
  if (event.type === "session.created" || event.type === "session.updated") {
1031
- const info = asObject3(event.properties.info);
1032
- if (!info)
1042
+ const info2 = asObject3(event.properties.info);
1043
+ if (!info2)
1033
1044
  return;
1034
- const sessionID = asString(info.id);
1035
- if (!sessionID)
1045
+ const sessionID2 = asString(info2.id);
1046
+ if (!sessionID2)
1036
1047
  return;
1037
- const parentID = asString(info.parentID);
1038
- state.setParentID(sessionID, parentID || undefined);
1039
- return;
1040
- }
1041
- if (event.type === "tui.prompt.append") {
1042
- const text = event.properties.text;
1043
- if (typeof text === "string") {
1044
- promptBuffer.append(text);
1048
+ const parentID = asString(info2.parentID);
1049
+ if (parentID) {
1050
+ state.setParentID(sessionID2, parentID);
1051
+ debugLog?.(`ParentID tracked: ${shortSessionID3(sessionID2)} \u2192 parent=${shortSessionID3(parentID)}`);
1045
1052
  }
1046
1053
  return;
1047
1054
  }
1048
1055
  if (event.type === "tui.command.execute") {
1049
1056
  const command = event.properties.command;
1050
- if (command === "session.interrupt") {
1051
- const dedupKey = "user-interrupt:tui-command";
1052
- if (!dedup.shouldSend(dedupKey))
1053
- return;
1054
- notify("user-interrupt", "[OpenCode] User interrupt requested");
1057
+ if (command !== "session.interrupt")
1055
1058
  return;
1056
- }
1057
- if (command === "prompt.clear") {
1058
- promptBuffer.clear();
1059
- return;
1060
- }
1061
- if (command === "prompt.submit") {
1062
- const preview = promptBuffer.flush();
1063
- if (!preview)
1064
- return;
1065
- const dedupKey = `user-prompt:submit:${Date.now()}`;
1066
- if (!dedup.shouldSend(dedupKey))
1067
- return;
1068
- notify("user-prompt", `[OpenCode] User prompt submitted: ${preview}`, undefined, { promptText: preview });
1059
+ const dedupKey = "user-interrupt:tui-command";
1060
+ if (!dedup.shouldSend(dedupKey))
1069
1061
  return;
1070
- }
1062
+ notify("user-interrupt", "[OpenCode] User interrupt requested");
1071
1063
  return;
1072
1064
  }
1073
1065
  if (event.type === "session.error") {
1074
- const sessionID = event.properties.sessionID;
1066
+ const sessionID2 = event.properties.sessionID;
1075
1067
  const errorObj = event.properties.error;
1076
- if (!sessionID || errorObj?.name !== "MessageAbortedError")
1068
+ if (!sessionID2 || errorObj?.name !== "MessageAbortedError")
1077
1069
  return;
1078
- const dedupKey = `user-interrupt:session-error:${sessionID}`;
1070
+ const dedupKey = `user-interrupt:session-error:${sessionID2}`;
1079
1071
  if (!dedup.shouldSend(dedupKey))
1080
1072
  return;
1081
- notify("user-interrupt", `[OpenCode] User interrupt (${shortSessionID3(sessionID)})`, state.getAgent(sessionID));
1073
+ notify("user-interrupt", `[OpenCode] User interrupt (${shortSessionID3(sessionID2)})`, state.getAgent(sessionID2));
1082
1074
  return;
1083
1075
  }
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
+ pending.text += (pending.text ? " " : "") + partText;
1100
+ flushPendingPrompt(messageID2, notify, debugLog);
1101
+ return;
1102
+ }
1103
+ if (event.type !== "message.updated")
1104
+ return;
1105
+ const info = asObject3(event.properties.info);
1106
+ if (!info)
1107
+ return;
1108
+ if (asString(info.role) !== "user")
1109
+ return;
1110
+ const sessionID = asString(info.sessionID);
1111
+ if (!sessionID)
1112
+ return;
1113
+ const messageID = asString(info.id);
1114
+ if (!messageID)
1115
+ return;
1116
+ if (seenMessageIDs.has(messageID))
1117
+ return;
1118
+ if (pendingPrompts.has(messageID))
1119
+ return;
1120
+ if (state.hasParentID(sessionID)) {
1121
+ debugLog?.(`User prompt SKIPPED (sub-agent): session=${shortSessionID3(sessionID)} msg=${messageID}`);
1122
+ seenMessageIDs.add(messageID);
1123
+ evictOldestSeen();
1124
+ return;
1125
+ }
1126
+ debugLog?.(`User prompt PENDING: session=${shortSessionID3(sessionID)} msg=${messageID} (waiting for text parts)`);
1127
+ const timer = setTimeout(() => {
1128
+ flushPendingPrompt(messageID, notify, debugLog);
1129
+ }, PROMPT_FLUSH_DELAY_MS);
1130
+ pendingPrompts.set(messageID, {
1131
+ sessionID,
1132
+ messageID,
1133
+ agent: state.getAgent(sessionID),
1134
+ text: "",
1135
+ timer
1136
+ });
1084
1137
  }
1085
1138
 
1086
1139
  // src/project-config.ts
@@ -1819,7 +1872,7 @@ var SessionMonitorPlugin = async (ctx) => {
1819
1872
  event: async ({ event }) => {
1820
1873
  debugLog?.(`EVENT: ${event.type} props=${JSON.stringify(event.properties).slice(0, 200)}`);
1821
1874
  handleAgentEvent(event, state);
1822
- handleUserEvent(event, dedup, state, notifyByPolicy);
1875
+ handleUserEvent(event, dedup, state, notifyByPolicy, debugLog);
1823
1876
  handlePermissionEvent(event, dedup, timers, state, notifyByPolicy);
1824
1877
  handleQuestionEvent(event, dedup, timers, state, notifyByPolicy);
1825
1878
  handleSessionStatusEvent(event, dedup, state, timers, sendRealtimeWebhook, notifyByPolicy, debugLog);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omoclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
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",