@elhu/pit 0.1.1
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/LICENSE +21 -0
- package/README.md +380 -0
- package/dist/adapters/claude-code.d.ts +70 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +166 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +49 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +53 -0
- package/dist/adapters/opencode.d.ts.map +1 -0
- package/dist/adapters/opencode.js +120 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/process-utils.d.ts +29 -0
- package/dist/adapters/process-utils.d.ts.map +1 -0
- package/dist/adapters/process-utils.js +96 -0
- package/dist/adapters/process-utils.js.map +1 -0
- package/dist/adapters/types.d.ts +41 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/assets.generated.d.ts +13 -0
- package/dist/assets.generated.d.ts.map +1 -0
- package/dist/assets.generated.js +162 -0
- package/dist/assets.generated.js.map +1 -0
- package/dist/beads.d.ts +85 -0
- package/dist/beads.d.ts.map +1 -0
- package/dist/beads.js +120 -0
- package/dist/beads.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +10 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +58 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/cleanup.d.ts +13 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +174 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +162 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install-keybinding.d.ts +61 -0
- package/dist/commands/install-keybinding.d.ts.map +1 -0
- package/dist/commands/install-keybinding.js +138 -0
- package/dist/commands/install-keybinding.js.map +1 -0
- package/dist/commands/install-status.d.ts +35 -0
- package/dist/commands/install-status.d.ts.map +1 -0
- package/dist/commands/install-status.js +115 -0
- package/dist/commands/install-status.js.map +1 -0
- package/dist/commands/log.d.ts +7 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +60 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/pause.d.ts +12 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +47 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/resume.d.ts +12 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +59 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/shared.d.ts +7 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +56 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.d.ts +12 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +274 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +24 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +101 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +52 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/teardown.d.ts +15 -0
- package/dist/commands/teardown.d.ts.map +1 -0
- package/dist/commands/teardown.js +72 -0
- package/dist/commands/teardown.js.map +1 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/client.d.ts +38 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +254 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/context.d.ts +63 -0
- package/dist/daemon/context.d.ts.map +1 -0
- package/dist/daemon/context.js +14 -0
- package/dist/daemon/context.js.map +1 -0
- package/dist/daemon/handlers.d.ts +79 -0
- package/dist/daemon/handlers.d.ts.map +1 -0
- package/dist/daemon/handlers.js +1260 -0
- package/dist/daemon/handlers.js.map +1 -0
- package/dist/daemon/index.d.ts +6 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +7 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +56 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +341 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/protocol.d.ts +174 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +3 -0
- package/dist/daemon/protocol.js.map +1 -0
- package/dist/daemon/recovery.d.ts +37 -0
- package/dist/daemon/recovery.d.ts.map +1 -0
- package/dist/daemon/recovery.js +197 -0
- package/dist/daemon/recovery.js.map +1 -0
- package/dist/daemon/server.d.ts +31 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +294 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/socket.d.ts +18 -0
- package/dist/daemon/socket.d.ts.map +1 -0
- package/dist/daemon/socket.js +36 -0
- package/dist/daemon/socket.js.map +1 -0
- package/dist/daemon/state.d.ts +60 -0
- package/dist/daemon/state.d.ts.map +1 -0
- package/dist/daemon/state.js +156 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/daemon/systemd.d.ts +19 -0
- package/dist/daemon/systemd.d.ts.map +1 -0
- package/dist/daemon/systemd.js +131 -0
- package/dist/daemon/systemd.js.map +1 -0
- package/dist/hooks/claude-code-hook.d.ts +32 -0
- package/dist/hooks/claude-code-hook.d.ts.map +1 -0
- package/dist/hooks/claude-code-hook.js +112 -0
- package/dist/hooks/claude-code-hook.js.map +1 -0
- package/dist/instructions-template.d.ts +9 -0
- package/dist/instructions-template.d.ts.map +1 -0
- package/dist/instructions-template.js +123 -0
- package/dist/instructions-template.js.map +1 -0
- package/dist/logger.d.ts +25 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +44 -0
- package/dist/logger.js.map +1 -0
- package/dist/loop.d.ts +88 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +161 -0
- package/dist/loop.js.map +1 -0
- package/dist/orchestrator-instructions-template.d.ts +13 -0
- package/dist/orchestrator-instructions-template.d.ts.map +1 -0
- package/dist/orchestrator-instructions-template.js +147 -0
- package/dist/orchestrator-instructions-template.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +25 -0
- package/dist/output.js.map +1 -0
- package/dist/plugin/pit.js +57 -0
- package/dist/session.d.ts +55 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +135 -0
- package/dist/session.js.map +1 -0
- package/dist/setup.d.ts +92 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +382 -0
- package/dist/setup.js.map +1 -0
- package/dist/shell-quote.d.ts +16 -0
- package/dist/shell-quote.d.ts.map +1 -0
- package/dist/shell-quote.js +18 -0
- package/dist/shell-quote.js.map +1 -0
- package/dist/signals.d.ts +17 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +26 -0
- package/dist/signals.js.map +1 -0
- package/dist/state-machine.d.ts +74 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +153 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/tmux.d.ts +101 -0
- package/dist/tmux.d.ts.map +1 -0
- package/dist/tmux.js +208 -0
- package/dist/tmux.js.map +1 -0
- package/dist/worktree.d.ts +33 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +116 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter factory — resolves the correct AgentAdapter based on the requested
|
|
3
|
+
* agent name, or auto-detects based on what is installed.
|
|
4
|
+
*/
|
|
5
|
+
import { OpenCodeAdapter } from "./opencode.js";
|
|
6
|
+
import { ClaudeCodeAdapter } from "./claude-code.js";
|
|
7
|
+
const SUPPORTED_AGENTS = ["opencode", "claude-code"];
|
|
8
|
+
function isSupportedAgent(agent) {
|
|
9
|
+
return SUPPORTED_AGENTS.includes(agent);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve an AgentAdapter by name.
|
|
13
|
+
*
|
|
14
|
+
* @param agent - "auto" | "opencode" | "claude-code"
|
|
15
|
+
* @returns The resolved adapter (Promise<AgentAdapter>)
|
|
16
|
+
*
|
|
17
|
+
* "auto" tries opencode first, then claude-code. Throws if neither is installed.
|
|
18
|
+
* Named agents throw if not installed.
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveAdapter(agent) {
|
|
21
|
+
if (agent === "auto") {
|
|
22
|
+
const opencode = new OpenCodeAdapter();
|
|
23
|
+
if (await opencode.checkInstalled())
|
|
24
|
+
return opencode;
|
|
25
|
+
const claudeCode = new ClaudeCodeAdapter();
|
|
26
|
+
if (await claudeCode.checkInstalled())
|
|
27
|
+
return claudeCode;
|
|
28
|
+
throw new Error(`No supported agent is installed. Install one of: ${SUPPORTED_AGENTS.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
if (!isSupportedAgent(agent)) {
|
|
31
|
+
throw new Error(`Unknown agent "${agent}". Supported agents: ${SUPPORTED_AGENTS.join(", ")}`);
|
|
32
|
+
}
|
|
33
|
+
if (agent === "claude-code") {
|
|
34
|
+
const adapter = new ClaudeCodeAdapter();
|
|
35
|
+
const installed = await adapter.checkInstalled();
|
|
36
|
+
if (!installed) {
|
|
37
|
+
throw new Error(`Agent "${agent}" is not installed.`);
|
|
38
|
+
}
|
|
39
|
+
return adapter;
|
|
40
|
+
}
|
|
41
|
+
// agent === "opencode"
|
|
42
|
+
const adapter = new OpenCodeAdapter();
|
|
43
|
+
const installed = await adapter.checkInstalled();
|
|
44
|
+
if (!installed) {
|
|
45
|
+
throw new Error(`Agent "${agent}" is not installed.`);
|
|
46
|
+
}
|
|
47
|
+
return adapter;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE,aAAa,CAAU,CAAC;AAG9D,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAQ,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa;IAChD,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACvC,IAAI,MAAM,QAAQ,CAAC,cAAc,EAAE;YAAE,OAAO,QAAQ,CAAC;QAErD,MAAM,UAAU,GAAG,IAAI,iBAAiB,EAAE,CAAC;QAC3C,IAAI,MAAM,UAAU,CAAC,cAAc,EAAE;YAAE,OAAO,UAAU,CAAC;QAEzD,MAAM,IAAI,KAAK,CACb,oDAAoD,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,wBAAwB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC;QACjD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,qBAAqB,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,uBAAuB;IACvB,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC;IACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,qBAAqB,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCodeAdapter — implements AgentAdapter for the opencode TUI agent.
|
|
3
|
+
*
|
|
4
|
+
* opencode is a full-screen bubbletea TUI installed at ~/.opencode/bin/opencode.
|
|
5
|
+
* Its plugin directory is .opencode/plugins/ inside the project/worktree.
|
|
6
|
+
* Instructions are written to .opencode/pit-instructions.md and referenced
|
|
7
|
+
* in opencode.json to avoid overwriting the project's AGENTS.md.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentAdapter } from "./types.js";
|
|
10
|
+
export declare class OpenCodeAdapter implements AgentAdapter {
|
|
11
|
+
readonly name = "opencode";
|
|
12
|
+
startCommand(_worktree: string, _env: Record<string, string>, model?: string): string;
|
|
13
|
+
/** opencode is a full-screen TUI; capture-pane regex is unreliable. */
|
|
14
|
+
readonly readinessPattern: undefined;
|
|
15
|
+
readonly clearCommand = "/new";
|
|
16
|
+
/**
|
|
17
|
+
* Install the pit plugin into the worktree's .opencode/plugins/ directory.
|
|
18
|
+
* The plugin content is embedded as an inline string (OPENCODE_PLUGIN_JS) at
|
|
19
|
+
* build time, so it is always the correct version regardless of where pit is
|
|
20
|
+
* installed globally (npm link, npm install -g, etc.). Resolving via __dirname
|
|
21
|
+
* was unreliable because it pointed to the global install's dist directory
|
|
22
|
+
* rather than the worktree's bundled copy.
|
|
23
|
+
*/
|
|
24
|
+
installEventBridge(worktreeDir: string, _epic: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Write agent instructions to .opencode/pit-instructions.md and ensure
|
|
27
|
+
* opencode.json references it. Preserves any existing opencode.json keys.
|
|
28
|
+
* Does NOT modify the project's AGENTS.md.
|
|
29
|
+
*/
|
|
30
|
+
writeInstructions(worktreeDir: string, content: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check whether the opencode CLI is available on PATH.
|
|
33
|
+
* Returns true if `which opencode` exits 0, false otherwise.
|
|
34
|
+
*/
|
|
35
|
+
checkInstalled(): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Wait for opencode to be ready using a simple timeout.
|
|
38
|
+
* Signal-based readiness will be added in epic-loop once the full
|
|
39
|
+
* integration is in place.
|
|
40
|
+
*/
|
|
41
|
+
waitForReady({ timeoutMs }: {
|
|
42
|
+
timeoutMs: number;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Verify the opencode process is running in the given tmux window.
|
|
46
|
+
*
|
|
47
|
+
* The tmux pane PID is the shell (zsh/bash), not opencode itself.
|
|
48
|
+
* We walk the process tree rooted at the pane PID and check whether any
|
|
49
|
+
* descendant process has a comm of "node" or "opencode".
|
|
50
|
+
*/
|
|
51
|
+
verifyRunning(tmuxSession: string, windowName: string): Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=opencode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/adapters/opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAS/C,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,cAAc;IAE3B,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAKrF,uEAAuE;IACvE,QAAQ,CAAC,gBAAgB,YAAa;IAEtC,QAAQ,CAAC,YAAY,UAAU;IAE/B;;;;;;;OAOG;IACG,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3E;;;;OAIG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B5E;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAYxC;;;;OAIG;IACG,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;;;;OAMG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAsB/E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCodeAdapter — implements AgentAdapter for the opencode TUI agent.
|
|
3
|
+
*
|
|
4
|
+
* opencode is a full-screen bubbletea TUI installed at ~/.opencode/bin/opencode.
|
|
5
|
+
* Its plugin directory is .opencode/plugins/ inside the project/worktree.
|
|
6
|
+
* Instructions are written to .opencode/pit-instructions.md and referenced
|
|
7
|
+
* in opencode.json to avoid overwriting the project's AGENTS.md.
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import fs from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { execTmux } from "../tmux.js";
|
|
13
|
+
import { shellQuote } from "../shell-quote.js";
|
|
14
|
+
import { OPENCODE_PLUGIN_JS } from "../assets.generated.js";
|
|
15
|
+
import { hasAgentDescendant } from "./process-utils.js";
|
|
16
|
+
/**
|
|
17
|
+
* Names that indicate opencode is running as a descendant process.
|
|
18
|
+
* opencode is distributed as a Node.js binary, so it may appear as "node"
|
|
19
|
+
* or "opencode" depending on how it was installed.
|
|
20
|
+
*/
|
|
21
|
+
const OPENCODE_COMM_NAMES = new Set(["node", "opencode"]);
|
|
22
|
+
export class OpenCodeAdapter {
|
|
23
|
+
name = "opencode";
|
|
24
|
+
startCommand(_worktree, _env, model) {
|
|
25
|
+
const base = "opencode";
|
|
26
|
+
return model ? `${base} --model ${shellQuote(model)}` : base;
|
|
27
|
+
}
|
|
28
|
+
/** opencode is a full-screen TUI; capture-pane regex is unreliable. */
|
|
29
|
+
readinessPattern = undefined;
|
|
30
|
+
clearCommand = "/new";
|
|
31
|
+
/**
|
|
32
|
+
* Install the pit plugin into the worktree's .opencode/plugins/ directory.
|
|
33
|
+
* The plugin content is embedded as an inline string (OPENCODE_PLUGIN_JS) at
|
|
34
|
+
* build time, so it is always the correct version regardless of where pit is
|
|
35
|
+
* installed globally (npm link, npm install -g, etc.). Resolving via __dirname
|
|
36
|
+
* was unreliable because it pointed to the global install's dist directory
|
|
37
|
+
* rather than the worktree's bundled copy.
|
|
38
|
+
*/
|
|
39
|
+
async installEventBridge(worktreeDir, _epic) {
|
|
40
|
+
const pluginsDir = path.join(worktreeDir, ".opencode", "plugins");
|
|
41
|
+
await fs.mkdir(pluginsDir, { recursive: true });
|
|
42
|
+
const destPath = path.join(pluginsDir, "pit.js");
|
|
43
|
+
await fs.writeFile(destPath, OPENCODE_PLUGIN_JS, "utf8");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Write agent instructions to .opencode/pit-instructions.md and ensure
|
|
47
|
+
* opencode.json references it. Preserves any existing opencode.json keys.
|
|
48
|
+
* Does NOT modify the project's AGENTS.md.
|
|
49
|
+
*/
|
|
50
|
+
async writeInstructions(worktreeDir, content) {
|
|
51
|
+
const opencodeDir = path.join(worktreeDir, ".opencode");
|
|
52
|
+
await fs.mkdir(opencodeDir, { recursive: true });
|
|
53
|
+
const instructionsPath = path.join(opencodeDir, "pit-instructions.md");
|
|
54
|
+
await fs.writeFile(instructionsPath, content, "utf8");
|
|
55
|
+
const opencodeJsonPath = path.join(worktreeDir, "opencode.json");
|
|
56
|
+
let config = {};
|
|
57
|
+
try {
|
|
58
|
+
const raw = await fs.readFile(opencodeJsonPath, "utf8");
|
|
59
|
+
config = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// File does not exist or is invalid JSON — start fresh
|
|
63
|
+
}
|
|
64
|
+
const instructionsEntry = ".opencode/pit-instructions.md";
|
|
65
|
+
const existingInstructions = Array.isArray(config.instructions)
|
|
66
|
+
? config.instructions
|
|
67
|
+
: [];
|
|
68
|
+
if (!existingInstructions.includes(instructionsEntry)) {
|
|
69
|
+
config.instructions = [...existingInstructions, instructionsEntry];
|
|
70
|
+
}
|
|
71
|
+
await fs.writeFile(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check whether the opencode CLI is available on PATH.
|
|
75
|
+
* Returns true if `which opencode` exits 0, false otherwise.
|
|
76
|
+
*/
|
|
77
|
+
async checkInstalled() {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
execFile("which", ["opencode"], (err) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
resolve(false);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
resolve(true);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Wait for opencode to be ready using a simple timeout.
|
|
91
|
+
* Signal-based readiness will be added in epic-loop once the full
|
|
92
|
+
* integration is in place.
|
|
93
|
+
*/
|
|
94
|
+
async waitForReady({ timeoutMs }) {
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verify the opencode process is running in the given tmux window.
|
|
99
|
+
*
|
|
100
|
+
* The tmux pane PID is the shell (zsh/bash), not opencode itself.
|
|
101
|
+
* We walk the process tree rooted at the pane PID and check whether any
|
|
102
|
+
* descendant process has a comm of "node" or "opencode".
|
|
103
|
+
*/
|
|
104
|
+
async verifyRunning(tmuxSession, windowName) {
|
|
105
|
+
try {
|
|
106
|
+
// Get the PID of the process in the pane (this is the shell, e.g. zsh)
|
|
107
|
+
const { stdout } = await execTmux("list-panes", "-t", `${tmuxSession}:${windowName}`, "-F", "#{pane_pid}");
|
|
108
|
+
const panePid = stdout.trim();
|
|
109
|
+
if (!panePid) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return await hasAgentDescendant(panePid, OPENCODE_COMM_NAMES);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Any error means we can't verify - assume not running
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=opencode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/adapters/opencode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared process-tree utilities for agent adapters.
|
|
3
|
+
*
|
|
4
|
+
* These helpers allow adapter implementations to walk a tmux pane's process
|
|
5
|
+
* tree and determine whether a specific agent process is running as a
|
|
6
|
+
* descendant of the shell.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Return the direct child PIDs of the given PID.
|
|
10
|
+
* Tries /proc/<pid>/task/<pid>/children (Linux), then falls back to
|
|
11
|
+
* `pgrep -P <pid>` which works on both Linux and macOS.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getChildPids(pid: string): Promise<string[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Read the comm (process name) for the given PID.
|
|
16
|
+
* Returns null when the process no longer exists or cannot be read.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getComm(pid: string): Promise<string | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Walk the process tree rooted at `panePid` (BFS) and return true if any
|
|
21
|
+
* descendant process has a name matching one of the given comm names.
|
|
22
|
+
* Limits traversal depth to avoid infinite loops on misconfigured systems.
|
|
23
|
+
*
|
|
24
|
+
* @param panePid - The tmux pane's PID (typically the shell).
|
|
25
|
+
* @param commNames - Set of process comm names to match (e.g. new Set(['claude', 'node'])).
|
|
26
|
+
* @param maxDepth - Maximum BFS depth (default: 8).
|
|
27
|
+
*/
|
|
28
|
+
export declare function hasAgentDescendant(panePid: string, commNames: Set<string>, maxDepth?: number): Promise<boolean>;
|
|
29
|
+
//# sourceMappingURL=process-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-utils.d.ts","sourceRoot":"","sources":["../../src/adapters/process-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBjE;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBjE;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,QAAQ,SAAI,GACX,OAAO,CAAC,OAAO,CAAC,CA4BlB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared process-tree utilities for agent adapters.
|
|
3
|
+
*
|
|
4
|
+
* These helpers allow adapter implementations to walk a tmux pane's process
|
|
5
|
+
* tree and determine whether a specific agent process is running as a
|
|
6
|
+
* descendant of the shell.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
/**
|
|
11
|
+
* Return the direct child PIDs of the given PID.
|
|
12
|
+
* Tries /proc/<pid>/task/<pid>/children (Linux), then falls back to
|
|
13
|
+
* `pgrep -P <pid>` which works on both Linux and macOS.
|
|
14
|
+
*/
|
|
15
|
+
export async function getChildPids(pid) {
|
|
16
|
+
// Linux fast path: /proc/<pid>/task/<pid>/children
|
|
17
|
+
const childrenPath = `/proc/${pid}/task/${pid}/children`;
|
|
18
|
+
try {
|
|
19
|
+
const data = await fs.readFile(childrenPath, "utf8");
|
|
20
|
+
const pids = data.trim().split(/\s+/).filter(Boolean);
|
|
21
|
+
if (pids.length > 0)
|
|
22
|
+
return pids;
|
|
23
|
+
// If the file exists but is empty, there really are no children
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Not on Linux or file unavailable — fall back to pgrep
|
|
28
|
+
}
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
execFile("pgrep", ["-P", pid], (_error, stdout) => {
|
|
31
|
+
// pgrep exits 1 when no processes match — that is not an error here
|
|
32
|
+
resolve(stdout.trim().split(/\s+/).filter(Boolean));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the comm (process name) for the given PID.
|
|
38
|
+
* Returns null when the process no longer exists or cannot be read.
|
|
39
|
+
*/
|
|
40
|
+
export async function getComm(pid) {
|
|
41
|
+
// Linux: /proc/<pid>/comm
|
|
42
|
+
try {
|
|
43
|
+
const comm = await fs.readFile(`/proc/${pid}/comm`, "utf8");
|
|
44
|
+
return comm.trim();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// macOS or process gone
|
|
48
|
+
}
|
|
49
|
+
// macOS / fallback: ps -p <pid> -o comm=
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
execFile("ps", ["-p", pid, "-o", "comm="], (error, stdout) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
resolve(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const name = stdout.trim();
|
|
57
|
+
resolve(name || null);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Walk the process tree rooted at `panePid` (BFS) and return true if any
|
|
63
|
+
* descendant process has a name matching one of the given comm names.
|
|
64
|
+
* Limits traversal depth to avoid infinite loops on misconfigured systems.
|
|
65
|
+
*
|
|
66
|
+
* @param panePid - The tmux pane's PID (typically the shell).
|
|
67
|
+
* @param commNames - Set of process comm names to match (e.g. new Set(['claude', 'node'])).
|
|
68
|
+
* @param maxDepth - Maximum BFS depth (default: 8).
|
|
69
|
+
*/
|
|
70
|
+
export async function hasAgentDescendant(panePid, commNames, maxDepth = 8) {
|
|
71
|
+
const queue = [{ pid: panePid, depth: 0 }];
|
|
72
|
+
while (queue.length > 0) {
|
|
73
|
+
const item = queue.shift();
|
|
74
|
+
if (!item)
|
|
75
|
+
break;
|
|
76
|
+
let comm;
|
|
77
|
+
try {
|
|
78
|
+
comm = await getComm(item.pid);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Skip nodes we can't read — continue BFS
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (comm !== null && commNames.has(comm)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (item.depth < maxDepth) {
|
|
88
|
+
const children = await getChildPids(item.pid);
|
|
89
|
+
for (const childPid of children) {
|
|
90
|
+
queue.push({ pid: childPid, depth: item.depth + 1 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=process-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/adapters/process-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentAdapter interface — the abstraction boundary between the agent-agnostic
|
|
3
|
+
* orchestrator and agent-specific behavior (Claude Code, opencode, etc.).
|
|
4
|
+
*/
|
|
5
|
+
export interface AgentAdapter {
|
|
6
|
+
/** Human-readable agent name (e.g., "opencode", "claude-code") */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The shell command to start the agent TUI in a tmux window */
|
|
9
|
+
startCommand(worktree: string, env: Record<string, string>, model?: string): string;
|
|
10
|
+
/** Regex to match against captured pane content to detect TUI readiness.
|
|
11
|
+
*
|
|
12
|
+
* Optional — currently unused. Both agents render in the terminal's alternate
|
|
13
|
+
* screen buffer. Reliable patterns exist in `capture-pane` output (see §8.3
|
|
14
|
+
* of REQUIREMENTS.md), but polling adds complexity for a narrow timing window
|
|
15
|
+
* where errors are recoverable anyway. The real loop control comes from agent
|
|
16
|
+
* event bridges (signal files), not startup/clear waits. Retained for future
|
|
17
|
+
* use if faster startup or correctness-critical readiness detection is needed.
|
|
18
|
+
*/
|
|
19
|
+
readinessPattern?: RegExp;
|
|
20
|
+
/** The command typed into the TUI to clear context (e.g., "/clear") */
|
|
21
|
+
clearCommand: string;
|
|
22
|
+
/** Install event bridge (plugin/hooks) into the worktree so the agent
|
|
23
|
+
* emits events to the daemon via Unix socket */
|
|
24
|
+
installEventBridge(worktreeDir: string, epic: string): Promise<void>;
|
|
25
|
+
/** Write project instructions to the location the agent auto-loads.
|
|
26
|
+
* For opencode: .opencode/pit-instructions.md + update opencode.json
|
|
27
|
+
* For Claude Code: CLAUDE.md */
|
|
28
|
+
writeInstructions(worktreeDir: string, content: string): Promise<void>;
|
|
29
|
+
/** Check if the agent CLI is installed and available */
|
|
30
|
+
checkInstalled(): Promise<boolean>;
|
|
31
|
+
/** Wait for the agent TUI to be ready for input.
|
|
32
|
+
* Initial startup and post-clear uses timeout-based waiting. */
|
|
33
|
+
waitForReady(options: {
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** Verify the agent process is running in the given tmux window.
|
|
37
|
+
* Returns true if agent is alive and responsive.
|
|
38
|
+
* Returns false if agent has crashed or is not running. */
|
|
39
|
+
verifyRunning(tmuxSession: string, windowName: string): Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IAEb,gEAAgE;IAChE,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEpF;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IAErB;qDACiD;IACjD,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE;;qCAEiC;IACjC,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE,wDAAwD;IACxD,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;qEACiE;IACjE,YAAY,CAAC,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5D;;gEAE4D;IAC5D,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled content of the opencode plugin (dist/plugin/pit.js).
|
|
3
|
+
* Embedded as a string so adapters can write it without resolving __dirname,
|
|
4
|
+
* which breaks when pit is installed globally via npm link or npm install -g.
|
|
5
|
+
*/
|
|
6
|
+
export declare const OPENCODE_PLUGIN_JS = "// src/plugin/pit.ts\nimport * as net from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nasync function sendToDaemon(socketPath, method, params) {\n return new Promise((resolve) => {\n const socket = net.createConnection(socketPath);\n const request = {\n id: randomUUID(),\n method,\n params\n };\n socket.on(\"connect\", () => {\n socket.write(JSON.stringify(request) + \"\\n\", () => {\n socket.destroy();\n resolve();\n });\n });\n socket.on(\"error\", (err) => {\n console.error(`[pit plugin] Socket error (${method}):`, err.message);\n socket.destroy();\n resolve();\n });\n });\n}\nvar PitPlugin = async () => {\n const epic = process.env.PIT_EPIC;\n const socketPath = process.env.PIT_SOCKET_PATH;\n if (!epic || !socketPath) {\n console.error(\"[pit plugin] PIT_EPIC and PIT_SOCKET_PATH env vars required. Plugin inactive.\");\n return {};\n }\n return {\n event: async ({ event }) => {\n try {\n if (event.type === \"session.idle\") {\n await sendToDaemon(socketPath, \"agent-idle\", { epicId: epic });\n }\n } catch (err) {\n console.error(\"[pit plugin] Error handling session.idle:\", err);\n }\n },\n \"permission.ask\": async ({ input }) => {\n try {\n await sendToDaemon(socketPath, \"agent-permission\", {\n epicId: epic,\n tool: input.type ?? \"unknown\",\n input: JSON.stringify(input)\n });\n } catch (err) {\n console.error(\"[pit plugin] Error handling permission.ask:\", err);\n }\n }\n };\n};\nexport {\n PitPlugin\n};\n";
|
|
7
|
+
/**
|
|
8
|
+
* Bundled content of the Claude Code hook (dist/hooks/claude-code-hook.js).
|
|
9
|
+
* Embedded as a string so adapters can write it without resolving __dirname,
|
|
10
|
+
* which breaks when pit is installed globally via npm link or npm install -g.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CLAUDE_CODE_HOOK_JS = "// src/hooks/claude-code-hook.ts\nimport * as net from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nfunction handleStopEvent(input) {\n if (input.stop_hook_active) {\n console.error(\"[pit hook] stop_hook_active=true, notifying daemon anyway\");\n }\n}\nfunction handlePermissionEvent(input) {\n return {\n tool: input.tool_name ?? \"unknown\",\n input: JSON.stringify(input.tool_input ?? {})\n };\n}\nasync function sendToDaemon(socketPath, method, params) {\n return new Promise((resolve) => {\n const socket = net.createConnection(socketPath);\n const request = {\n id: randomUUID(),\n method,\n params\n };\n socket.on(\"connect\", () => {\n socket.write(JSON.stringify(request) + \"\\n\", () => {\n socket.destroy();\n resolve();\n });\n });\n socket.on(\"error\", (err) => {\n console.error(`[pit hook] Socket error (${method}):`, err.message);\n socket.destroy();\n resolve();\n });\n });\n}\nasync function main() {\n const socketPath = process.env.PIT_SOCKET_PATH;\n const epicId = process.env.PIT_EPIC;\n if (!socketPath) {\n console.error(\"[pit hook] PIT_SOCKET_PATH not set, exiting\");\n process.exit(0);\n }\n if (!epicId) {\n console.error(\"[pit hook] PIT_EPIC not set, exiting\");\n process.exit(0);\n }\n const chunks = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n let parsed;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n console.error(\"[pit hook] Failed to parse stdin as JSON:\", err);\n process.exit(0);\n }\n if (!parsed || typeof parsed !== \"object\") {\n console.error(\"[pit hook] Parsed input is not an object, exiting\");\n process.exit(0);\n }\n const input = parsed;\n if (input.hook_event_name === \"Stop\") {\n handleStopEvent(input);\n await sendToDaemon(socketPath, \"agent-idle\", { epicId });\n } else if (input.hook_event_name === \"PermissionRequest\") {\n const { tool, input: toolInput } = handlePermissionEvent(input);\n await sendToDaemon(socketPath, \"agent-permission\", {\n epicId,\n tool,\n input: toolInput\n });\n } else {\n process.exit(0);\n }\n process.exit(0);\n}\nif (import.meta.url === `file://${process.argv[1]}`) {\n main().catch((err) => {\n console.error(\"[pit hook] Unexpected error:\", err);\n process.exit(0);\n });\n}\nexport {\n handlePermissionEvent,\n handleStopEvent,\n sendToDaemon\n};\n";
|
|
13
|
+
//# sourceMappingURL=assets.generated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.generated.d.ts","sourceRoot":"","sources":["../src/assets.generated.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,yoDAyD9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mgFAyF/B,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
|
|
2
|
+
// Generated by scripts/generate-assets.mjs from the bundled plugin/hook JS files.
|
|
3
|
+
// Re-run `npm run build` to regenerate.
|
|
4
|
+
/**
|
|
5
|
+
* Bundled content of the opencode plugin (dist/plugin/pit.js).
|
|
6
|
+
* Embedded as a string so adapters can write it without resolving __dirname,
|
|
7
|
+
* which breaks when pit is installed globally via npm link or npm install -g.
|
|
8
|
+
*/
|
|
9
|
+
export const OPENCODE_PLUGIN_JS = `// src/plugin/pit.ts
|
|
10
|
+
import * as net from "node:net";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
async function sendToDaemon(socketPath, method, params) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const socket = net.createConnection(socketPath);
|
|
15
|
+
const request = {
|
|
16
|
+
id: randomUUID(),
|
|
17
|
+
method,
|
|
18
|
+
params
|
|
19
|
+
};
|
|
20
|
+
socket.on("connect", () => {
|
|
21
|
+
socket.write(JSON.stringify(request) + "\\n", () => {
|
|
22
|
+
socket.destroy();
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
socket.on("error", (err) => {
|
|
27
|
+
console.error(\`[pit plugin] Socket error (\${method}):\`, err.message);
|
|
28
|
+
socket.destroy();
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
var PitPlugin = async () => {
|
|
34
|
+
const epic = process.env.PIT_EPIC;
|
|
35
|
+
const socketPath = process.env.PIT_SOCKET_PATH;
|
|
36
|
+
if (!epic || !socketPath) {
|
|
37
|
+
console.error("[pit plugin] PIT_EPIC and PIT_SOCKET_PATH env vars required. Plugin inactive.");
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
event: async ({ event }) => {
|
|
42
|
+
try {
|
|
43
|
+
if (event.type === "session.idle") {
|
|
44
|
+
await sendToDaemon(socketPath, "agent-idle", { epicId: epic });
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error("[pit plugin] Error handling session.idle:", err);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"permission.ask": async ({ input }) => {
|
|
51
|
+
try {
|
|
52
|
+
await sendToDaemon(socketPath, "agent-permission", {
|
|
53
|
+
epicId: epic,
|
|
54
|
+
tool: input.type ?? "unknown",
|
|
55
|
+
input: JSON.stringify(input)
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error("[pit plugin] Error handling permission.ask:", err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
PitPlugin
|
|
65
|
+
};
|
|
66
|
+
`;
|
|
67
|
+
/**
|
|
68
|
+
* Bundled content of the Claude Code hook (dist/hooks/claude-code-hook.js).
|
|
69
|
+
* Embedded as a string so adapters can write it without resolving __dirname,
|
|
70
|
+
* which breaks when pit is installed globally via npm link or npm install -g.
|
|
71
|
+
*/
|
|
72
|
+
export const CLAUDE_CODE_HOOK_JS = `// src/hooks/claude-code-hook.ts
|
|
73
|
+
import * as net from "node:net";
|
|
74
|
+
import { randomUUID } from "node:crypto";
|
|
75
|
+
function handleStopEvent(input) {
|
|
76
|
+
if (input.stop_hook_active) {
|
|
77
|
+
console.error("[pit hook] stop_hook_active=true, notifying daemon anyway");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function handlePermissionEvent(input) {
|
|
81
|
+
return {
|
|
82
|
+
tool: input.tool_name ?? "unknown",
|
|
83
|
+
input: JSON.stringify(input.tool_input ?? {})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function sendToDaemon(socketPath, method, params) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
const socket = net.createConnection(socketPath);
|
|
89
|
+
const request = {
|
|
90
|
+
id: randomUUID(),
|
|
91
|
+
method,
|
|
92
|
+
params
|
|
93
|
+
};
|
|
94
|
+
socket.on("connect", () => {
|
|
95
|
+
socket.write(JSON.stringify(request) + "\\n", () => {
|
|
96
|
+
socket.destroy();
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
socket.on("error", (err) => {
|
|
101
|
+
console.error(\`[pit hook] Socket error (\${method}):\`, err.message);
|
|
102
|
+
socket.destroy();
|
|
103
|
+
resolve();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function main() {
|
|
108
|
+
const socketPath = process.env.PIT_SOCKET_PATH;
|
|
109
|
+
const epicId = process.env.PIT_EPIC;
|
|
110
|
+
if (!socketPath) {
|
|
111
|
+
console.error("[pit hook] PIT_SOCKET_PATH not set, exiting");
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
if (!epicId) {
|
|
115
|
+
console.error("[pit hook] PIT_EPIC not set, exiting");
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
const chunks = [];
|
|
119
|
+
for await (const chunk of process.stdin) {
|
|
120
|
+
chunks.push(chunk);
|
|
121
|
+
}
|
|
122
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(raw);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error("[pit hook] Failed to parse stdin as JSON:", err);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
if (!parsed || typeof parsed !== "object") {
|
|
131
|
+
console.error("[pit hook] Parsed input is not an object, exiting");
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
const input = parsed;
|
|
135
|
+
if (input.hook_event_name === "Stop") {
|
|
136
|
+
handleStopEvent(input);
|
|
137
|
+
await sendToDaemon(socketPath, "agent-idle", { epicId });
|
|
138
|
+
} else if (input.hook_event_name === "PermissionRequest") {
|
|
139
|
+
const { tool, input: toolInput } = handlePermissionEvent(input);
|
|
140
|
+
await sendToDaemon(socketPath, "agent-permission", {
|
|
141
|
+
epicId,
|
|
142
|
+
tool,
|
|
143
|
+
input: toolInput
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
if (import.meta.url === \`file://\${process.argv[1]}\`) {
|
|
151
|
+
main().catch((err) => {
|
|
152
|
+
console.error("[pit hook] Unexpected error:", err);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
handlePermissionEvent,
|
|
158
|
+
handleStopEvent,
|
|
159
|
+
sendToDaemon
|
|
160
|
+
};
|
|
161
|
+
`;
|
|
162
|
+
//# sourceMappingURL=assets.generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.generated.js","sourceRoot":"","sources":["../src/assets.generated.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,kFAAkF;AAClF,wCAAwC;AAExC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFlC,CAAC"}
|