@whimmy-ai/whimmy 0.2.1 → 0.3.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.
Files changed (3) hide show
  1. package/index.ts +18 -1
  2. package/package.json +1 -1
  3. package/src/channel.ts +40 -6
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
- import { whimmyPlugin, registerWhimmyHooks } from './src/channel';
2
+ import { whimmyPlugin, registerWhimmyHooks, setApprovalManager, broadcastApprovalRequest } from './src/channel';
3
3
  import { setWhimmyRuntime } from './src/runtime';
4
4
  import { registerWhimmyCli } from './src/setup';
5
5
 
@@ -13,6 +13,23 @@ const plugin = {
13
13
  api.registerChannel({ plugin: whimmyPlugin });
14
14
  registerWhimmyCli(api);
15
15
  registerWhimmyHooks(api);
16
+
17
+ // Register a gateway method that captures the ExecApprovalManager reference.
18
+ // The manager is only accessible via GatewayRequestHandlerOptions.context,
19
+ // so we use this method as the capture point.
20
+ api.registerGatewayMethod('whimmy.approval.init', (opts) => {
21
+ const manager = opts.context.execApprovalManager;
22
+ if (manager) {
23
+ setApprovalManager(manager);
24
+ }
25
+ opts.respond(true, { ok: true });
26
+ });
27
+
28
+ // Once the gateway starts, the approval manager will be available
29
+ // after the first gateway method invocation.
30
+ api.on('gateway_start', () => {
31
+ // ExecApprovalManager is captured on the first whimmy.approval.init call.
32
+ });
16
33
  },
17
34
  };
18
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whimmy-ai/whimmy",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Whimmy channel plugin for OpenClaw",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/src/channel.ts CHANGED
@@ -31,6 +31,27 @@ import type {
31
31
  AgentInfo,
32
32
  } from './types';
33
33
 
34
+ // ============ Exec Approval Manager Singleton ============
35
+
36
+ /**
37
+ * Minimal interface matching ExecApprovalManager.resolve().
38
+ * The full class isn't exported from openclaw/plugin-sdk's barrel,
39
+ * so we type just the method we need.
40
+ */
41
+ interface ApprovalManagerLike {
42
+ resolve(recordId: string, decision: 'allow-once' | 'allow-always' | 'deny', resolvedBy?: string | null): boolean;
43
+ }
44
+
45
+ let approvalManager: ApprovalManagerLike | null = null;
46
+
47
+ export function setApprovalManager(manager: ApprovalManagerLike): void {
48
+ approvalManager = manager;
49
+ }
50
+
51
+ export function getApprovalManager(): ApprovalManagerLike | null {
52
+ return approvalManager;
53
+ }
54
+
34
55
  // ============ Config Helpers ============
35
56
 
36
57
  function getConfig(cfg: OpenClawConfig, accountId?: string): WhimmyConfig {
@@ -264,14 +285,22 @@ async function handleHookApproval(
264
285
  request: HookApprovalRequest,
265
286
  log?: Logger,
266
287
  ): Promise<void> {
267
- const rt = getWhimmyRuntime();
268
-
269
288
  log?.info?.(`[Whimmy] Approval: execution=${request.executionId} approved=${request.approved}`);
270
289
 
271
- // TODO: Route approval resolution back into OpenClaw's execution engine.
272
- // This depends on how OpenClaw exposes approval resolution to channel plugins.
273
- // For now, log itthe exact API will depend on the OpenClaw SDK version.
274
- log?.debug?.(`[Whimmy] Approval resolution for ${request.executionId}: approved=${request.approved}, reason=${request.reason}`);
290
+ const manager = getApprovalManager();
291
+ if (!manager) {
292
+ log?.warn?.(`[Whimmy] ExecApprovalManager not yet capturedcannot resolve execution ${request.executionId}`);
293
+ return;
294
+ }
295
+
296
+ const decision = request.approved ? 'allow-once' as const : 'deny' as const;
297
+ const resolved = manager.resolve(request.executionId, decision, 'whimmy');
298
+
299
+ if (resolved) {
300
+ log?.info?.(`[Whimmy] Resolved execution ${request.executionId} → ${decision}`);
301
+ } else {
302
+ log?.warn?.(`[Whimmy] Failed to resolve execution ${request.executionId} (expired or unknown)`);
303
+ }
275
304
  }
276
305
 
277
306
  // ============ Inbound Event Handlers ============
@@ -300,6 +329,11 @@ function broadcastEvent(event: string, payload: unknown): void {
300
329
  }
301
330
  }
302
331
 
332
+ /** Forward an exec.approval.requested event to all connected Whimmy backends. */
333
+ export function broadcastApprovalRequest(payload: ExecApprovalRequestedPayload): void {
334
+ broadcastEvent('exec.approval.requested', payload);
335
+ }
336
+
303
337
  // ============ Actions ============
304
338
 
305
339
  function createWhimmyActions(ws: WebSocket, sessionKey: string, agentId: string) {