@agentdeck/hooks 0.2.0 → 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-mini-toml.js","sourceRoot":"","sources":["../src/codex-mini-toml.ts"],"names":[],"mappings":"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"}
package/dist/install.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export { installCodexHooksIfNeeded, uninstallCodexHooks, migrateCodexHooks, managedBlockBody as codexManagedBlockBody, DEFAULT_CODEX_CONFIG_PATH, } from './codex-install.js';
2
+ export type { InstallOptions as CodexInstallOptions, InstallResult as CodexInstallResult } from './codex-install.js';
3
+ export { installOpenCodeHooksIfNeeded, uninstallOpenCodeHooks, opencodePluginPath, opencodePluginSource, } from './opencode-install.js';
4
+ export type { OpenCodeInstallOptions, OpenCodeInstallResult } from './opencode-install.js';
1
5
  export declare const HOOK_EVENTS: readonly ["SessionStart", "SessionEnd", "PreToolUse", "PostToolUse", "Stop", "Notification", "UserPromptSubmit"];
2
6
  /**
3
7
  * Shell snippet that resolves the AgentDeck daemon's HTTP port at hook
@@ -10,15 +14,31 @@ export declare const HOOK_EVENTS: readonly ["SessionStart", "SessionEnd", "PreTo
10
14
  * so the hook targets the *owning* daemon even when several daemons
11
15
  * are running in parallel)
12
16
  * 2. `~/.agentdeck/daemon.json` (CLI)
13
- * 3. App Store group container `daemon.json` (Swift)
14
- * 4. `9120` fallback (legacy, never reached in practice)
17
+ * 3. App Store sandbox container `daemon.json` (Swift)
18
+ * 4. Legacy App Store group container `daemon.json` (Swift)
19
+ * 5. `9120` fallback (legacy, never reached in practice)
15
20
  *
16
- * For (2) and (3) the snippet prefers the daemon's `httpPort` field when
21
+ * For (2)-(4) the snippet prefers the daemon's `httpPort` field when
17
22
  * present — Swift runs WS and HTTP on separate ports. Each candidate is
18
23
  * verified with a short `/health` probe so a stale daemon.json from a
19
24
  * crashed daemon doesn't swallow the hook.
20
25
  */
21
26
  export declare function buildHookCommand(eventName: string): string;
27
+ /**
28
+ * Windows variant of `buildHookCommand`. Claude Code v2.1+ executes hook
29
+ * commands through `cmd.exe` on Windows, so we shell out to PowerShell for
30
+ * the JSON read + HTTP POST. Discovery is narrower than POSIX since the
31
+ * macOS App Store sandbox paths don't exist on Windows:
32
+ *
33
+ * 1. `$env:AGENTDECK_PORT`
34
+ * 2. `%USERPROFILE%\.agentdeck\daemon.json` (verified with `/health` probe)
35
+ * 3. `9120` fallback
36
+ *
37
+ * Single quotes are used inside the PowerShell script so the entire `-Command`
38
+ * argument can stay double-quoted under cmd.exe. Errors are swallowed so a
39
+ * dead daemon never blocks the host session.
40
+ */
41
+ export declare function buildHookCommandWin(eventName: string): string;
22
42
  export declare function buildHookEntry(eventName: string): {
23
43
  matcher: string;
24
44
  hooks: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,kHAQd,CAAC;AAEX;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAa1D;AAGD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM;;;EAa/C;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAuB7C;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAwB9C;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,GAAG,GAAG;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAyChF;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,IAAI,CAkBnC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,IAAI,IAAI,CASrC;AAED;iEACiE;AACjE,wBAAgB,oBAAoB,IAAI,IAAI,CAwB3C"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,IAAI,qBAAqB,EACzC,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,IAAI,mBAAmB,EAAE,aAAa,IAAI,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAIrH,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE3F,eAAO,MAAM,WAAW,kHAQd,CAAC;AAEX;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAqC1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAe7D;AAGD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM;;;EAgB/C;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAuB7C;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAwB9C;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,GAAG,GAAG;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAyChF;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,IAAI,CAkBnC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,IAAI,IAAI,CASrC;AAED;iEACiE;AACjE,wBAAgB,oBAAoB,IAAI,IAAI,CA+B3C"}
package/dist/install.js CHANGED
@@ -1,6 +1,12 @@
1
1
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
+ // Re-export the Codex installer surface so callers reach it via the
5
+ // canonical `@agentdeck/hooks` entry point alongside the Claude installer.
6
+ export { installCodexHooksIfNeeded, uninstallCodexHooks, migrateCodexHooks, managedBlockBody as codexManagedBlockBody, DEFAULT_CODEX_CONFIG_PATH, } from './codex-install.js';
7
+ // Re-export the OpenCode plugin installer (standalone-session observation
8
+ // via opencode_* lifecycle hooks) through the same canonical entry point.
9
+ export { installOpenCodeHooksIfNeeded, uninstallOpenCodeHooks, opencodePluginPath, opencodePluginSource, } from './opencode-install.js';
4
10
  export const HOOK_EVENTS = [
5
11
  'SessionStart',
6
12
  'SessionEnd',
@@ -21,33 +27,91 @@ export const HOOK_EVENTS = [
21
27
  * so the hook targets the *owning* daemon even when several daemons
22
28
  * are running in parallel)
23
29
  * 2. `~/.agentdeck/daemon.json` (CLI)
24
- * 3. App Store group container `daemon.json` (Swift)
25
- * 4. `9120` fallback (legacy, never reached in practice)
30
+ * 3. App Store sandbox container `daemon.json` (Swift)
31
+ * 4. Legacy App Store group container `daemon.json` (Swift)
32
+ * 5. `9120` fallback (legacy, never reached in practice)
26
33
  *
27
- * For (2) and (3) the snippet prefers the daemon's `httpPort` field when
34
+ * For (2)-(4) the snippet prefers the daemon's `httpPort` field when
28
35
  * present — Swift runs WS and HTTP on separate ports. Each candidate is
29
36
  * verified with a short `/health` probe so a stale daemon.json from a
30
37
  * crashed daemon doesn't swallow the hook.
31
38
  */
32
39
  export function buildHookCommand(eventName) {
33
- return [
40
+ const preamble = [
34
41
  `PORT="\${AGENTDECK_PORT:-}"`,
35
42
  `if [ -z "$PORT" ]; then`,
36
- ` for F in "$HOME/.agentdeck/daemon.json" "$HOME/Library/Group Containers/group.bound.serendipity.agentdeck.dashboard/daemon.json"; do`,
43
+ ` for F in "$HOME/.agentdeck/daemon.json" "$HOME/Library/Containers/bound.serendipity.agent.deck/Data/Library/Application Support/AgentDeck/daemon.json" "$HOME/Library/Group Containers/group.bound.serendipity.agent.deck/daemon.json"; do`,
37
44
  ` [ -f "$F" ] || continue`,
38
45
  ` P=$(python3 -c "import json;d=json.load(open('$F'));print(d.get('httpPort') or d.get('port',''))" 2>/dev/null)`,
39
46
  ` [ -n "$P" ] && curl -sf --max-time 0.3 "http://127.0.0.1:$P/health" >/dev/null 2>&1 && { PORT="$P"; break; }`,
40
47
  ` done`,
41
48
  `fi`,
42
49
  `PORT="\${PORT:-9120}"`,
50
+ ];
51
+ // PreToolUse is request-response: the daemon may hold the connection open and
52
+ // return a permission decision (device approval) or a soft-STOP deny. Capture
53
+ // the body and echo it to stdout so Claude can gate the tool; empty output
54
+ // (timeout/error/disabled) = Claude's normal permission flow. `--max-time 60`
55
+ // exceeds the daemon's internal hold timeout (default 25s) so the fallback
56
+ // reaches Claude before curl quits.
57
+ if (eventName === 'PreToolUse') {
58
+ return preamble.concat([
59
+ `RESP=$(curl -s -X POST "http://127.0.0.1:$PORT/hooks/PreToolUse" -H 'Content-Type: application/json' --max-time 60 -d @- 2>/dev/null)`,
60
+ `printf '%s' "\${RESP:-}"`,
61
+ ]).join('\n');
62
+ }
63
+ // Stop is also request-response: the daemon answers instantly — either an
64
+ // empty body (turn ends normally) or `{decision:"block", reason}` delivering
65
+ // a deck-queued directive so Claude continues with it. Short --max-time:
66
+ // this runs on EVERY turn end, so a wedged daemon must never stall the TUI.
67
+ if (eventName === 'Stop') {
68
+ return preamble.concat([
69
+ `RESP=$(curl -s -X POST "http://127.0.0.1:$PORT/hooks/Stop" -H 'Content-Type: application/json' --max-time 10 -d @- 2>/dev/null)`,
70
+ `printf '%s' "\${RESP:-}"`,
71
+ ]).join('\n');
72
+ }
73
+ return preamble.concat([
43
74
  `curl -sf -X POST "http://127.0.0.1:$PORT/hooks/${eventName}" -H 'Content-Type: application/json' -d @- 2>/dev/null || true`,
44
- ].join('\n');
75
+ ]).join('\n');
76
+ }
77
+ /**
78
+ * Windows variant of `buildHookCommand`. Claude Code v2.1+ executes hook
79
+ * commands through `cmd.exe` on Windows, so we shell out to PowerShell for
80
+ * the JSON read + HTTP POST. Discovery is narrower than POSIX since the
81
+ * macOS App Store sandbox paths don't exist on Windows:
82
+ *
83
+ * 1. `$env:AGENTDECK_PORT`
84
+ * 2. `%USERPROFILE%\.agentdeck\daemon.json` (verified with `/health` probe)
85
+ * 3. `9120` fallback
86
+ *
87
+ * Single quotes are used inside the PowerShell script so the entire `-Command`
88
+ * argument can stay double-quoted under cmd.exe. Errors are swallowed so a
89
+ * dead daemon never blocks the host session.
90
+ */
91
+ export function buildHookCommandWin(eventName) {
92
+ const ps = [
93
+ `$ev='${eventName}'`,
94
+ `$port=$env:AGENTDECK_PORT`,
95
+ `if(-not $port){$f=Join-Path $env:USERPROFILE '.agentdeck\\daemon.json'; if(Test-Path $f){try{$d=Get-Content -Raw $f|ConvertFrom-Json; $p=if($d.httpPort){$d.httpPort}else{$d.port}; if($p){try{Invoke-RestMethod -Uri ('http://127.0.0.1:'+$p+'/health') -TimeoutSec 1 -ErrorAction Stop|Out-Null; $port=$p}catch{}}}catch{}}}`,
96
+ `if(-not $port){$port=9120}`,
97
+ // Read stdin as UTF-8: [Console]::In decodes piped stdin with the console OEM
98
+ // codepage (e.g. CP949), garbling non-ASCII payload text.
99
+ `$body=(New-Object System.IO.StreamReader([Console]::OpenStandardInput(),[System.Text.Encoding]::UTF8)).ReadToEnd()`,
100
+ // Post UTF-8 bytes: Invoke-RestMethod encodes a string body as ISO-8859-1 when
101
+ // the content type carries no charset, replacing non-ASCII characters with '?'.
102
+ `$bytes=[System.Text.Encoding]::UTF8.GetBytes([string]$body)`,
103
+ `try{Invoke-RestMethod -Uri ('http://127.0.0.1:'+$port+'/hooks/'+$ev) -Method Post -Body $bytes -ContentType 'application/json; charset=utf-8' -TimeoutSec 2 -ErrorAction Stop|Out-Null}catch{}`,
104
+ ].join('; ');
105
+ return `powershell -NoProfile -ExecutionPolicy Bypass -Command "${ps}"`;
45
106
  }
46
107
  // Claude Code v2.1+ requires 3-level nesting: event → matcher group → hook handler.
47
108
  export function buildHookEntry(eventName) {
109
+ const command = process.platform === 'win32'
110
+ ? buildHookCommandWin(eventName)
111
+ : buildHookCommand(eventName);
48
112
  const handler = {
49
113
  type: 'command',
50
- command: buildHookCommand(eventName),
114
+ command,
51
115
  };
52
116
  // Tool-specific hooks (PreToolUse, PostToolUse) need a glob matcher to fire.
53
117
  // Empty string "" means "match nothing" for tool events — use "" for non-tool
@@ -185,6 +249,12 @@ export function migrateHooksIfNeeded() {
185
249
  applyHooks(settings);
186
250
  migrated = true;
187
251
  }
252
+ // Migration 5: upgrade fire-and-forget Stop hooks to the request-response
253
+ // form (turn-end directive queue needs the response echoed to Claude).
254
+ if (raw.includes('/hooks/Stop') && !/RESP=\$\(curl[^\n]*\/hooks\/Stop/.test(raw)) {
255
+ applyHooks(settings);
256
+ migrated = true;
257
+ }
188
258
  if (migrated) {
189
259
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
190
260
  }
@@ -194,12 +264,22 @@ export function migrateHooksIfNeeded() {
194
264
  }
195
265
  }
196
266
  // CLI execution
197
- const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
198
- import.meta.url === new URL(process.argv[1], 'file://').href;
267
+ // Use pathToFileURL so the comparison is correct on Windows too — process.argv[1]
268
+ // is a native path (E:\dev\...\install.js), but import.meta.url is a file:// URL
269
+ // (file:///E:/dev/.../install.js). Manually building `file://${argv[1]}` only
270
+ // matches on POSIX, which is why the installer used to be a silent no-op on
271
+ // Windows.
272
+ import { pathToFileURL } from 'url';
273
+ const isMainModule = process.argv[1] ? import.meta.url === pathToFileURL(process.argv[1]).href : false;
199
274
  if (isMainModule) {
200
275
  const action = process.argv[2] || 'install';
201
276
  if (action === 'uninstall') {
202
277
  uninstallHooks();
278
+ // The OpenCode observer plugin is AgentDeck-owned in its entirety, so
279
+ // uninstall removes the file (unlike ~/.codex/config.toml, where only
280
+ // the fenced block is AgentDeck's and removal has its own dedicated
281
+ // flow to avoid touching user TOML).
282
+ import('./opencode-install.js').then((m) => m.uninstallOpenCodeHooks()).catch(() => { });
203
283
  }
204
284
  else {
205
285
  installHooks();
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export declare const OPENCODE_PLUGIN_FILENAME = "agentdeck.js";
2
+ /** OpenCode's global config root: `$XDG_CONFIG_HOME/opencode` falling back
3
+ * to `~/.config/opencode`. Plugins load from the `plugins/` subdirectory
4
+ * (current OpenCode releases; older builds also scanned `plugin/`). */
5
+ export declare function opencodePluginPath(): string;
6
+ export interface OpenCodeInstallOptions {
7
+ /** Override the plugin file path (tests + non-default homes). */
8
+ pluginPath?: string;
9
+ }
10
+ export interface OpenCodeInstallResult {
11
+ installed: boolean;
12
+ /** Human-readable reason when `installed === false`. */
13
+ reason?: string;
14
+ }
15
+ /** Assemble the plugin source. Exported so tests can assert schema
16
+ * regressions (event names, payload fields, managed-session guard)
17
+ * without driving the file installer. */
18
+ export declare function opencodePluginSource(): string;
19
+ /** Install (or refresh) the AgentDeck OpenCode plugin. Idempotent: writes
20
+ * only when the on-disk content differs from the current template. Safe to
21
+ * call from setup, `agentdeck opencode`, and `agentdeck daemon install`. */
22
+ export declare function installOpenCodeHooksIfNeeded(opts?: OpenCodeInstallOptions): OpenCodeInstallResult;
23
+ /** Remove the AgentDeck OpenCode plugin. Idempotent — no-op when absent. */
24
+ export declare function uninstallOpenCodeHooks(opts?: OpenCodeInstallOptions): void;
25
+ //# sourceMappingURL=opencode-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-install.d.ts","sourceRoot":"","sources":["../src/opencode-install.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,wBAAwB,iBAAiB,CAAC;AAEvD;;wEAEwE;AACxE,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAUD;;0CAE0C;AAC1C,wBAAgB,oBAAoB,IAAI,MAAM,CA+P7C;AAeD;;6EAE6E;AAC7E,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,sBAA2B,GAAG,qBAAqB,CAerG;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,sBAA2B,GAAG,IAAI,CAK9E"}
@@ -0,0 +1,345 @@
1
+ // opencode-install.ts — Install the AgentDeck observation plugin into
2
+ // OpenCode's global plugin directory so standalone `opencode` sessions
3
+ // report turn boundaries to the daemon as opencode_* lifecycle hooks.
4
+ //
5
+ // Why a plugin and not PTY parsing or an SSE attach: OpenCode has no
6
+ // lifecycle-hook config (unlike Claude Code / Codex), and a standalone
7
+ // `opencode` TUI does not listen on any TCP port (the embedded server
8
+ // only exists when launched with `--port`, i.e. managed sessions), so
9
+ // the documented plugin API is the only stable, update-resilient way to
10
+ // observe sessions the user starts outside `agentdeck opencode`. This is
11
+ // the hooks-first direction: no terminal-output parsing anywhere.
12
+ //
13
+ // The plugin file is wholly AgentDeck-owned (header sentinel + version).
14
+ // Install is idempotent: re-running writes only when content changed.
15
+ // Managed sessions are excluded inside the plugin itself via the
16
+ // AGENTDECK_PORT env marker (see plugin source below) — their session
17
+ // bridge already ingests the richer SSE stream, and double-posting would
18
+ // double-count every turn.
19
+ //
20
+ // Counterpart consumers:
21
+ // - bridge/src/daemon-server.ts /hooks/ handler (opencode_* → shared
22
+ // observed-session pipeline: APME run/turn + chat_start/chat_response)
23
+ // + GET /opencode/commands steering long-poll (opencode-steering.ts)
24
+ // - apple .../DaemonServer.swift ingests opencode_* (2026-07-12) and
25
+ // serves the same /opencode/commands queue (OpenCodeCommandQueue).
26
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, unlinkSync } from 'fs';
27
+ import { dirname, join } from 'path';
28
+ import { homedir } from 'os';
29
+ export const OPENCODE_PLUGIN_FILENAME = 'agentdeck.js';
30
+ /** OpenCode's global config root: `$XDG_CONFIG_HOME/opencode` falling back
31
+ * to `~/.config/opencode`. Plugins load from the `plugins/` subdirectory
32
+ * (current OpenCode releases; older builds also scanned `plugin/`). */
33
+ export function opencodePluginPath() {
34
+ const xdg = process.env.XDG_CONFIG_HOME;
35
+ const configRoot = xdg && xdg.trim() ? xdg : join(homedir(), '.config');
36
+ return join(configRoot, 'opencode', 'plugins', OPENCODE_PLUGIN_FILENAME);
37
+ }
38
+ /** Honour `AGENTDECK_NO_OPENCODE_HOOKS=1` from the environment (same
39
+ * opt-out contract as AGENTDECK_NO_CODEX_HOOKS). */
40
+ function envOptOut() {
41
+ return process.env.AGENTDECK_NO_OPENCODE_HOOKS === '1';
42
+ }
43
+ const PLUGIN_VERSION = 2;
44
+ /** Assemble the plugin source. Exported so tests can assert schema
45
+ * regressions (event names, payload fields, managed-session guard)
46
+ * without driving the file installer. */
47
+ export function opencodePluginSource() {
48
+ return `// agentdeck-opencode-plugin v${PLUGIN_VERSION} — AUTO-GENERATED by AgentDeck.
49
+ // Do not edit: reinstalls overwrite this file. Remove it (or set
50
+ // AGENTDECK_NO_OPENCODE_HOOKS=1 before installing) to opt out.
51
+ //
52
+ // Forwards OpenCode lifecycle events to the AgentDeck daemon as
53
+ // opencode_* hooks — the same observed-session pipeline Claude Code and
54
+ // Codex use — so standalone \`opencode\` sessions appear on the AgentDeck
55
+ // timeline with the same prompt → response turn shape. Every POST is
56
+ // fire-and-forget with a hard timeout; OpenCode never blocks on AgentDeck.
57
+
58
+ import { readFileSync } from "node:fs";
59
+ import { homedir } from "node:os";
60
+ import { join } from "node:path";
61
+
62
+ const PORT_TTL_MS = 60000;
63
+ let cachedPort = null;
64
+ let cachedAt = 0;
65
+
66
+ async function resolvePort() {
67
+ const now = Date.now();
68
+ if (cachedPort && now - cachedAt < PORT_TTL_MS) return cachedPort;
69
+ const home = homedir();
70
+ const candidates = [];
71
+ for (const f of [
72
+ join(home, ".agentdeck", "daemon.json"),
73
+ join(home, "Library/Containers/bound.serendipity.agent.deck/Data/Library/Application Support/AgentDeck/daemon.json"),
74
+ join(home, "Library/Group Containers/group.bound.serendipity.agent.deck/daemon.json"),
75
+ ]) {
76
+ try {
77
+ const d = JSON.parse(readFileSync(f, "utf-8"));
78
+ const p = d.httpPort || d.port;
79
+ if (typeof p === "number" && p > 0 && !candidates.includes(p)) candidates.push(p);
80
+ } catch { /* missing/malformed — try next */ }
81
+ }
82
+ if (!candidates.includes(9120)) candidates.push(9120);
83
+ for (const p of candidates) {
84
+ try {
85
+ const r = await fetch("http://127.0.0.1:" + p + "/health", { signal: AbortSignal.timeout(300) });
86
+ if (r.ok) { cachedPort = p; cachedAt = now; return p; }
87
+ } catch { /* dead port — try next */ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function post(event, body) {
93
+ resolvePort().then((port) => {
94
+ if (!port) return;
95
+ return fetch("http://127.0.0.1:" + port + "/hooks/" + event, {
96
+ method: "POST",
97
+ headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify(body),
99
+ signal: AbortSignal.timeout(800),
100
+ });
101
+ }).catch(() => { /* daemon down — observation is best-effort */ });
102
+ }
103
+
104
+ // ── Steering loop ──
105
+ // The daemon queues device commands per session (soft/hard interrupt, prompt
106
+ // injection); this loop long-polls GET /opencode/commands and executes them
107
+ // through the in-process SDK client — abort is immediate, prompts land even
108
+ // while the session is idle. One loop per observed session; stops on
109
+ // opencode_stop-less session death via the poll's own error backoff.
110
+ const steerLoops = new Set();
111
+
112
+ function startSteerLoop(client, sessionID) {
113
+ if (!client || steerLoops.has(sessionID)) return;
114
+ steerLoops.add(sessionID);
115
+ (async () => {
116
+ let failures = 0;
117
+ while (steerLoops.has(sessionID)) {
118
+ const port = await resolvePort();
119
+ if (!port) { await new Promise((r) => setTimeout(r, 30000)); continue; }
120
+ try {
121
+ const r = await fetch(
122
+ "http://127.0.0.1:" + port + "/opencode/commands?sid=" + encodeURIComponent(sessionID) + "&wait=25",
123
+ { signal: AbortSignal.timeout(32000) },
124
+ );
125
+ if (!r.ok) throw new Error("poll " + r.status);
126
+ failures = 0;
127
+ const { commands } = await r.json();
128
+ for (const cmd of commands || []) {
129
+ try {
130
+ if (cmd.type === "interrupt") {
131
+ await client.session.abort({ path: { id: sessionID } });
132
+ } else if (cmd.type === "send_prompt" && cmd.text) {
133
+ const body = { parts: [{ type: "text", text: cmd.text }] };
134
+ // promptAsync (POST /session/{id}/prompt_async) returns
135
+ // immediately; session.prompt BLOCKS until the whole assistant
136
+ // turn completes, so the older fallback must not be awaited or
137
+ // it would stall this loop for the entire turn.
138
+ if (client.session.promptAsync) await client.session.promptAsync({ path: { id: sessionID }, body });
139
+ else if (client.session.prompt) client.session.prompt({ path: { id: sessionID }, body }).catch(() => {});
140
+ else if (client.session.chat) client.session.chat({ path: { id: sessionID }, body }).catch(() => {});
141
+ } else if (cmd.type === "permission_respond" && cmd.permissionId) {
142
+ // Resolve a pending OpenCode permission from the device.
143
+ // "once" (not "always") keeps device-approval one-shot.
144
+ if (client.postSessionIdPermissionsPermissionId) {
145
+ await client.postSessionIdPermissionsPermissionId({
146
+ path: { id: sessionID, permissionID: cmd.permissionId },
147
+ body: { response: cmd.response === "allow" ? "once" : "reject" },
148
+ });
149
+ }
150
+ }
151
+ } catch { /* one bad command must not kill the loop */ }
152
+ }
153
+ } catch {
154
+ failures += 1;
155
+ // Backoff on repeated failures so a dead daemon costs one probe/30s.
156
+ await new Promise((r) => setTimeout(r, Math.min(30000, 1000 * failures)));
157
+ }
158
+ }
159
+ })();
160
+ }
161
+
162
+ function stopSteerLoop(sessionID) {
163
+ steerLoops.delete(sessionID);
164
+ }
165
+
166
+ export const AgentDeckObserver = async ({ directory, client }) => {
167
+ // Managed \`agentdeck opencode\` sessions already stream structured SSE
168
+ // events through their session bridge; posting hooks too would
169
+ // double-count every turn. AGENTDECK_PORT marks a managed PTY.
170
+ if (process.env.AGENTDECK_PORT) return {};
171
+
172
+ const cwd = directory;
173
+ const announced = new Set(); // sessionIDs whose opencode_session_start was posted
174
+ const userMsgs = new Map(); // user messageID -> sessionID
175
+ const promptSent = new Set(); // user messageIDs whose prompt_submit was posted
176
+ const promptTimers = new Map();// user messageID -> flush timer
177
+ const responses = new Map(); // sessionID -> latest assistant text
178
+ const startedAt = new Map(); // sessionID -> turn start ms
179
+ const toolPhase = new Map(); // tool callID -> "start" | "end"
180
+
181
+ function announce(sessionID) {
182
+ if (!sessionID || announced.has(sessionID)) return;
183
+ announced.add(sessionID);
184
+ post("opencode_session_start", { session_id: sessionID, cwd });
185
+ // Steering: start draining device commands for this session (abort /
186
+ // prompt injection through the in-process client). Loop lives for the
187
+ // opencode process lifetime — sessions in one TUI are few and cheap.
188
+ startSteerLoop(client, sessionID);
189
+ }
190
+
191
+ function sendPrompt(messageID, sessionID, prompt) {
192
+ if (promptSent.has(messageID)) return;
193
+ promptSent.add(messageID);
194
+ const t = promptTimers.get(messageID);
195
+ if (t) { clearTimeout(t); promptTimers.delete(messageID); }
196
+ if (!startedAt.has(sessionID)) startedAt.set(sessionID, Date.now());
197
+ post("opencode_user_prompt_submit", { session_id: sessionID, prompt: prompt || "", cwd });
198
+ }
199
+
200
+ return {
201
+ event: async ({ event }) => {
202
+ try {
203
+ const type = event.type;
204
+ const props = event.properties || {};
205
+ if (type === "message.updated") {
206
+ const info = props.info || {};
207
+ const sessionID = info.sessionID;
208
+ announce(sessionID);
209
+ if (info.role === "user" && info.id) {
210
+ // userMsgs/promptSent are dedup memory keyed on user messageID and
211
+ // must outlive the turn (see the session.idle note), so cap them
212
+ // here instead of clearing per turn — same bound as toolPhase.
213
+ if (userMsgs.size > 512) userMsgs.clear();
214
+ if (promptSent.size > 512) promptSent.clear();
215
+ userMsgs.set(info.id, sessionID);
216
+ // Prompt text usually streams via message.part.updated moments
217
+ // later; flush immediately when the info carries it, otherwise
218
+ // wait briefly so the chat_start row shows the real prompt.
219
+ const direct = typeof info.text === "string" ? info.text
220
+ : (typeof info.content === "string" ? info.content : "");
221
+ if (direct.trim()) {
222
+ sendPrompt(info.id, sessionID, direct.trim());
223
+ } else if (!promptTimers.has(info.id) && !promptSent.has(info.id)) {
224
+ const timer = setTimeout(() => sendPrompt(info.id, sessionID, ""), 800);
225
+ if (typeof timer.unref === "function") timer.unref();
226
+ promptTimers.set(info.id, timer);
227
+ }
228
+ }
229
+ } else if (type === "message.part.updated") {
230
+ const part = props.part || {};
231
+ const sessionID = part.sessionID;
232
+ announce(sessionID);
233
+ if (part.type === "text" && typeof part.text === "string") {
234
+ const owner = userMsgs.get(part.messageID);
235
+ if (owner) sendPrompt(part.messageID, owner, part.text.trim());
236
+ else responses.set(sessionID, part.text);
237
+ } else if (part.type === "tool" && part.callID) {
238
+ const status = (part.state && part.state.status) || "running";
239
+ const phase = toolPhase.get(part.callID);
240
+ if (status === "completed" || status === "error") {
241
+ if (phase !== "end") {
242
+ toolPhase.set(part.callID, "end");
243
+ post("opencode_tool_end", { session_id: sessionID, tool_name: part.tool || "tool", cwd });
244
+ }
245
+ } else if (!phase) {
246
+ toolPhase.set(part.callID, "start");
247
+ post("opencode_tool_start", { session_id: sessionID, tool_name: part.tool || "tool", cwd });
248
+ }
249
+ if (toolPhase.size > 512) toolPhase.clear();
250
+ }
251
+ } else if (type === "permission.asked" || type === "permission.updated") {
252
+ // OpenCode emits this only when it is GENUINELY asking the user —
253
+ // no prediction needed, zero false-positive risk. Forward it so
254
+ // devices can render Allow/Deny; the daemon replies through the
255
+ // steering queue as a permission_respond command.
256
+ const perm = props.permission || props.info || props || {};
257
+ const psid = perm.sessionID;
258
+ if (psid && perm.id) {
259
+ announce(psid);
260
+ post("opencode_permission_asked", {
261
+ session_id: psid,
262
+ permission_id: perm.id,
263
+ title: typeof perm.title === "string" ? perm.title : "",
264
+ cwd,
265
+ });
266
+ }
267
+ } else if (type === "permission.replied") {
268
+ const perm = props.permission || props.info || props || {};
269
+ const psid = perm.sessionID;
270
+ if (psid) {
271
+ post("opencode_permission_replied", {
272
+ session_id: psid,
273
+ permission_id: perm.id || "",
274
+ cwd,
275
+ });
276
+ }
277
+ } else if (type === "session.idle") {
278
+ const sessionID = props.sessionID;
279
+ announce(sessionID);
280
+ post("opencode_stop", {
281
+ session_id: sessionID,
282
+ last_assistant_message: responses.get(sessionID) || "",
283
+ cwd,
284
+ });
285
+ responses.delete(sessionID);
286
+ startedAt.delete(sessionID);
287
+ // Do NOT clear userMsgs/promptSent for this session. OpenCode
288
+ // re-emits message.updated for a user message after the turn
289
+ // settles; dropping its promptSent entry let that trailing update
290
+ // re-post opencode_user_prompt_submit, which flips the row back to
291
+ // processing and opens a phantom turn. The session is already idle
292
+ // by then, so no session.idle follows to close it and the row reads
293
+ // WORKING until the 30-min idle TTL evicts it. A messageID is never
294
+ // re-submitted, so these entries are pure dedup memory — bounded
295
+ // where they are added, not here.
296
+ }
297
+ } catch { /* never throw into OpenCode's event loop */ }
298
+ },
299
+ };
300
+ };
301
+ `;
302
+ }
303
+ function writeTextAtomic(text, path) {
304
+ const dir = dirname(path);
305
+ try {
306
+ if (!existsSync(dir))
307
+ mkdirSync(dir, { recursive: true });
308
+ const tmp = `${path}.agentdeck.tmp`;
309
+ writeFileSync(tmp, text, 'utf-8');
310
+ renameSync(tmp, path);
311
+ return true;
312
+ }
313
+ catch {
314
+ return false;
315
+ }
316
+ }
317
+ /** Install (or refresh) the AgentDeck OpenCode plugin. Idempotent: writes
318
+ * only when the on-disk content differs from the current template. Safe to
319
+ * call from setup, `agentdeck opencode`, and `agentdeck daemon install`. */
320
+ export function installOpenCodeHooksIfNeeded(opts = {}) {
321
+ if (envOptOut())
322
+ return { installed: false, reason: 'AGENTDECK_NO_OPENCODE_HOOKS=1' };
323
+ const path = opts.pluginPath ?? opencodePluginPath();
324
+ const desired = opencodePluginSource();
325
+ try {
326
+ if (existsSync(path) && readFileSync(path, 'utf-8') === desired) {
327
+ return { installed: true };
328
+ }
329
+ }
330
+ catch { /* unreadable — rewrite below */ }
331
+ if (writeTextAtomic(desired, path)) {
332
+ return { installed: true };
333
+ }
334
+ return { installed: false, reason: `write failed: ${path}` };
335
+ }
336
+ /** Remove the AgentDeck OpenCode plugin. Idempotent — no-op when absent. */
337
+ export function uninstallOpenCodeHooks(opts = {}) {
338
+ const path = opts.pluginPath ?? opencodePluginPath();
339
+ try {
340
+ if (existsSync(path))
341
+ unlinkSync(path);
342
+ }
343
+ catch { /* best-effort */ }
344
+ }
345
+ //# sourceMappingURL=opencode-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-install.js","sourceRoot":"","sources":["../src/opencode-install.ts"],"names":[],"mappings":"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"}