omoclaw 3.2.3 → 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
@@ -1220,16 +1220,15 @@ function resolveSessionRouting(config, projectConfig) {
1220
1220
  if (config.webhook.sessionKey) {
1221
1221
  const parsed = parseSessionKey(config.webhook.sessionKey);
1222
1222
  return {
1223
- sessionKey: config.webhook.sessionKey,
1223
+ sessionKey: "",
1224
1224
  source: "config",
1225
1225
  agentId: parsed.agentId || config.webhook.agentId,
1226
1226
  channelId: parsed.channelId || config.webhook.channel
1227
1227
  };
1228
1228
  }
1229
- const derivedSessionKey = buildSessionKeyFromConfig(config);
1230
- if (derivedSessionKey) {
1229
+ if (buildSessionKeyFromConfig(config)) {
1231
1230
  return {
1232
- sessionKey: derivedSessionKey,
1231
+ sessionKey: "",
1233
1232
  source: "derived",
1234
1233
  agentId: config.webhook.agentId,
1235
1234
  channelId: config.webhook.channel
@@ -1701,7 +1700,11 @@ function buildCorrelationId(text, provided) {
1701
1700
  function sendWebhook(config, text, options = {}) {
1702
1701
  const { webhook } = config;
1703
1702
  const envSessionKey = process.env.OPENCLAW_SESSION_KEY?.trim();
1704
- 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
+ }
1705
1708
  const correlationId = buildCorrelationId(text, options.correlationId);
1706
1709
  const eventType = options.eventType?.trim();
1707
1710
  const body = {
@@ -1710,15 +1713,13 @@ function sendWebhook(config, text, options = {}) {
1710
1713
  metadata: {
1711
1714
  correlationId,
1712
1715
  ...options.metadata ?? {}
1713
- }
1716
+ },
1717
+ sessionKey
1714
1718
  };
1715
- if (sessionKey) {
1716
- body.sessionKey = sessionKey;
1717
- }
1718
1719
  if (eventType) {
1719
1720
  body.eventType = eventType;
1720
1721
  }
1721
- 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}`);
1722
1723
  fetch(webhook.url, {
1723
1724
  method: "POST",
1724
1725
  headers: {
@@ -1773,7 +1774,7 @@ var SessionMonitorPlugin = async (ctx) => {
1773
1774
  const projectConfig = loadProjectConfig();
1774
1775
  const sessionRouting = resolveSessionRouting(config, projectConfig);
1775
1776
  const prefix = buildPrefix(config);
1776
- const wakeCommandPath = ensureWakeCommandFile(ctx.worktree || process.cwd());
1777
+ const wakeCommandPath = ensureWakeCommandFile((ctx.worktree && ctx.worktree !== "/") ? ctx.worktree : (process.env.HOME || process.cwd()));
1777
1778
  const debugLog = config.debug ? (msg) => {
1778
1779
  const line = `[${new Date().toISOString()}] ${msg}
1779
1780
  `;
@@ -1789,6 +1790,11 @@ var SessionMonitorPlugin = async (ctx) => {
1789
1790
  debugLog?.("Plugin disabled, exiting.");
1790
1791
  return {};
1791
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
+ }
1792
1798
  const registry = new Registry({ debugLog });
1793
1799
  registry.start({
1794
1800
  hostLabel: config.hostLabel || os3.hostname(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omoclaw",
3
- "version": "3.2.3",
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 {};