omoclaw 3.2.2 → 3.2.4

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/README.md CHANGED
@@ -25,36 +25,12 @@ Create a configuration file at `~/.config/opencode/opencode-monitor.json`:
25
25
  {
26
26
  "webhook": {
27
27
  "url": "http://your-webhook-endpoint/hooks/wake",
28
- "token": "your-bearer-token",
29
- "channelType": "discord",
30
- "chatType": "channel",
31
- "agentId": "main",
32
- "channel": "",
33
- "sessionKey": ""
28
+ "token": "your-bearer-token"
34
29
  },
35
- "hostLabel": "",
36
30
  "staleTimeoutMs": 900000,
37
31
  "permissionReminderMs": 120000,
38
32
  "dedupTtlMs": 30000,
39
33
  "idleConfirmDelayMs": 1500,
40
- "batch": {
41
- "maxAgeMs": 600000
42
- },
43
- "events": {
44
- "done": {
45
- "default": "realtime",
46
- "agents": {
47
- "explore": "batch",
48
- "oracle": "batch",
49
- "librarian": "off"
50
- }
51
- },
52
- "error": { "default": "realtime", "agents": {} },
53
- "permission": { "default": "realtime", "agents": {} },
54
- "question": { "default": "realtime", "agents": {} },
55
- "user-interrupt": { "default": "realtime", "agents": {} },
56
- "user-prompt": { "default": "off", "agents": {} }
57
- },
58
34
  "debug": false,
59
35
  "enabled": true
60
36
  }
@@ -64,13 +40,10 @@ Create a configuration file at `~/.config/opencode/opencode-monitor.json`:
64
40
 
65
41
  - **webhook.url**: The endpoint where notifications will be sent.
66
42
  - **webhook.token**: Bearer token for authentication.
67
- - **hostLabel**: Optional host label used in notification prefix.
68
43
  - **staleTimeoutMs**: Timeout for busy sessions (default: 15 minutes).
69
44
  - **permissionReminderMs**: Interval to re-notify when waiting for permissions (default: 2 minutes).
70
45
  - **dedupTtlMs**: Time window for deduplicating similar notifications (default: 30 seconds).
71
46
  - **idleConfirmDelayMs**: Delay before confirming an idle state.
72
- - **batch.maxAgeMs**: Auto-flush age for batch queue (default: 10 minutes).
73
- - **events**: Event-level default modes and per-agent overrides (`off | realtime | batch`).
74
47
  - **debug**: Enable file logging to `/tmp/opencode-monitor-debug.log`.
75
48
  - **enabled**: Toggle the plugin on or off.
76
49
 
@@ -82,41 +55,6 @@ Create a configuration file at `~/.config/opencode/opencode-monitor.json`:
82
55
  - Stale session detection (default: 15 minutes)
83
56
  - Deduplication to prevent webhook spam
84
57
  - Completion notification with full agent chain
85
- - Runtime event control: `off | realtime | batch` per event and per agent
86
- - Batch queue with flush on realtime done event and max-age auto flush
87
- - `/wake` command support with runtime-only updates
88
- - Local control HTTP server (`127.0.0.1`, bearer auth, dynamic port file)
89
-
90
- ## Runtime Control
91
-
92
- ### `/wake` slash command
93
-
94
- `omoclaw` creates `.opencode/commands/wake.md` automatically at startup.
95
-
96
- Usage:
97
-
98
- - `/wake`
99
- - `/wake on`
100
- - `/wake off`
101
- - `/wake <event> <mode>`
102
- - `/wake <event> <agent> <mode>`
103
-
104
- All `/wake` changes are runtime-only (in-memory) and are not persisted.
105
-
106
- ### HTTP control API
107
-
108
- - Bind address: `127.0.0.1`
109
- - Port: auto-assigned (`port: 0`)
110
- - Port discovery file: `/tmp/omoclaw-<pid>.port`
111
- - Auth: `Authorization: Bearer <webhook.token>`
112
-
113
- Endpoints:
114
-
115
- - `GET /status`
116
- - `POST /config` (runtime partial patch)
117
- - `POST /pause`
118
- - `POST /resume`
119
- - `POST /flush`
120
58
 
121
59
  ## Webhook Payload Format
122
60
 
@@ -130,19 +68,10 @@ The plugin sends a POST request to the configured URL.
130
68
  ```json
131
69
  {
132
70
  "text": "[OpenCode] ...",
133
- "mode": "now",
134
- "metadata": {
135
- "correlationId": "..."
136
- }
71
+ "mode": "now"
137
72
  }
138
73
  ```
139
74
 
140
- Batch flush sends the same envelope with additional metadata:
141
-
142
- - `metadata.batch = true`
143
- - `metadata.batchReason = "done-realtime" | "max-age" | "manual"`
144
- - `metadata.batchCount = <number>`
145
-
146
75
  ## License
147
76
 
148
77
  MIT
package/dist/index.js CHANGED
@@ -1087,10 +1087,6 @@ function handleUserEvent(event, dedup, state, notify, debugLog) {
1087
1087
  return;
1088
1088
  if (part.synthetic === true) {
1089
1089
  debugLog?.(`User prompt part SKIPPED (synthetic): msg=${messageID2}`);
1090
- seenMessageIDs.add(messageID2);
1091
- evictOldestSeen();
1092
- clearTimeout(pending.timer);
1093
- pendingPrompts.delete(messageID2);
1094
1090
  return;
1095
1091
  }
1096
1092
  const partText = asString(part.text);
@@ -1224,16 +1220,15 @@ function resolveSessionRouting(config, projectConfig) {
1224
1220
  if (config.webhook.sessionKey) {
1225
1221
  const parsed = parseSessionKey(config.webhook.sessionKey);
1226
1222
  return {
1227
- sessionKey: config.webhook.sessionKey,
1223
+ sessionKey: "",
1228
1224
  source: "config",
1229
1225
  agentId: parsed.agentId || config.webhook.agentId,
1230
1226
  channelId: parsed.channelId || config.webhook.channel
1231
1227
  };
1232
1228
  }
1233
- const derivedSessionKey = buildSessionKeyFromConfig(config);
1234
- if (derivedSessionKey) {
1229
+ if (buildSessionKeyFromConfig(config)) {
1235
1230
  return {
1236
- sessionKey: derivedSessionKey,
1231
+ sessionKey: "",
1237
1232
  source: "derived",
1238
1233
  agentId: config.webhook.agentId,
1239
1234
  channelId: config.webhook.channel
@@ -1705,7 +1700,11 @@ function buildCorrelationId(text, provided) {
1705
1700
  function sendWebhook(config, text, options = {}) {
1706
1701
  const { webhook } = config;
1707
1702
  const envSessionKey = process.env.OPENCLAW_SESSION_KEY?.trim();
1708
- const sessionKey = envSessionKey || options.sessionKey?.trim() || resolveFallbackSessionKey(config);
1703
+ const sessionKey = envSessionKey || options.sessionKey?.trim() || "";
1704
+ if (!sessionKey) {
1705
+ wlog(`Skipping /hooks/wake (no explicit sessionKey, eventType: ${options.eventType ?? "unknown"})`);
1706
+ return;
1707
+ }
1709
1708
  const correlationId = buildCorrelationId(text, options.correlationId);
1710
1709
  const eventType = options.eventType?.trim();
1711
1710
  const body = {
@@ -1714,15 +1713,13 @@ function sendWebhook(config, text, options = {}) {
1714
1713
  metadata: {
1715
1714
  correlationId,
1716
1715
  ...options.metadata ?? {}
1717
- }
1716
+ },
1717
+ sessionKey
1718
1718
  };
1719
- if (sessionKey) {
1720
- body.sessionKey = sessionKey;
1721
- }
1722
1719
  if (eventType) {
1723
1720
  body.eventType = eventType;
1724
1721
  }
1725
- wlog(`Sending /hooks/wake to ${webhook.url} (session: ${sessionKey || "main"}, correlationId: ${correlationId}): ${text}`);
1722
+ wlog(`Sending /hooks/wake to ${webhook.url} (session: ${sessionKey}, correlationId: ${correlationId}): ${text}`);
1726
1723
  fetch(webhook.url, {
1727
1724
  method: "POST",
1728
1725
  headers: {
@@ -1777,7 +1774,7 @@ var SessionMonitorPlugin = async (ctx) => {
1777
1774
  const projectConfig = loadProjectConfig();
1778
1775
  const sessionRouting = resolveSessionRouting(config, projectConfig);
1779
1776
  const prefix = buildPrefix(config);
1780
- const wakeCommandPath = ensureWakeCommandFile(ctx.worktree || process.cwd());
1777
+ const wakeCommandPath = ensureWakeCommandFile((ctx.worktree && ctx.worktree !== "/") ? ctx.worktree : (process.env.HOME || process.cwd()));
1781
1778
  const debugLog = config.debug ? (msg) => {
1782
1779
  const line = `[${new Date().toISOString()}] ${msg}
1783
1780
  `;
@@ -1793,6 +1790,11 @@ var SessionMonitorPlugin = async (ctx) => {
1793
1790
  debugLog?.("Plugin disabled, exiting.");
1794
1791
  return {};
1795
1792
  }
1793
+ const hasExplicitRouting = sessionRouting.source === "env";
1794
+ if (!hasExplicitRouting || !sessionRouting.sessionKey) {
1795
+ debugLog?.(`No explicit session routing (source=${sessionRouting.source}); monitor will not send webhooks.`);
1796
+ return {};
1797
+ }
1796
1798
  const registry = new Registry({ debugLog });
1797
1799
  registry.start({
1798
1800
  hostLabel: config.hostLabel || os3.hostname(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omoclaw",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
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",
@@ -1,33 +0,0 @@
1
- import type { MonitorEventType } from "./config";
2
- export type BatchFlushReason = "done-realtime" | "max-age" | "manual";
3
- export interface BatchQueueEntry {
4
- eventType: MonitorEventType;
5
- agentName?: string;
6
- }
7
- export interface BatchFlushPayload {
8
- reason: BatchFlushReason;
9
- count: number;
10
- summary: string;
11
- }
12
- export interface DoneRealtimeContext {
13
- promptHistory: string[];
14
- batchSummary: string;
15
- }
16
- type BatchFlushHandler = (payload: BatchFlushPayload) => void;
17
- export declare class BatchQueue {
18
- private readonly maxAgeMs;
19
- private readonly onFlush;
20
- private readonly entries;
21
- private readonly userPromptHistory;
22
- private maxAgeTimer;
23
- constructor(maxAgeMs: number, onFlush: BatchFlushHandler);
24
- enqueue(entry: BatchQueueEntry): void;
25
- recordUserPrompt(text: string): void;
26
- getDoneRealtimeContext(): DoneRealtimeContext;
27
- clearDoneRealtimeState(): void;
28
- flush(reason: BatchFlushReason): BatchFlushPayload | undefined;
29
- size(): number;
30
- dispose(): void;
31
- private clearTimer;
32
- }
33
- export {};
package/dist/config.d.ts DELETED
@@ -1,41 +0,0 @@
1
- export type EventMode = "off" | "realtime" | "batch";
2
- export type MonitorEventType = "done" | "error" | "permission" | "question" | "user-interrupt" | "user-prompt";
3
- export interface MonitorEventRule {
4
- default: EventMode;
5
- agents: Record<string, EventMode>;
6
- }
7
- export interface MonitorBatchConfig {
8
- maxAgeMs: number;
9
- }
10
- export type MonitorEventConfig = Record<MonitorEventType, MonitorEventRule>;
11
- export interface MonitorConfig {
12
- webhook: {
13
- url: string;
14
- token: string;
15
- /** Gateway auth token for /v1/responses API (may differ from hooks token) */
16
- gatewayToken: string;
17
- /** Use /hooks/agent instead of /hooks/wake for targeted delivery */
18
- useAgent: boolean;
19
- /** Channel/chat ID to deliver notifications to */
20
- channel: string;
21
- /** Delivery channel type (discord, telegram, slack, etc.) */
22
- channelType: string;
23
- /** Chat type within channel (channel, dm, groupdm, private, etc.) */
24
- chatType: string;
25
- /** Agent ID for session key generation */
26
- agentId: string;
27
- /** Full session key override — if set, channelType/channel/chatType/agentId are ignored */
28
- sessionKey: string;
29
- };
30
- /** Label to identify the host machine (e.g. "MainPC", "32w2"). Auto-detected from hostname if omitted. */
31
- hostLabel: string;
32
- staleTimeoutMs: number;
33
- permissionReminderMs: number;
34
- dedupTtlMs: number;
35
- idleConfirmDelayMs: number;
36
- debug: boolean;
37
- enabled: boolean;
38
- events: MonitorEventConfig;
39
- batch: MonitorBatchConfig;
40
- }
41
- export declare function loadConfig(): MonitorConfig;
@@ -1,15 +0,0 @@
1
- import type { BatchFlushPayload } from "./batch-queue";
2
- import type { RuntimeControl } from "./runtime-control";
3
- export interface ControlHttpOptions {
4
- runtimeControl: RuntimeControl;
5
- bearerToken: string;
6
- getBatchSize: () => number;
7
- flushBatch: () => BatchFlushPayload | undefined;
8
- debugLog?: (msg: string) => void;
9
- }
10
- export interface ControlHttpServer {
11
- port: number;
12
- portFilePath: string;
13
- stop: () => Promise<void>;
14
- }
15
- export declare function startControlHttpServer(options: ControlHttpOptions): Promise<ControlHttpServer>;
@@ -1,10 +0,0 @@
1
- import type { Part } from "@opencode-ai/sdk";
2
- import { RuntimeControl } from "./runtime-control";
3
- export declare function ensureWakeCommandFile(worktree: string): string;
4
- export declare function handleWakeCommandBefore(input: {
5
- command: string;
6
- sessionID: string;
7
- arguments: string;
8
- }, output: {
9
- parts: Part[];
10
- }, runtimeControl: RuntimeControl, getBatchSize: () => number): boolean;
package/dist/dedup.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export declare class DedupEngine {
2
- private readonly ttlMs;
3
- private readonly seen;
4
- constructor(ttlMs: number);
5
- shouldSend(key: string): boolean;
6
- }
@@ -1,6 +0,0 @@
1
- import type { EventMode, MonitorEventConfig, MonitorEventType } from "./config";
2
- export declare class EventPolicyEngine {
3
- private readonly rules;
4
- constructor(rules: MonitorEventConfig);
5
- resolveMode(eventType: MonitorEventType, agentName?: string): EventMode;
6
- }
@@ -1,3 +0,0 @@
1
- import type { Event } from "@opencode-ai/sdk";
2
- import type { SessionStateTracker } from "../state";
3
- export declare function handleAgentEvent(event: Event, state: SessionStateTracker): void;
@@ -1,10 +0,0 @@
1
- import type { Event } from "@opencode-ai/sdk";
2
- import type { MonitorEventType } from "../config";
3
- import type { DedupEngine } from "../dedup";
4
- import type { SessionStateTracker } from "../state";
5
- import type { TimerManager } from "../timers";
6
- interface PolicyNotifier {
7
- (eventType: MonitorEventType, text: string, agentName?: string): void;
8
- }
9
- export declare function handlePermissionEvent(event: Event, dedup: DedupEngine, timers: TimerManager, state: SessionStateTracker, notify: PolicyNotifier): void;
10
- export {};
@@ -1,15 +0,0 @@
1
- import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
2
- import type { MonitorEventType } from "../config";
3
- import type { DedupEngine } from "../dedup";
4
- import type { SessionStateTracker } from "../state";
5
- import type { TimerManager } from "../timers";
6
- interface PolicyNotifier {
7
- (eventType: MonitorEventType, text: string, agentName?: string): void;
8
- }
9
- export declare function handleQuestionEvent(event: {
10
- type: string;
11
- properties: unknown;
12
- }, dedup: DedupEngine, timers: TimerManager, state: SessionStateTracker, notify: PolicyNotifier): void;
13
- export declare function replyToQuestion(client: OpencodeClient, requestID: string, answers: string[][]): Promise<boolean>;
14
- export declare function rejectQuestion(client: OpencodeClient, requestID: string): Promise<boolean>;
15
- export {};
@@ -1,10 +0,0 @@
1
- import type { Event } from "@opencode-ai/sdk";
2
- import type { MonitorEventType } from "../config";
3
- import type { DedupEngine } from "../dedup";
4
- import type { SessionStateTracker } from "../state";
5
- import type { TimerManager } from "../timers";
6
- interface PolicyNotifier {
7
- (eventType: MonitorEventType, text: string, agentName?: string): void;
8
- }
9
- export declare function handleSessionStatusEvent(event: Event, dedup: DedupEngine, state: SessionStateTracker, timers: TimerManager, webhookFn: (text: string) => void, notify: PolicyNotifier, debugLog?: (msg: string) => void): void;
10
- export {};
@@ -1,12 +0,0 @@
1
- import type { Event } from "@opencode-ai/sdk";
2
- import type { MonitorEventType } from "../config";
3
- import type { DedupEngine } from "../dedup";
4
- import type { SessionStateTracker } from "../state";
5
- interface UserEventNotifier {
6
- (eventType: MonitorEventType, text: string, agentName?: string, options?: {
7
- promptText?: string;
8
- }): void;
9
- }
10
- type DebugLogger = ((msg: string) => void) | undefined;
11
- export declare function handleUserEvent(event: Event, dedup: DedupEngine, state: SessionStateTracker, notify: UserEventNotifier, debugLog?: DebugLogger): void;
12
- export {};
@@ -1,13 +0,0 @@
1
- import type { MonitorConfig } from "./config";
2
- export interface ProjectConfig {
3
- sessionKey: string;
4
- tmuxSession: string;
5
- }
6
- export interface SessionRouting {
7
- sessionKey: string;
8
- source: "env" | "project" | "config" | "derived" | "none";
9
- agentId: string;
10
- channelId: string;
11
- }
12
- export declare function loadProjectConfig(cwd?: string): ProjectConfig;
13
- export declare function resolveSessionRouting(config: MonitorConfig, projectConfig: ProjectConfig): SessionRouting;
@@ -1,57 +0,0 @@
1
- export declare const REGISTRY_FILE_PATH: string;
2
- export interface RegistryEntry {
3
- instanceId: string;
4
- hostname: string;
5
- hostLabel: string;
6
- pid: number;
7
- startedAt: string;
8
- leaseExpiresAt: string;
9
- project: string;
10
- projectId: string;
11
- workspaceRoot: string;
12
- tmuxSession: string;
13
- openclawSessionKey: string;
14
- agentId: string;
15
- channelId: string;
16
- }
17
- export interface RegistryStartInput {
18
- hostLabel: string;
19
- tmuxSession: string;
20
- openclawSessionKey: string;
21
- agentId: string;
22
- channelId: string;
23
- }
24
- export interface RegistryOptions {
25
- workspaceRoot?: string;
26
- ttlMs?: number;
27
- heartbeatMs?: number;
28
- debugLog?: (msg: string) => void;
29
- }
30
- export declare class Registry {
31
- private readonly instanceId;
32
- private readonly hostname;
33
- private readonly workspaceRoot;
34
- private readonly project;
35
- private readonly projectId;
36
- private readonly ttlMs;
37
- private readonly heartbeatMs;
38
- private readonly debugLog;
39
- private heartbeatTimer;
40
- private hooksInstalled;
41
- private entryTemplate;
42
- private readonly stopOnProcessExit;
43
- constructor(options?: RegistryOptions);
44
- start(input: RegistryStartInput): void;
45
- stop(): void;
46
- readSessions(): RegistryEntry[];
47
- private startHeartbeat;
48
- private installProcessHooks;
49
- private upsert;
50
- private remove;
51
- private updateSessions;
52
- private readAndGc;
53
- private readRegistryFile;
54
- private writeRegistryFile;
55
- private ensureRegistryDir;
56
- private isPidAlive;
57
- }
@@ -1,33 +0,0 @@
1
- import type { EventMode, MonitorEventConfig, MonitorEventType } from "./config";
2
- declare const EVENT_TYPES: readonly MonitorEventType[];
3
- interface EventPatchInput {
4
- default?: EventMode;
5
- agents?: Record<string, EventMode | null>;
6
- }
7
- export interface RuntimePatchInput {
8
- paused?: boolean;
9
- events?: Partial<Record<MonitorEventType, EventPatchInput>>;
10
- }
11
- export interface RuntimePatchResult {
12
- ok: boolean;
13
- errors: string[];
14
- }
15
- declare function isEventType(value: string): value is MonitorEventType;
16
- declare function isEventMode(value: unknown): value is EventMode;
17
- export declare class RuntimeControl {
18
- private paused;
19
- private readonly rules;
20
- private readonly policy;
21
- constructor(baseRules: MonitorEventConfig);
22
- isPaused(): boolean;
23
- setPaused(next: boolean): void;
24
- pause(): void;
25
- resume(): void;
26
- getRules(): MonitorEventConfig;
27
- resolveMode(eventType: MonitorEventType, agentName?: string): EventMode;
28
- setEventDefault(eventType: MonitorEventType, mode: EventMode): void;
29
- setEventAgentMode(eventType: MonitorEventType, agentName: string, mode: EventMode): void;
30
- removeEventAgentMode(eventType: MonitorEventType, agentName: string): void;
31
- applyPatch(input: unknown): RuntimePatchResult;
32
- }
33
- export { EVENT_TYPES, isEventMode, isEventType };
package/dist/state.d.ts DELETED
@@ -1,27 +0,0 @@
1
- type SessionStatus = "idle" | "busy" | "retry" | "error" | "unknown";
2
- interface SessionState {
3
- status: SessionStatus;
4
- agent: string | undefined;
5
- parentID: string | undefined;
6
- busySince: number | undefined;
7
- lastTransition: number;
8
- }
9
- interface TransitionResult {
10
- changed: boolean;
11
- prev: SessionStatus;
12
- current: SessionStatus;
13
- }
14
- export declare class SessionStateTracker {
15
- private readonly states;
16
- private readonly agentHistory;
17
- transition(sessionID: string, newStatus: SessionStatus): TransitionResult;
18
- setAgent(sessionID: string, agentName: string): void;
19
- getAgent(sessionID: string): string | undefined;
20
- getState(sessionID: string): SessionState | undefined;
21
- setParentID(sessionID: string, parentID: string | undefined): void;
22
- hasParentID(sessionID: string): boolean;
23
- getAgentHistory(sessionID: string): string[];
24
- setBusySince(sessionID: string, time: number): void;
25
- clearSession(sessionID: string): void;
26
- }
27
- export type { SessionStatus };
package/dist/timers.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import type { MonitorConfig } from "./config";
2
- export declare class TimerManager {
3
- private readonly config;
4
- private readonly webhookFn;
5
- private readonly staleTimers;
6
- private readonly permissionTimers;
7
- private readonly questionTimers;
8
- constructor(config: MonitorConfig, webhookFn: (text: string) => void);
9
- startStaleTimer(sessionID: string): void;
10
- clearStaleTimer(sessionID: string): void;
11
- startPermissionReminder(permissionID: string, title: string, sessionID: string): void;
12
- clearPermissionReminder(permissionID: string): void;
13
- startQuestionReminder(questionID: string, header: string, sessionID: string): void;
14
- clearQuestionReminder(questionID: string): void;
15
- clearSession(sessionID: string): void;
16
- }
package/dist/webhook.d.ts DELETED
@@ -1,15 +0,0 @@
1
- import type { MonitorConfig } from "./config";
2
- import type { BatchFlushReason } from "./batch-queue";
3
- interface SendWebhookOptions {
4
- sessionKey?: string;
5
- correlationId?: string;
6
- metadata?: Record<string, unknown>;
7
- eventType?: string;
8
- }
9
- interface SendBatchWebhookOptions extends SendWebhookOptions {
10
- reason: BatchFlushReason;
11
- count: number;
12
- }
13
- export declare function sendWebhook(config: MonitorConfig, text: string, options?: SendWebhookOptions): void;
14
- export declare function sendBatchWebhook(config: MonitorConfig, text: string, options: SendBatchWebhookOptions): void;
15
- export {};