@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.
- package/dist/codex-install.d.ts +64 -0
- package/dist/codex-install.d.ts.map +1 -0
- package/dist/codex-install.js +342 -0
- package/dist/codex-install.js.map +1 -0
- package/dist/codex-mini-toml.d.ts +22 -0
- package/dist/codex-mini-toml.d.ts.map +1 -0
- package/dist/codex-mini-toml.js +209 -0
- package/dist/codex-mini-toml.js.map +1 -0
- package/dist/install.d.ts +23 -3
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +89 -9
- package/dist/install.js.map +1 -1
- package/dist/opencode-install.d.ts +25 -0
- package/dist/opencode-install.d.ts.map +1 -0
- package/dist/opencode-install.js +345 -0
- package/dist/opencode-install.js.map +1 -0
- package/package.json +2 -7
|
@@ -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
|
|
14
|
-
* 4.
|
|
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)
|
|
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[];
|
package/dist/install.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"
|
|
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
|
|
25
|
-
* 4.
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
198
|
-
|
|
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();
|
package/dist/install.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAE7B,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,cAAc;IACd,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,MAAM;IACN,cAAc;IACd,kBAAkB;CACV,CAAC;AAEX
|
|
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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"}
|