ha-opencode 0.1.1 → 0.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.
- package/README.md +119 -236
- package/dist/commands.d.ts +13 -11
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +87 -135
- package/dist/commands.js.map +1 -1
- package/dist/ha-config.d.ts +42 -0
- package/dist/ha-config.d.ts.map +1 -0
- package/dist/ha-config.js +104 -0
- package/dist/ha-config.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -83
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +20 -18
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +99 -108
- package/dist/state.js.map +1 -1
- package/dist/websocket.d.ts +113 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +394 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +6 -5
- package/blueprints/opencode_permission_response.yaml +0 -81
- package/blueprints/opencode_state_notifications.yaml +0 -191
- package/dist/cleanup.d.ts +0 -30
- package/dist/cleanup.d.ts.map +0 -1
- package/dist/cleanup.js +0 -125
- package/dist/cleanup.js.map +0 -1
- package/dist/config.d.ts +0 -28
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -18
- package/dist/config.js.map +0 -1
- package/dist/discovery.d.ts +0 -129
- package/dist/discovery.d.ts.map +0 -1
- package/dist/discovery.js +0 -257
- package/dist/discovery.js.map +0 -1
- package/dist/mqtt.d.ts +0 -16
- package/dist/mqtt.d.ts.map +0 -1
- package/dist/mqtt.js +0 -141
- package/dist/mqtt.js.map +0 -1
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
const RECONNECT_DELAY_MS = 5000;
|
|
3
|
+
const AUTH_TIMEOUT_MS = 10000;
|
|
4
|
+
const PING_INTERVAL_MS = 30000;
|
|
5
|
+
export function createHAWebSocketClient(config) {
|
|
6
|
+
let ws = null;
|
|
7
|
+
let connected = false;
|
|
8
|
+
let authenticated = false;
|
|
9
|
+
let messageId = 1;
|
|
10
|
+
let pendingRequests = new Map();
|
|
11
|
+
let commandHandler = null;
|
|
12
|
+
let stateRequestHandler = null;
|
|
13
|
+
let disconnectHandler = null;
|
|
14
|
+
let pingInterval = null;
|
|
15
|
+
let reconnecting = false;
|
|
16
|
+
// Auth flow handlers
|
|
17
|
+
let authResolve = null;
|
|
18
|
+
let authReject = null;
|
|
19
|
+
function nextId() {
|
|
20
|
+
return messageId++;
|
|
21
|
+
}
|
|
22
|
+
function send(message) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
25
|
+
reject(new Error("WebSocket not connected"));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!authenticated && message.type !== "auth") {
|
|
29
|
+
reject(new Error("Not authenticated"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const id = nextId();
|
|
33
|
+
message.id = id;
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
pendingRequests.delete(id);
|
|
36
|
+
reject(new Error(`Request ${id} timed out`));
|
|
37
|
+
}, AUTH_TIMEOUT_MS);
|
|
38
|
+
pendingRequests.set(id, { resolve, reject, timeout });
|
|
39
|
+
ws.send(JSON.stringify(message));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function sendNoResponse(message) {
|
|
43
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!authenticated && message.type !== "auth") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const id = nextId();
|
|
50
|
+
message.id = id;
|
|
51
|
+
ws.send(JSON.stringify(message));
|
|
52
|
+
}
|
|
53
|
+
function sendRaw(message) {
|
|
54
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
ws.send(JSON.stringify(message));
|
|
58
|
+
}
|
|
59
|
+
function handleMessage(data) {
|
|
60
|
+
try {
|
|
61
|
+
const message = JSON.parse(data);
|
|
62
|
+
// Handle auth flow
|
|
63
|
+
if (message.type === "auth_required") {
|
|
64
|
+
// HA is asking for authentication
|
|
65
|
+
if (config.accessToken) {
|
|
66
|
+
sendRaw({ type: "auth", access_token: config.accessToken });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (authReject) {
|
|
70
|
+
authReject(new Error("No access token provided for authentication"));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (message.type === "auth_ok") {
|
|
76
|
+
authenticated = true;
|
|
77
|
+
if (authResolve) {
|
|
78
|
+
authResolve();
|
|
79
|
+
authResolve = null;
|
|
80
|
+
authReject = null;
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (message.type === "auth_invalid") {
|
|
85
|
+
const errorMsg = message.message || "Authentication failed";
|
|
86
|
+
if (authReject) {
|
|
87
|
+
authReject(new Error(errorMsg));
|
|
88
|
+
authResolve = null;
|
|
89
|
+
authReject = null;
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Handle responses to our requests
|
|
94
|
+
if (message.id && pendingRequests.has(message.id)) {
|
|
95
|
+
const pending = pendingRequests.get(message.id);
|
|
96
|
+
clearTimeout(pending.timeout);
|
|
97
|
+
pendingRequests.delete(message.id);
|
|
98
|
+
pending.resolve(message);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Handle server-initiated messages
|
|
102
|
+
if (message.type === "opencode/command" && commandHandler) {
|
|
103
|
+
const command = message.command;
|
|
104
|
+
const sessionId = message.session_id;
|
|
105
|
+
const msgData = message.data || {};
|
|
106
|
+
commandHandler(command, sessionId, msgData);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (message.type === "opencode/request_state" && stateRequestHandler) {
|
|
110
|
+
stateRequestHandler();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Handle pong (keep-alive response)
|
|
114
|
+
if (message.type === "pong") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Silent failure - ignore malformed messages
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function startPing() {
|
|
123
|
+
if (pingInterval) {
|
|
124
|
+
clearInterval(pingInterval);
|
|
125
|
+
}
|
|
126
|
+
pingInterval = setInterval(() => {
|
|
127
|
+
if (ws && ws.readyState === WebSocket.OPEN && authenticated) {
|
|
128
|
+
sendNoResponse({ type: "ping" });
|
|
129
|
+
}
|
|
130
|
+
}, PING_INTERVAL_MS);
|
|
131
|
+
}
|
|
132
|
+
function stopPing() {
|
|
133
|
+
if (pingInterval) {
|
|
134
|
+
clearInterval(pingInterval);
|
|
135
|
+
pingInterval = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function cleanup() {
|
|
139
|
+
stopPing();
|
|
140
|
+
connected = false;
|
|
141
|
+
authenticated = false;
|
|
142
|
+
// Reject all pending requests
|
|
143
|
+
for (const [id, pending] of pendingRequests) {
|
|
144
|
+
clearTimeout(pending.timeout);
|
|
145
|
+
pending.reject(new Error("Connection closed"));
|
|
146
|
+
}
|
|
147
|
+
pendingRequests.clear();
|
|
148
|
+
// Clean up auth handlers
|
|
149
|
+
if (authReject) {
|
|
150
|
+
authReject(new Error("Connection closed"));
|
|
151
|
+
authResolve = null;
|
|
152
|
+
authReject = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
async connect() {
|
|
157
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
ws = new WebSocket(config.url);
|
|
162
|
+
const connectTimeout = setTimeout(() => {
|
|
163
|
+
if (ws) {
|
|
164
|
+
ws.close();
|
|
165
|
+
}
|
|
166
|
+
reject(new Error("Connection timeout"));
|
|
167
|
+
}, AUTH_TIMEOUT_MS);
|
|
168
|
+
// Set up auth flow handlers
|
|
169
|
+
authResolve = () => {
|
|
170
|
+
clearTimeout(connectTimeout);
|
|
171
|
+
connected = true;
|
|
172
|
+
startPing();
|
|
173
|
+
resolve();
|
|
174
|
+
};
|
|
175
|
+
authReject = (err) => {
|
|
176
|
+
clearTimeout(connectTimeout);
|
|
177
|
+
reject(err);
|
|
178
|
+
};
|
|
179
|
+
ws.on("open", () => {
|
|
180
|
+
// Don't resolve yet - wait for auth_ok
|
|
181
|
+
});
|
|
182
|
+
ws.on("message", (data) => {
|
|
183
|
+
handleMessage(data.toString());
|
|
184
|
+
});
|
|
185
|
+
ws.on("close", () => {
|
|
186
|
+
cleanup();
|
|
187
|
+
if (disconnectHandler && !reconnecting) {
|
|
188
|
+
disconnectHandler();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
ws.on("error", (err) => {
|
|
192
|
+
// Only log if we're not in a reconnect loop
|
|
193
|
+
if (!connected) {
|
|
194
|
+
clearTimeout(connectTimeout);
|
|
195
|
+
reject(err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
async disconnect() {
|
|
201
|
+
cleanup();
|
|
202
|
+
if (ws) {
|
|
203
|
+
ws.close();
|
|
204
|
+
ws = null;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
isConnected() {
|
|
208
|
+
return connected && authenticated && ws !== null && ws.readyState === WebSocket.OPEN;
|
|
209
|
+
},
|
|
210
|
+
async pair(code, hostname) {
|
|
211
|
+
if (!this.isConnected()) {
|
|
212
|
+
return { success: false, error: "Not connected" };
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const response = await send({
|
|
216
|
+
type: "opencode/pair",
|
|
217
|
+
code,
|
|
218
|
+
hostname,
|
|
219
|
+
});
|
|
220
|
+
if (response.type === "result" && response.success) {
|
|
221
|
+
const result = response.result;
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
instanceId: result.instance_id,
|
|
225
|
+
instanceToken: result.instance_token,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
error: response.error?.message || "Pairing failed",
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
async reconnect(instanceToken, hostname) {
|
|
243
|
+
if (!this.isConnected()) {
|
|
244
|
+
return { success: false, error: "Not connected" };
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const response = await send({
|
|
248
|
+
type: "opencode/connect",
|
|
249
|
+
instance_token: instanceToken,
|
|
250
|
+
hostname,
|
|
251
|
+
});
|
|
252
|
+
if (response.type === "result" && response.success) {
|
|
253
|
+
const result = response.result;
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
instanceId: result.instance_id,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: response.error?.message || "Reconnection failed",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
async sendSessionUpdate(instanceToken, session) {
|
|
274
|
+
if (!this.isConnected()) {
|
|
275
|
+
throw new Error("Not connected");
|
|
276
|
+
}
|
|
277
|
+
await send({
|
|
278
|
+
type: "opencode/session_update",
|
|
279
|
+
instance_token: instanceToken,
|
|
280
|
+
session,
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
async sendSessionRemoved(instanceToken, sessionId) {
|
|
284
|
+
if (!this.isConnected()) {
|
|
285
|
+
throw new Error("Not connected");
|
|
286
|
+
}
|
|
287
|
+
await send({
|
|
288
|
+
type: "opencode/session_removed",
|
|
289
|
+
instance_token: instanceToken,
|
|
290
|
+
session_id: sessionId,
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
async sendStateResponse(instanceToken, sessions) {
|
|
294
|
+
if (!this.isConnected()) {
|
|
295
|
+
throw new Error("Not connected");
|
|
296
|
+
}
|
|
297
|
+
await send({
|
|
298
|
+
type: "opencode/state_response",
|
|
299
|
+
instance_token: instanceToken,
|
|
300
|
+
sessions,
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
async sendHistoryResponse(instanceToken, data) {
|
|
304
|
+
if (!this.isConnected()) {
|
|
305
|
+
throw new Error("Not connected");
|
|
306
|
+
}
|
|
307
|
+
await send({
|
|
308
|
+
type: "opencode/history_response",
|
|
309
|
+
instance_token: instanceToken,
|
|
310
|
+
...data,
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
async sendAgentsResponse(instanceToken, data) {
|
|
314
|
+
if (!this.isConnected()) {
|
|
315
|
+
throw new Error("Not connected");
|
|
316
|
+
}
|
|
317
|
+
await send({
|
|
318
|
+
type: "opencode/agents_response",
|
|
319
|
+
instance_token: instanceToken,
|
|
320
|
+
...data,
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
onCommand(handler) {
|
|
324
|
+
commandHandler = handler;
|
|
325
|
+
},
|
|
326
|
+
onStateRequest(handler) {
|
|
327
|
+
stateRequestHandler = handler;
|
|
328
|
+
},
|
|
329
|
+
onDisconnect(handler) {
|
|
330
|
+
disconnectHandler = handler;
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Create a WebSocket client with automatic reconnection.
|
|
336
|
+
*/
|
|
337
|
+
export function createReconnectingClient(config, instanceToken, hostname, callbacks) {
|
|
338
|
+
// Support both old function signature and new callbacks object
|
|
339
|
+
const cb = typeof callbacks === 'function'
|
|
340
|
+
? { onReconnected: callbacks }
|
|
341
|
+
: callbacks;
|
|
342
|
+
const client = createHAWebSocketClient(config);
|
|
343
|
+
let shouldReconnect = true;
|
|
344
|
+
let reconnectTimeout = null;
|
|
345
|
+
let hasNotifiedDisconnect = false;
|
|
346
|
+
async function attemptReconnect() {
|
|
347
|
+
if (!shouldReconnect)
|
|
348
|
+
return;
|
|
349
|
+
try {
|
|
350
|
+
await client.connect();
|
|
351
|
+
const result = await client.reconnect(instanceToken, hostname);
|
|
352
|
+
if (result.success) {
|
|
353
|
+
hasNotifiedDisconnect = false; // Reset for next disconnect
|
|
354
|
+
cb.onReconnected();
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
scheduleReconnect();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
scheduleReconnect();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function scheduleReconnect() {
|
|
365
|
+
if (!shouldReconnect)
|
|
366
|
+
return;
|
|
367
|
+
if (reconnectTimeout) {
|
|
368
|
+
clearTimeout(reconnectTimeout);
|
|
369
|
+
}
|
|
370
|
+
reconnectTimeout = setTimeout(attemptReconnect, RECONNECT_DELAY_MS);
|
|
371
|
+
}
|
|
372
|
+
client.onDisconnect(() => {
|
|
373
|
+
if (shouldReconnect) {
|
|
374
|
+
// Only notify once per disconnect cycle
|
|
375
|
+
if (!hasNotifiedDisconnect && cb.onDisconnected) {
|
|
376
|
+
hasNotifiedDisconnect = true;
|
|
377
|
+
cb.onDisconnected();
|
|
378
|
+
}
|
|
379
|
+
scheduleReconnect();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// Override disconnect to prevent reconnection
|
|
383
|
+
const originalDisconnect = client.disconnect.bind(client);
|
|
384
|
+
client.disconnect = async () => {
|
|
385
|
+
shouldReconnect = false;
|
|
386
|
+
if (reconnectTimeout) {
|
|
387
|
+
clearTimeout(reconnectTimeout);
|
|
388
|
+
reconnectTimeout = null;
|
|
389
|
+
}
|
|
390
|
+
await originalDisconnect();
|
|
391
|
+
};
|
|
392
|
+
return client;
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ha-opencode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenCode plugin for Home Assistant integration via
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenCode plugin for Home Assistant integration via native WebSocket API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -30,19 +30,20 @@
|
|
|
30
30
|
"keywords": [
|
|
31
31
|
"opencode",
|
|
32
32
|
"home-assistant",
|
|
33
|
-
"
|
|
33
|
+
"websocket",
|
|
34
34
|
"plugin"
|
|
35
35
|
],
|
|
36
36
|
"author": "Stephen Golub",
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
39
|
+
"source-map-support": "^0.5.21",
|
|
40
|
+
"ws": "^8.18.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@opencode-ai/plugin": "^1.0.23",
|
|
44
44
|
"@types/node": "^22.0.0",
|
|
45
45
|
"@types/source-map-support": "^0.5.10",
|
|
46
|
+
"@types/ws": "^8.5.13",
|
|
46
47
|
"@vitest/coverage-v8": "^4.0.16",
|
|
47
48
|
"typescript": "^5.7.0",
|
|
48
49
|
"vitest": "^4.0.16"
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
blueprint:
|
|
2
|
-
name: OpenCode Permission Response Handler
|
|
3
|
-
description: >
|
|
4
|
-
Handles approve/reject actions from OpenCode permission notifications.
|
|
5
|
-
|
|
6
|
-
This blueprint listens for notification actions (button taps) and sends
|
|
7
|
-
the appropriate permission response via MQTT.
|
|
8
|
-
|
|
9
|
-
Works with both iOS and Android via the Home Assistant Companion app.
|
|
10
|
-
|
|
11
|
-
Use this together with the OpenCode State Notifications blueprint.
|
|
12
|
-
domain: automation
|
|
13
|
-
author: ha-opencode
|
|
14
|
-
source_url: https://github.com/your-repo/ha-opencode/blob/main/blueprints/opencode_permission_response.yaml
|
|
15
|
-
input: {}
|
|
16
|
-
|
|
17
|
-
mode: parallel
|
|
18
|
-
max_exceeded: silent
|
|
19
|
-
|
|
20
|
-
trigger:
|
|
21
|
-
- platform: event
|
|
22
|
-
event_type: mobile_app_notification_action
|
|
23
|
-
|
|
24
|
-
variables:
|
|
25
|
-
# Get the action string
|
|
26
|
-
action: "{{ trigger.event.data.action | default('') }}"
|
|
27
|
-
|
|
28
|
-
# Check if this is an OpenCode action
|
|
29
|
-
is_opencode_action: >
|
|
30
|
-
{{ action.startswith('OPENCODE_APPROVE_') or action.startswith('OPENCODE_REJECT_') }}
|
|
31
|
-
|
|
32
|
-
is_approve: "{{ action.startswith('OPENCODE_APPROVE_') }}"
|
|
33
|
-
response: "{{ 'once' if is_approve else 'reject' }}"
|
|
34
|
-
|
|
35
|
-
# Get data passed from the notification
|
|
36
|
-
# iOS uses action_data, Android might use data directly
|
|
37
|
-
action_data: >
|
|
38
|
-
{% if trigger.event.data.action_data is defined %}
|
|
39
|
-
{{ trigger.event.data.action_data }}
|
|
40
|
-
{% elif trigger.event.data.data is defined %}
|
|
41
|
-
{{ trigger.event.data.data }}
|
|
42
|
-
{% else %}
|
|
43
|
-
{}
|
|
44
|
-
{% endif %}
|
|
45
|
-
|
|
46
|
-
command_topic: "{{ action_data.command_topic | default('') }}"
|
|
47
|
-
permission_id: "{{ action_data.permission_id | default('') }}"
|
|
48
|
-
device_id: "{{ action_data.device_id | default('') }}"
|
|
49
|
-
|
|
50
|
-
condition:
|
|
51
|
-
- condition: template
|
|
52
|
-
value_template: "{{ is_opencode_action }}"
|
|
53
|
-
|
|
54
|
-
action:
|
|
55
|
-
# Log for debugging
|
|
56
|
-
- service: system_log.write
|
|
57
|
-
data:
|
|
58
|
-
message: >
|
|
59
|
-
OpenCode permission response: action={{ action }}, is_approve={{ is_approve }},
|
|
60
|
-
response={{ response }}, command_topic={{ command_topic }},
|
|
61
|
-
permission_id={{ permission_id }}, device_id={{ device_id }},
|
|
62
|
-
action_data={{ action_data }}
|
|
63
|
-
level: info
|
|
64
|
-
logger: ha-opencode.permission
|
|
65
|
-
|
|
66
|
-
# Check we have required data before publishing
|
|
67
|
-
- condition: template
|
|
68
|
-
value_template: "{{ command_topic != '' and permission_id != '' }}"
|
|
69
|
-
|
|
70
|
-
- service: mqtt.publish
|
|
71
|
-
data:
|
|
72
|
-
topic: "{{ command_topic }}"
|
|
73
|
-
payload: >
|
|
74
|
-
{"command": "permission_response", "permission_id": "{{ permission_id }}", "response": "{{ response }}"}
|
|
75
|
-
|
|
76
|
-
# Log success
|
|
77
|
-
- service: system_log.write
|
|
78
|
-
data:
|
|
79
|
-
message: "OpenCode permission {{ response }} sent for {{ permission_id }}"
|
|
80
|
-
level: info
|
|
81
|
-
logger: ha-opencode.permission
|