omoclaw 3.0.3 → 3.1.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.
- package/dist/batch-queue.d.ts +8 -0
- package/dist/handlers/user-events.d.ts +3 -1
- package/dist/index.js +123 -131
- package/package.json +1 -1
package/dist/batch-queue.d.ts
CHANGED
|
@@ -9,14 +9,22 @@ export interface BatchFlushPayload {
|
|
|
9
9
|
count: number;
|
|
10
10
|
summary: string;
|
|
11
11
|
}
|
|
12
|
+
export interface DoneRealtimeContext {
|
|
13
|
+
promptHistory: string[];
|
|
14
|
+
batchSummary: string;
|
|
15
|
+
}
|
|
12
16
|
type BatchFlushHandler = (payload: BatchFlushPayload) => void;
|
|
13
17
|
export declare class BatchQueue {
|
|
14
18
|
private readonly maxAgeMs;
|
|
15
19
|
private readonly onFlush;
|
|
16
20
|
private readonly entries;
|
|
21
|
+
private readonly userPromptHistory;
|
|
17
22
|
private maxAgeTimer;
|
|
18
23
|
constructor(maxAgeMs: number, onFlush: BatchFlushHandler);
|
|
19
24
|
enqueue(entry: BatchQueueEntry): void;
|
|
25
|
+
recordUserPrompt(text: string): void;
|
|
26
|
+
getDoneRealtimeContext(): DoneRealtimeContext;
|
|
27
|
+
clearDoneRealtimeState(): void;
|
|
20
28
|
flush(reason: BatchFlushReason): BatchFlushPayload | undefined;
|
|
21
29
|
size(): number;
|
|
22
30
|
dispose(): void;
|
|
@@ -3,7 +3,9 @@ import type { MonitorEventType } from "../config";
|
|
|
3
3
|
import type { DedupEngine } from "../dedup";
|
|
4
4
|
import type { SessionStateTracker } from "../state";
|
|
5
5
|
interface UserEventNotifier {
|
|
6
|
-
(eventType: MonitorEventType, text: string, agentName?: string
|
|
6
|
+
(eventType: MonitorEventType, text: string, agentName?: string, options?: {
|
|
7
|
+
promptText?: string;
|
|
8
|
+
}): void;
|
|
7
9
|
}
|
|
8
10
|
export declare function handleUserEvent(event: Event, dedup: DedupEngine, state: SessionStateTracker, notify: UserEventNotifier): void;
|
|
9
11
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import os3 from "os";
|
|
|
5
5
|
import path5 from "path";
|
|
6
6
|
|
|
7
7
|
// src/batch-queue.ts
|
|
8
|
+
var USER_PROMPT_HISTORY_LIMIT = 5;
|
|
8
9
|
function formatCounts(counts) {
|
|
9
10
|
return [...counts.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, count]) => `${name} \xD7${count}`).join(", ");
|
|
10
11
|
}
|
|
@@ -12,11 +13,36 @@ function normalizedAgentName(agentName) {
|
|
|
12
13
|
const trimmed = agentName?.trim();
|
|
13
14
|
return trimmed ? trimmed : "unknown-agent";
|
|
14
15
|
}
|
|
16
|
+
function summarizeEntries(entries, includeCompletedSuffix) {
|
|
17
|
+
const doneByAgent = new Map;
|
|
18
|
+
const byEventType = new Map;
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.eventType === "done") {
|
|
21
|
+
const agent = normalizedAgentName(entry.agentName);
|
|
22
|
+
doneByAgent.set(agent, (doneByAgent.get(agent) ?? 0) + 1);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
byEventType.set(entry.eventType, (byEventType.get(entry.eventType) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
const segments = [];
|
|
28
|
+
if (doneByAgent.size > 0) {
|
|
29
|
+
const doneSummary = formatCounts(doneByAgent);
|
|
30
|
+
segments.push(includeCompletedSuffix ? `${doneSummary} completed` : doneSummary);
|
|
31
|
+
}
|
|
32
|
+
for (const [eventType, count] of [...byEventType.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
33
|
+
segments.push(`${eventType} \xD7${count}`);
|
|
34
|
+
}
|
|
35
|
+
return segments.join(", ");
|
|
36
|
+
}
|
|
37
|
+
function normalizePromptText(text) {
|
|
38
|
+
return text.replace(/\s+/g, " ").trim();
|
|
39
|
+
}
|
|
15
40
|
|
|
16
41
|
class BatchQueue {
|
|
17
42
|
maxAgeMs;
|
|
18
43
|
onFlush;
|
|
19
44
|
entries = [];
|
|
45
|
+
userPromptHistory = [];
|
|
20
46
|
maxAgeTimer;
|
|
21
47
|
constructor(maxAgeMs, onFlush) {
|
|
22
48
|
this.maxAgeMs = maxAgeMs;
|
|
@@ -31,33 +57,39 @@ class BatchQueue {
|
|
|
31
57
|
this.maxAgeTimer.unref?.();
|
|
32
58
|
}
|
|
33
59
|
}
|
|
60
|
+
recordUserPrompt(text) {
|
|
61
|
+
const normalized = normalizePromptText(text);
|
|
62
|
+
if (!normalized) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.userPromptHistory.push(normalized);
|
|
66
|
+
if (this.userPromptHistory.length > USER_PROMPT_HISTORY_LIMIT) {
|
|
67
|
+
const deleteCount = this.userPromptHistory.length - USER_PROMPT_HISTORY_LIMIT;
|
|
68
|
+
this.userPromptHistory.splice(0, deleteCount);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
getDoneRealtimeContext() {
|
|
72
|
+
return {
|
|
73
|
+
promptHistory: [...this.userPromptHistory],
|
|
74
|
+
batchSummary: summarizeEntries(this.entries, false)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
clearDoneRealtimeState() {
|
|
78
|
+
this.entries.length = 0;
|
|
79
|
+
this.userPromptHistory.length = 0;
|
|
80
|
+
this.clearTimer();
|
|
81
|
+
}
|
|
34
82
|
flush(reason) {
|
|
35
83
|
if (this.entries.length === 0) {
|
|
36
84
|
return;
|
|
37
85
|
}
|
|
38
86
|
const drained = this.entries.splice(0, this.entries.length);
|
|
39
87
|
this.clearTimer();
|
|
40
|
-
const
|
|
41
|
-
const byEventType = new Map;
|
|
42
|
-
for (const entry of drained) {
|
|
43
|
-
if (entry.eventType === "done") {
|
|
44
|
-
const agent = normalizedAgentName(entry.agentName);
|
|
45
|
-
doneByAgent.set(agent, (doneByAgent.get(agent) ?? 0) + 1);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
byEventType.set(entry.eventType, (byEventType.get(entry.eventType) ?? 0) + 1);
|
|
49
|
-
}
|
|
50
|
-
const segments = [];
|
|
51
|
-
if (doneByAgent.size > 0) {
|
|
52
|
-
segments.push(`${formatCounts(doneByAgent)} completed`);
|
|
53
|
-
}
|
|
54
|
-
for (const [eventType, count] of [...byEventType.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
55
|
-
segments.push(`${eventType} \xD7${count}`);
|
|
56
|
-
}
|
|
88
|
+
const summaryBody = summarizeEntries(drained, true);
|
|
57
89
|
const payload = {
|
|
58
90
|
reason,
|
|
59
91
|
count: drained.length,
|
|
60
|
-
summary: `[batch] ${
|
|
92
|
+
summary: summaryBody ? `[batch] ${summaryBody}` : "[batch]"
|
|
61
93
|
};
|
|
62
94
|
this.onFlush(payload);
|
|
63
95
|
return payload;
|
|
@@ -68,6 +100,7 @@ class BatchQueue {
|
|
|
68
100
|
dispose() {
|
|
69
101
|
this.clearTimer();
|
|
70
102
|
this.entries.length = 0;
|
|
103
|
+
this.userPromptHistory.length = 0;
|
|
71
104
|
}
|
|
72
105
|
clearTimer() {
|
|
73
106
|
if (!this.maxAgeTimer) {
|
|
@@ -974,142 +1007,80 @@ function asObject3(value) {
|
|
|
974
1007
|
function asString(value) {
|
|
975
1008
|
return typeof value === "string" ? value : "";
|
|
976
1009
|
}
|
|
977
|
-
function collectContentText(value, output) {
|
|
978
|
-
if (typeof value === "string") {
|
|
979
|
-
output.push(value);
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
if (Array.isArray(value)) {
|
|
983
|
-
for (const item of value) {
|
|
984
|
-
collectContentText(item, output);
|
|
985
|
-
}
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
const record = asObject3(value);
|
|
989
|
-
if (!record) {
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
if (typeof record.text === "string") {
|
|
993
|
-
output.push(record.text);
|
|
994
|
-
}
|
|
995
|
-
if (typeof record.content === "string") {
|
|
996
|
-
output.push(record.content);
|
|
997
|
-
} else if (Array.isArray(record.content)) {
|
|
998
|
-
collectContentText(record.content, output);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
function collectPartsText(value, output) {
|
|
1002
|
-
if (!Array.isArray(value)) {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
for (const item of value) {
|
|
1006
|
-
const part = asObject3(item);
|
|
1007
|
-
if (!part) {
|
|
1008
|
-
continue;
|
|
1009
|
-
}
|
|
1010
|
-
const partType = asString(part.type);
|
|
1011
|
-
if (partType === "text" && typeof part.text === "string") {
|
|
1012
|
-
output.push(part.text);
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
if (typeof part.text === "string") {
|
|
1016
|
-
output.push(part.text);
|
|
1017
|
-
}
|
|
1018
|
-
if (typeof part.content === "string") {
|
|
1019
|
-
output.push(part.content);
|
|
1020
|
-
} else if (Array.isArray(part.content)) {
|
|
1021
|
-
collectContentText(part.content, output);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
1010
|
function normalizePreviewText(input) {
|
|
1026
1011
|
return input.replace(/\s+/g, " ").trim();
|
|
1027
1012
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
candidates.push(summary.body);
|
|
1037
|
-
}
|
|
1013
|
+
|
|
1014
|
+
class PromptBuffer {
|
|
1015
|
+
buffer = "";
|
|
1016
|
+
append(text) {
|
|
1017
|
+
this.buffer += text;
|
|
1018
|
+
}
|
|
1019
|
+
clear() {
|
|
1020
|
+
this.buffer = "";
|
|
1038
1021
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1022
|
+
flush() {
|
|
1023
|
+
const text = normalizePreviewText(this.buffer);
|
|
1024
|
+
this.buffer = "";
|
|
1025
|
+
return text.slice(0, PROMPT_PREVIEW_MAX);
|
|
1041
1026
|
}
|
|
1042
|
-
collectContentText(info.content, candidates);
|
|
1043
|
-
collectPartsText(info.parts, candidates);
|
|
1044
|
-
const merged = normalizePreviewText(candidates.join(" "));
|
|
1045
|
-
return merged.slice(0, PROMPT_PREVIEW_MAX);
|
|
1046
1027
|
}
|
|
1028
|
+
var promptBuffer = new PromptBuffer;
|
|
1047
1029
|
function handleUserEvent(event, dedup, state, notify) {
|
|
1048
1030
|
if (event.type === "session.created" || event.type === "session.updated") {
|
|
1049
|
-
const
|
|
1050
|
-
if (!
|
|
1031
|
+
const info = asObject3(event.properties.info);
|
|
1032
|
+
if (!info)
|
|
1051
1033
|
return;
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
if (!sessionID2) {
|
|
1034
|
+
const sessionID = asString(info.id);
|
|
1035
|
+
if (!sessionID)
|
|
1055
1036
|
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);
|
|
1056
1045
|
}
|
|
1057
|
-
const parentID = asString(info2.parentID);
|
|
1058
|
-
state.setParentID(sessionID2, parentID || undefined);
|
|
1059
1046
|
return;
|
|
1060
1047
|
}
|
|
1061
1048
|
if (event.type === "tui.command.execute") {
|
|
1062
1049
|
const command = event.properties.command;
|
|
1063
|
-
if (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");
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
if (command === "prompt.clear") {
|
|
1058
|
+
promptBuffer.clear();
|
|
1064
1059
|
return;
|
|
1065
1060
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
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 });
|
|
1068
1069
|
return;
|
|
1069
1070
|
}
|
|
1070
|
-
notify("user-interrupt", "[OpenCode] User interrupt requested");
|
|
1071
1071
|
return;
|
|
1072
1072
|
}
|
|
1073
1073
|
if (event.type === "session.error") {
|
|
1074
|
-
const
|
|
1075
|
-
const
|
|
1076
|
-
if (!
|
|
1074
|
+
const sessionID = event.properties.sessionID;
|
|
1075
|
+
const errorObj = event.properties.error;
|
|
1076
|
+
if (!sessionID || errorObj?.name !== "MessageAbortedError")
|
|
1077
1077
|
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
if (!dedup.shouldSend(dedupKey2)) {
|
|
1078
|
+
const dedupKey = `user-interrupt:session-error:${sessionID}`;
|
|
1079
|
+
if (!dedup.shouldSend(dedupKey))
|
|
1081
1080
|
return;
|
|
1082
|
-
}
|
|
1083
|
-
notify("user-interrupt", `[OpenCode] User interrupt (${shortSessionID3(sessionID2)})`, state.getAgent(sessionID2));
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
if (event.type !== "message.updated") {
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
const info = asObject3(event.properties.info);
|
|
1090
|
-
if (!info) {
|
|
1081
|
+
notify("user-interrupt", `[OpenCode] User interrupt (${shortSessionID3(sessionID)})`, state.getAgent(sessionID));
|
|
1091
1082
|
return;
|
|
1092
1083
|
}
|
|
1093
|
-
if (asString(info?.role) !== "user") {
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
const sessionID = asString(info?.sessionID);
|
|
1097
|
-
if (!sessionID) {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
if (state.hasParentID(sessionID)) {
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
const promptPreview = extractPromptPreview(info);
|
|
1104
|
-
if (!promptPreview) {
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
const messageID = asString(info?.id);
|
|
1108
|
-
const dedupKey = messageID ? `user-prompt:message:${messageID}` : `user-prompt:session:${sessionID}`;
|
|
1109
|
-
if (!dedup.shouldSend(dedupKey)) {
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
notify("user-prompt", `[OpenCode] User prompt submitted (${shortSessionID3(sessionID)}): ${promptPreview}`, state.getAgent(sessionID));
|
|
1113
1084
|
}
|
|
1114
1085
|
|
|
1115
1086
|
// src/project-config.ts
|
|
@@ -1723,6 +1694,20 @@ function buildPrefix(config) {
|
|
|
1723
1694
|
const project = path5.basename(process.cwd());
|
|
1724
1695
|
return `[OpenCode@${host}:${project}]`;
|
|
1725
1696
|
}
|
|
1697
|
+
function quotePrompt(text) {
|
|
1698
|
+
return `"${text.replace(/"/g, "\\\"")}"`;
|
|
1699
|
+
}
|
|
1700
|
+
function appendDoneRealtimeContext(baseText, context) {
|
|
1701
|
+
const lines = [baseText];
|
|
1702
|
+
if (context.promptHistory.length > 0) {
|
|
1703
|
+
lines.push(`- Prompts: ${context.promptHistory.map(quotePrompt).join(" -> ")}`);
|
|
1704
|
+
}
|
|
1705
|
+
if (context.batchSummary) {
|
|
1706
|
+
lines.push(`- Batch: ${context.batchSummary}`);
|
|
1707
|
+
}
|
|
1708
|
+
return lines.join(`
|
|
1709
|
+
`);
|
|
1710
|
+
}
|
|
1726
1711
|
var SessionMonitorPlugin = async (ctx) => {
|
|
1727
1712
|
const config = loadConfig();
|
|
1728
1713
|
const projectConfig = loadProjectConfig();
|
|
@@ -1782,7 +1767,10 @@ var SessionMonitorPlugin = async (ctx) => {
|
|
|
1782
1767
|
const batchQueue = new BatchQueue(config.batch.maxAgeMs, (payload) => {
|
|
1783
1768
|
sendBatchSummary(payload.summary, payload.reason, payload.count);
|
|
1784
1769
|
});
|
|
1785
|
-
const notifyByPolicy = (eventType, text, agentName) => {
|
|
1770
|
+
const notifyByPolicy = (eventType, text, agentName, options) => {
|
|
1771
|
+
if (eventType === "user-prompt" && options?.promptText) {
|
|
1772
|
+
batchQueue.recordUserPrompt(options.promptText);
|
|
1773
|
+
}
|
|
1786
1774
|
if (runtimeControl.isPaused()) {
|
|
1787
1775
|
debugLog?.(`Suppressed event=${eventType} paused=true`);
|
|
1788
1776
|
return;
|
|
@@ -1800,10 +1788,14 @@ var SessionMonitorPlugin = async (ctx) => {
|
|
|
1800
1788
|
debugLog?.(`Queued batch event=${eventType} agent=${agentName ?? ""}`);
|
|
1801
1789
|
return;
|
|
1802
1790
|
}
|
|
1803
|
-
sendRealtimeWebhook(text, eventType);
|
|
1804
1791
|
if (eventType === "done") {
|
|
1805
|
-
batchQueue.
|
|
1792
|
+
const doneContext = batchQueue.getDoneRealtimeContext();
|
|
1793
|
+
const doneText = appendDoneRealtimeContext(text, doneContext);
|
|
1794
|
+
sendRealtimeWebhook(doneText, eventType);
|
|
1795
|
+
batchQueue.clearDoneRealtimeState();
|
|
1796
|
+
return;
|
|
1806
1797
|
}
|
|
1798
|
+
sendRealtimeWebhook(text, eventType);
|
|
1807
1799
|
};
|
|
1808
1800
|
const timers = new TimerManager(config, sendRealtimeWebhook);
|
|
1809
1801
|
const flushBatchNow = () => {
|
package/package.json
CHANGED