@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
package/dist/loop.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop runner for pit.
|
|
3
|
+
*
|
|
4
|
+
* startLoop() starts the autonomous ticket loop for a single epic:
|
|
5
|
+
* - Creates the state machine and immediately advances it to RUNNING
|
|
6
|
+
* - On state changes: updates the tmux status bar
|
|
7
|
+
* - Writes all transitions to the session log
|
|
8
|
+
*
|
|
9
|
+
* Returns a LoopHandle that lets the caller stop the loop and query its state.
|
|
10
|
+
*/
|
|
11
|
+
import { EpicStateMachine, InvalidTransitionError, } from "./state-machine.js";
|
|
12
|
+
import { setUserOption } from "./tmux.js";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// startLoop
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Start the autonomous ticket loop for one epic.
|
|
18
|
+
*
|
|
19
|
+
* Precondition: the agent TUI is running and the initial prompt has already
|
|
20
|
+
* been sent by setupEpic(). The state machine starts in SETUP and this
|
|
21
|
+
* function immediately advances it to RUNNING.
|
|
22
|
+
*
|
|
23
|
+
* @returns A LoopHandle for stopping the loop and querying state.
|
|
24
|
+
*/
|
|
25
|
+
export function startLoop(options) {
|
|
26
|
+
const { epic, logger, initialState, initialPauseReason } = options;
|
|
27
|
+
const machine = new EpicStateMachine({
|
|
28
|
+
epic,
|
|
29
|
+
onTransition: handleTransition,
|
|
30
|
+
initialState,
|
|
31
|
+
initialPauseReason,
|
|
32
|
+
});
|
|
33
|
+
let stopped = false;
|
|
34
|
+
// --------------------------------------------------------------------------
|
|
35
|
+
// Transition handler — fires synchronously inside the mutex for each state change
|
|
36
|
+
// --------------------------------------------------------------------------
|
|
37
|
+
function handleTransition(transition) {
|
|
38
|
+
const { from, to, reason, pauseReason: transitionPauseReason, timestamp } = transition;
|
|
39
|
+
// Log every transition
|
|
40
|
+
logger.info(`[${epic}] ${from} -> ${to} (${reason})`, {
|
|
41
|
+
epic,
|
|
42
|
+
from,
|
|
43
|
+
to,
|
|
44
|
+
reason,
|
|
45
|
+
pauseReason: transitionPauseReason,
|
|
46
|
+
timestamp,
|
|
47
|
+
});
|
|
48
|
+
// Fire the optional status bar updater (fire-and-forget)
|
|
49
|
+
if (options.onStatusBarUpdate) {
|
|
50
|
+
const result = options.onStatusBarUpdate(epic, transition);
|
|
51
|
+
if (result && typeof result.catch === "function") {
|
|
52
|
+
result.catch(_swallow);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// --------------------------------------------------------------------------
|
|
57
|
+
// Stop helper
|
|
58
|
+
// --------------------------------------------------------------------------
|
|
59
|
+
function doStop() {
|
|
60
|
+
if (stopped)
|
|
61
|
+
return;
|
|
62
|
+
stopped = true;
|
|
63
|
+
// Transition to DONE (fire-and-forget)
|
|
64
|
+
machine.transition("DONE", "MANUAL_STOP").catch((err) => {
|
|
65
|
+
// Swallow InvalidTransitionError (may already be DONE)
|
|
66
|
+
if (!(err instanceof InvalidTransitionError)) {
|
|
67
|
+
logger.error(`[${epic}] failed to transition to DONE on stop`, { err: String(err) });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// --------------------------------------------------------------------------
|
|
72
|
+
// Start: advance SETUP → RUNNING
|
|
73
|
+
// --------------------------------------------------------------------------
|
|
74
|
+
// Immediately transition to RUNNING (setup already complete from setupEpic)
|
|
75
|
+
machine.transition("RUNNING", "TUI_READY").catch((err) => {
|
|
76
|
+
logger.error(`[${epic}] failed to transition to RUNNING`, {
|
|
77
|
+
epic,
|
|
78
|
+
err: String(err),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// --------------------------------------------------------------------------
|
|
82
|
+
// LoopHandle
|
|
83
|
+
// --------------------------------------------------------------------------
|
|
84
|
+
return {
|
|
85
|
+
get state() {
|
|
86
|
+
return machine.state;
|
|
87
|
+
},
|
|
88
|
+
get pauseReason() {
|
|
89
|
+
return machine.pauseReason;
|
|
90
|
+
},
|
|
91
|
+
machine,
|
|
92
|
+
stop() {
|
|
93
|
+
doStop();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Map a raw pauseReason string (or null) to a short category keyword.
|
|
99
|
+
*/
|
|
100
|
+
export function pauseReasonToCategory(reason) {
|
|
101
|
+
if (reason === null)
|
|
102
|
+
return "unknown";
|
|
103
|
+
if (reason.includes("idle") || reason === "UNRECOGNIZED_IDLE")
|
|
104
|
+
return "idle";
|
|
105
|
+
if (reason.startsWith("TIMEOUT:"))
|
|
106
|
+
return "timeout";
|
|
107
|
+
if (reason.includes("permission"))
|
|
108
|
+
return "permission";
|
|
109
|
+
if (reason.includes("crashed"))
|
|
110
|
+
return "crashed";
|
|
111
|
+
if (reason === "MANUAL_PAUSE" || reason.includes("manual"))
|
|
112
|
+
return "manual";
|
|
113
|
+
if (reason === "DAEMON_SHUTDOWN")
|
|
114
|
+
return "shutdown";
|
|
115
|
+
if (reason.includes("NEEDS_HUMAN_INPUT"))
|
|
116
|
+
return "input";
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build a tmux status bar string from a map of epic → StatusBarEpic.
|
|
121
|
+
*
|
|
122
|
+
* Format:
|
|
123
|
+
* RUNNING/CLEARING: "[auth: running 5/12]" or "[auth: running]" (no progress)
|
|
124
|
+
* PAUSED: "[pay: PAUSED! timeout]"
|
|
125
|
+
* DONE: "[ui: done 8/8]"
|
|
126
|
+
* SETUP: "[auth: setup]"
|
|
127
|
+
*/
|
|
128
|
+
export function formatStatusBar(epics) {
|
|
129
|
+
if (epics.size === 0)
|
|
130
|
+
return "";
|
|
131
|
+
return Array.from(epics.entries())
|
|
132
|
+
.map(([epic, { state, progress, pauseReason }]) => {
|
|
133
|
+
if (state === "PAUSED") {
|
|
134
|
+
const category = pauseReasonToCategory(pauseReason);
|
|
135
|
+
return `[${epic}: PAUSED! ${category}]`;
|
|
136
|
+
}
|
|
137
|
+
const label = state.toLowerCase();
|
|
138
|
+
if (state === "SETUP") {
|
|
139
|
+
return `[${epic}: ${label}]`;
|
|
140
|
+
}
|
|
141
|
+
// RUNNING, CLEARING, DONE — show progress if available
|
|
142
|
+
if (progress !== null) {
|
|
143
|
+
return `[${epic}: ${label} ${progress.done}/${progress.total}]`;
|
|
144
|
+
}
|
|
145
|
+
return `[${epic}: ${label}]`;
|
|
146
|
+
})
|
|
147
|
+
.join(" ");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Update the tmux status bar with the current epic states.
|
|
151
|
+
* Fire-and-forget — errors are swallowed.
|
|
152
|
+
*/
|
|
153
|
+
export async function updateStatusBar(tmuxSession, epics) {
|
|
154
|
+
const value = formatStatusBar(epics);
|
|
155
|
+
await setUserOption(tmuxSession, "pit-status", value).catch(_swallow);
|
|
156
|
+
}
|
|
157
|
+
/** Silently swallow a rejected promise — used to suppress fire-and-forget errors. */
|
|
158
|
+
function _swallow(_err) {
|
|
159
|
+
// intentionally empty
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=loop.js.map
|
package/dist/loop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator instructions template.
|
|
3
|
+
*
|
|
4
|
+
* Injected into a target project's AGENTS.md by `pit init` so the
|
|
5
|
+
* orchestrating LLM knows how to drive pit.
|
|
6
|
+
*
|
|
7
|
+
* Delimited by sentinel comments so `pit init` can find and replace
|
|
8
|
+
* the section idempotently.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ORCHESTRATOR_SECTION_START = "<!-- pit:orchestrator:start -->";
|
|
11
|
+
export declare const ORCHESTRATOR_SECTION_END = "<!-- pit:orchestrator:end -->";
|
|
12
|
+
export declare const ORCHESTRATOR_INSTRUCTIONS = "<!-- pit:orchestrator:start -->\n## Parallel Agent Orchestration with pit\n\nYou have access to `pit`, a tmux-based CLI that manages parallel AI coding agent sessions.\nEach agent works in its own git worktree and tmux window, autonomously looping through\ntickets in a beads epic.\n\n### Prerequisites\n\nBefore using pit, ensure the project has epics and tickets set up with beads (`bd`).\n\n### Workflow\n\n1. **Start agents**: `pit start --epics <epic-id>` (comma-separated for multiple)\n - Creates one git worktree + tmux window + agent TUI per epic\n - Each agent begins working through tickets autonomously\n2. **Monitor**: `pit status` \u2014 returns JSON with state of all epics\n3. **Read agent output**: `pit log <epic>` \u2014 captures the agent's terminal output\n4. **Pause an agent**: `pit pause <epic>` \u2014 pauses the agent's autonomous loop\n5. **Resume with guidance**: `pit resume <epic> --message \"your instructions\"`\n \u2014 resumes a paused agent, injecting your message into its context\n\n### CLI Reference\n\n| Command | Description |\n|---|---|\n| `pit start --epics <ids>` | Start agent sessions for given epic IDs |\n| `pit status` | Show status of all running epics (JSON) |\n| `pit log <epic>` | Capture agent's recent terminal output |\n| `pit log <epic> --lines 100` | Capture more lines of output |\n| `pit pause <epic>` | Pause a running epic's autonomous loop |\n| `pit resume <epic> --message \"...\"` | Resume a paused epic with guidance |\n| `pit teardown [<epic-id>]` | Tear down all epics (or a single epic), removes worktrees by default |\n| `pit teardown --keep-worktrees` | Tear down without removing git worktrees |\n| `pit teardown --force` | Tear down all epics even if some are still running |\n\n### Start Options\n\n| Flag | Default | Description |\n|---|---|---|\n| `--epics <ids>` | (required) | Comma-separated beads epic IDs |\n| `--agent <type>` | `auto` | Agent type: `auto`, `Claude`, or `claude-code` |\n| `--base-branch <branch>` | `main` | Base branch for worktrees |\n| `--tmux-session <name>` | `pit` | tmux session name |\n| `--worktree-dir <path>` | `.worktrees` | Directory for git worktrees |\n| `--prompt-template <path>` | (built-in) | Path to a custom prompt template file |\n| `--instructions-template <path>` | (built-in) | Path to a custom agent instructions template file |\n| `--ticket-timeout <minutes>` | (none) | Auto-pause agents when a ticket exceeds this duration |\n| `--model <value>` | (none) | Model to pass to the agent CLI (passed through as `--model <value>`) |\n| `--epic-model <mapping>` | (none) | Per-epic model override, repeatable: `--epic-model <epic>=<model>` |\n\n> **Model resolution order:** `--epic-model <epic>=<model>` overrides `--model` / `.pit.json` model, which overrides the agent default. The model string is passed through to the agent CLI as-is.\n\n### Custom Templates\n\nUse `--prompt-template` and `--instructions-template` to override the built-in agent\nprompt and instructions with your own files. Templates support variable substitution:\n\n- `{{EPIC_ID}}` \u2014 replaced with the epic ID being worked on\n- `{{EPIC_NAME}}` \u2014 replaced with the epic name\n\n### Configuration\n\npit reads `.pit.json` from the project root. CLI flags override file values; missing\nfields fall back to defaults.\n\n```json\n{\n \"agent\": \"auto\",\n \"model\": null,\n \"worktreeDir\": \".worktrees\",\n \"baseBranch\": \"main\",\n \"tmuxSession\": \"pit\",\n \"clearDelay\": 2000,\n \"initDelay\": 5000,\n \"ticketTimeout\": null\n}\n```\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `agent` | string | `\"auto\"` | Agent type: `auto`, `opencode`, or `claude-code` |\n| `model` | string|null | `null` | Model to pass to the agent CLI; null = agent default |\n| `worktreeDir` | string | `\".worktrees\"` | Directory for git worktrees |\n| `baseBranch` | string | `\"main\"` | Base branch for worktrees |\n| `tmuxSession` | string | `\"pit\"` | tmux session name |\n| `clearDelay` | number | `2000` | Ms to wait before clearing agent context after ticket completion |\n| `initDelay` | number | `5000` | Ms to wait before starting the agent after setup |\n| `ticketTimeout` | number|null | `null` | Minutes before a ticket auto-pauses the agent (null = disabled) |\n\n### Output Format\n\nAll pit commands output JSON by default (for LLM consumption). Add `--pretty` for\nhuman-readable output. Parse the JSON to understand the current state:\n\n```json\n// pit status example\n{\n \"epics\": {\n \"auth\": { \"state\": \"running\", \"progress\": \"3/7\" },\n \"payments\": { \"state\": \"paused\", \"progress\": \"1/4\" }\n }\n}\n```\n\n### When to Intervene\n\n- **`pit status` shows an epic as \"paused\"**: An agent hit `NEEDS_HUMAN_INPUT`.\n Read `pit log <epic>` to understand what the agent needs, then\n `pit resume <epic> --message \"your answer\"`.\n- **Pause reason starts with `TIMEOUT:`**: The agent was auto-paused because the\n ticket exceeded the configured `ticketTimeout`. Read `pit log <epic>` to assess\n progress, then `pit resume <epic> --message \"your guidance\"` or let it continue\n with `pit resume <epic>`.\n- **An agent seems stuck**: Use `pit log <epic>` to check progress.\n `pit pause <epic>` if needed, then resume with instructions.\n- **Epic completes**: The epic state becomes `\"done\"`. Merge the worktree branch\n back to the base branch.\n\n### Session Reliability\n\npit automatically manages long-running sessions reliably. Once started, agent sessions\ncontinue running independently until completion, regardless of how long they take.\nNo special flags or configuration needed.\n\n### Important Notes\n\n- Agents work independently \u2014 they do not communicate with each other.\n- Each agent works in its own git worktree, so there are no file conflicts during work.\n- Merging worktree branches back is a manual step (done by you after epic completion).\n- The tmux session is named `pit` by default. You can attach to it to observe agents directly.\n- `pit teardown` removes worktrees by default. Use `--keep-worktrees` if you need to inspect the worktree after teardown.\n- `pit teardown` (no epic specified) will fail if any epic is in `running` state, to prevent accidental destruction of active sessions. Use `pit teardown --force` to bypass this guard when you deliberately want to tear everything down.\n<!-- pit:orchestrator:end -->\n";
|
|
13
|
+
//# sourceMappingURL=orchestrator-instructions-template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-instructions-template.d.ts","sourceRoot":"","sources":["../src/orchestrator-instructions-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,0BAA0B,oCAAoC,CAAC;AAC5E,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,eAAO,MAAM,yBAAyB,ozMAsIrC,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator instructions template.
|
|
3
|
+
*
|
|
4
|
+
* Injected into a target project's AGENTS.md by `pit init` so the
|
|
5
|
+
* orchestrating LLM knows how to drive pit.
|
|
6
|
+
*
|
|
7
|
+
* Delimited by sentinel comments so `pit init` can find and replace
|
|
8
|
+
* the section idempotently.
|
|
9
|
+
*/
|
|
10
|
+
export const ORCHESTRATOR_SECTION_START = "<!-- pit:orchestrator:start -->";
|
|
11
|
+
export const ORCHESTRATOR_SECTION_END = "<!-- pit:orchestrator:end -->";
|
|
12
|
+
export const ORCHESTRATOR_INSTRUCTIONS = `${ORCHESTRATOR_SECTION_START}
|
|
13
|
+
## Parallel Agent Orchestration with pit
|
|
14
|
+
|
|
15
|
+
You have access to \`pit\`, a tmux-based CLI that manages parallel AI coding agent sessions.
|
|
16
|
+
Each agent works in its own git worktree and tmux window, autonomously looping through
|
|
17
|
+
tickets in a beads epic.
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
Before using pit, ensure the project has epics and tickets set up with beads (\`bd\`).
|
|
22
|
+
|
|
23
|
+
### Workflow
|
|
24
|
+
|
|
25
|
+
1. **Start agents**: \`pit start --epics <epic-id>\` (comma-separated for multiple)
|
|
26
|
+
- Creates one git worktree + tmux window + agent TUI per epic
|
|
27
|
+
- Each agent begins working through tickets autonomously
|
|
28
|
+
2. **Monitor**: \`pit status\` — returns JSON with state of all epics
|
|
29
|
+
3. **Read agent output**: \`pit log <epic>\` — captures the agent's terminal output
|
|
30
|
+
4. **Pause an agent**: \`pit pause <epic>\` — pauses the agent's autonomous loop
|
|
31
|
+
5. **Resume with guidance**: \`pit resume <epic> --message "your instructions"\`
|
|
32
|
+
— resumes a paused agent, injecting your message into its context
|
|
33
|
+
|
|
34
|
+
### CLI Reference
|
|
35
|
+
|
|
36
|
+
| Command | Description |
|
|
37
|
+
|---|---|
|
|
38
|
+
| \`pit start --epics <ids>\` | Start agent sessions for given epic IDs |
|
|
39
|
+
| \`pit status\` | Show status of all running epics (JSON) |
|
|
40
|
+
| \`pit log <epic>\` | Capture agent's recent terminal output |
|
|
41
|
+
| \`pit log <epic> --lines 100\` | Capture more lines of output |
|
|
42
|
+
| \`pit pause <epic>\` | Pause a running epic's autonomous loop |
|
|
43
|
+
| \`pit resume <epic> --message "..."\` | Resume a paused epic with guidance |
|
|
44
|
+
| \`pit teardown [<epic-id>]\` | Tear down all epics (or a single epic), removes worktrees by default |
|
|
45
|
+
| \`pit teardown --keep-worktrees\` | Tear down without removing git worktrees |
|
|
46
|
+
| \`pit teardown --force\` | Tear down all epics even if some are still running |
|
|
47
|
+
|
|
48
|
+
### Start Options
|
|
49
|
+
|
|
50
|
+
| Flag | Default | Description |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| \`--epics <ids>\` | (required) | Comma-separated beads epic IDs |
|
|
53
|
+
| \`--agent <type>\` | \`auto\` | Agent type: \`auto\`, \`Claude\`, or \`claude-code\` |
|
|
54
|
+
| \`--base-branch <branch>\` | \`main\` | Base branch for worktrees |
|
|
55
|
+
| \`--tmux-session <name>\` | \`pit\` | tmux session name |
|
|
56
|
+
| \`--worktree-dir <path>\` | \`.worktrees\` | Directory for git worktrees |
|
|
57
|
+
| \`--prompt-template <path>\` | (built-in) | Path to a custom prompt template file |
|
|
58
|
+
| \`--instructions-template <path>\` | (built-in) | Path to a custom agent instructions template file |
|
|
59
|
+
| \`--ticket-timeout <minutes>\` | (none) | Auto-pause agents when a ticket exceeds this duration |
|
|
60
|
+
| \`--model <value>\` | (none) | Model to pass to the agent CLI (passed through as \`--model <value>\`) |
|
|
61
|
+
| \`--epic-model <mapping>\` | (none) | Per-epic model override, repeatable: \`--epic-model <epic>=<model>\` |
|
|
62
|
+
|
|
63
|
+
> **Model resolution order:** \`--epic-model <epic>=<model>\` overrides \`--model\` / \`.pit.json\` model, which overrides the agent default. The model string is passed through to the agent CLI as-is.
|
|
64
|
+
|
|
65
|
+
### Custom Templates
|
|
66
|
+
|
|
67
|
+
Use \`--prompt-template\` and \`--instructions-template\` to override the built-in agent
|
|
68
|
+
prompt and instructions with your own files. Templates support variable substitution:
|
|
69
|
+
|
|
70
|
+
- \`{{EPIC_ID}}\` — replaced with the epic ID being worked on
|
|
71
|
+
- \`{{EPIC_NAME}}\` — replaced with the epic name
|
|
72
|
+
|
|
73
|
+
### Configuration
|
|
74
|
+
|
|
75
|
+
pit reads \`.pit.json\` from the project root. CLI flags override file values; missing
|
|
76
|
+
fields fall back to defaults.
|
|
77
|
+
|
|
78
|
+
\`\`\`json
|
|
79
|
+
{
|
|
80
|
+
"agent": "auto",
|
|
81
|
+
"model": null,
|
|
82
|
+
"worktreeDir": ".worktrees",
|
|
83
|
+
"baseBranch": "main",
|
|
84
|
+
"tmuxSession": "pit",
|
|
85
|
+
"clearDelay": 2000,
|
|
86
|
+
"initDelay": 5000,
|
|
87
|
+
"ticketTimeout": null
|
|
88
|
+
}
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
| Field | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| \`agent\` | string | \`"auto"\` | Agent type: \`auto\`, \`opencode\`, or \`claude-code\` |
|
|
94
|
+
| \`model\` | string|null | \`null\` | Model to pass to the agent CLI; null = agent default |
|
|
95
|
+
| \`worktreeDir\` | string | \`".worktrees"\` | Directory for git worktrees |
|
|
96
|
+
| \`baseBranch\` | string | \`"main"\` | Base branch for worktrees |
|
|
97
|
+
| \`tmuxSession\` | string | \`"pit"\` | tmux session name |
|
|
98
|
+
| \`clearDelay\` | number | \`2000\` | Ms to wait before clearing agent context after ticket completion |
|
|
99
|
+
| \`initDelay\` | number | \`5000\` | Ms to wait before starting the agent after setup |
|
|
100
|
+
| \`ticketTimeout\` | number|null | \`null\` | Minutes before a ticket auto-pauses the agent (null = disabled) |
|
|
101
|
+
|
|
102
|
+
### Output Format
|
|
103
|
+
|
|
104
|
+
All pit commands output JSON by default (for LLM consumption). Add \`--pretty\` for
|
|
105
|
+
human-readable output. Parse the JSON to understand the current state:
|
|
106
|
+
|
|
107
|
+
\`\`\`json
|
|
108
|
+
// pit status example
|
|
109
|
+
{
|
|
110
|
+
"epics": {
|
|
111
|
+
"auth": { "state": "running", "progress": "3/7" },
|
|
112
|
+
"payments": { "state": "paused", "progress": "1/4" }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
### When to Intervene
|
|
118
|
+
|
|
119
|
+
- **\`pit status\` shows an epic as "paused"**: An agent hit \`NEEDS_HUMAN_INPUT\`.
|
|
120
|
+
Read \`pit log <epic>\` to understand what the agent needs, then
|
|
121
|
+
\`pit resume <epic> --message "your answer"\`.
|
|
122
|
+
- **Pause reason starts with \`TIMEOUT:\`**: The agent was auto-paused because the
|
|
123
|
+
ticket exceeded the configured \`ticketTimeout\`. Read \`pit log <epic>\` to assess
|
|
124
|
+
progress, then \`pit resume <epic> --message "your guidance"\` or let it continue
|
|
125
|
+
with \`pit resume <epic>\`.
|
|
126
|
+
- **An agent seems stuck**: Use \`pit log <epic>\` to check progress.
|
|
127
|
+
\`pit pause <epic>\` if needed, then resume with instructions.
|
|
128
|
+
- **Epic completes**: The epic state becomes \`"done"\`. Merge the worktree branch
|
|
129
|
+
back to the base branch.
|
|
130
|
+
|
|
131
|
+
### Session Reliability
|
|
132
|
+
|
|
133
|
+
pit automatically manages long-running sessions reliably. Once started, agent sessions
|
|
134
|
+
continue running independently until completion, regardless of how long they take.
|
|
135
|
+
No special flags or configuration needed.
|
|
136
|
+
|
|
137
|
+
### Important Notes
|
|
138
|
+
|
|
139
|
+
- Agents work independently — they do not communicate with each other.
|
|
140
|
+
- Each agent works in its own git worktree, so there are no file conflicts during work.
|
|
141
|
+
- Merging worktree branches back is a manual step (done by you after epic completion).
|
|
142
|
+
- The tmux session is named \`pit\` by default. You can attach to it to observe agents directly.
|
|
143
|
+
- \`pit teardown\` removes worktrees by default. Use \`--keep-worktrees\` if you need to inspect the worktree after teardown.
|
|
144
|
+
- \`pit teardown\` (no epic specified) will fail if any epic is in \`running\` state, to prevent accidental destruction of active sessions. Use \`pit teardown --force\` to bypass this guard when you deliberately want to tear everything down.
|
|
145
|
+
${ORCHESTRATOR_SECTION_END}
|
|
146
|
+
`;
|
|
147
|
+
//# sourceMappingURL=orchestrator-instructions-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-instructions-template.js","sourceRoot":"","sources":["../src/orchestrator-instructions-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAC5E,MAAM,CAAC,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAExE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqIpE,wBAAwB;CACzB,CAAC"}
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for pit CLI.
|
|
3
|
+
* JSON output is the default (for LLM consumption).
|
|
4
|
+
* --pretty enables human-readable output.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatOutput(data: unknown, pretty: boolean): string;
|
|
7
|
+
export declare function printOutput(data: unknown, pretty: boolean): void;
|
|
8
|
+
export declare function printError(error: {
|
|
9
|
+
error: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}, pretty: boolean): void;
|
|
12
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAMnE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAChD,MAAM,EAAE,OAAO,GACd,IAAI,CAMN"}
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for pit CLI.
|
|
3
|
+
* JSON output is the default (for LLM consumption).
|
|
4
|
+
* --pretty enables human-readable output.
|
|
5
|
+
*/
|
|
6
|
+
export function formatOutput(data, pretty) {
|
|
7
|
+
if (pretty) {
|
|
8
|
+
if (typeof data === "string")
|
|
9
|
+
return data;
|
|
10
|
+
return JSON.stringify(data, null, 2);
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(data);
|
|
13
|
+
}
|
|
14
|
+
export function printOutput(data, pretty) {
|
|
15
|
+
process.stdout.write(formatOutput(data, pretty) + "\n");
|
|
16
|
+
}
|
|
17
|
+
export function printError(error, pretty) {
|
|
18
|
+
if (pretty) {
|
|
19
|
+
process.stderr.write(`Error: ${error.error}\n`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
process.stderr.write(JSON.stringify(error) + "\n");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,MAAe;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAa,EAAE,MAAe;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAgD,EAChD,MAAe;IAEf,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/plugin/pit.ts
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
async function sendToDaemon(socketPath, method, params) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const socket = net.createConnection(socketPath);
|
|
7
|
+
const request = {
|
|
8
|
+
id: randomUUID(),
|
|
9
|
+
method,
|
|
10
|
+
params
|
|
11
|
+
};
|
|
12
|
+
socket.on("connect", () => {
|
|
13
|
+
socket.write(JSON.stringify(request) + "\n", () => {
|
|
14
|
+
socket.destroy();
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
socket.on("error", (err) => {
|
|
19
|
+
console.error(`[pit plugin] Socket error (${method}):`, err.message);
|
|
20
|
+
socket.destroy();
|
|
21
|
+
resolve();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
var PitPlugin = async () => {
|
|
26
|
+
const epic = process.env.PIT_EPIC;
|
|
27
|
+
const socketPath = process.env.PIT_SOCKET_PATH;
|
|
28
|
+
if (!epic || !socketPath) {
|
|
29
|
+
console.error("[pit plugin] PIT_EPIC and PIT_SOCKET_PATH env vars required. Plugin inactive.");
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
event: async ({ event }) => {
|
|
34
|
+
try {
|
|
35
|
+
if (event.type === "session.idle") {
|
|
36
|
+
await sendToDaemon(socketPath, "agent-idle", { epicId: epic });
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error("[pit plugin] Error handling session.idle:", err);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"permission.ask": async ({ input }) => {
|
|
43
|
+
try {
|
|
44
|
+
await sendToDaemon(socketPath, "agent-permission", {
|
|
45
|
+
epicId: epic,
|
|
46
|
+
tool: input.type ?? "unknown",
|
|
47
|
+
input: JSON.stringify(input)
|
|
48
|
+
});
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error("[pit plugin] Error handling permission.ask:", err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
PitPlugin
|
|
57
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface LockData {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
pid: number;
|
|
4
|
+
tmuxSession: string;
|
|
5
|
+
epics: string[];
|
|
6
|
+
createdAt: string;
|
|
7
|
+
createdSession: boolean;
|
|
8
|
+
adapter?: string;
|
|
9
|
+
}
|
|
10
|
+
export type LockStatus = {
|
|
11
|
+
status: "none";
|
|
12
|
+
} | {
|
|
13
|
+
status: "alive";
|
|
14
|
+
data: LockData;
|
|
15
|
+
} | {
|
|
16
|
+
status: "stale";
|
|
17
|
+
data: LockData;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Generates a deterministic 12-character hex session ID from the resolved
|
|
21
|
+
* project path and tmux session name.
|
|
22
|
+
*
|
|
23
|
+
* Normalization:
|
|
24
|
+
* 1. Resolve symlinks via `fs.realpathSync` (falls back to `path.resolve`)
|
|
25
|
+
* 2. Strip any trailing slash
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateSessionId(projectPath: string, tmuxSession: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the session-level directory: `/tmp/pit/<sessionId>`
|
|
30
|
+
*/
|
|
31
|
+
export declare function sessionDir(sessionId: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Atomically writes `data` as JSON to `${projectRoot}/.pit.lock`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeLock(projectRoot: string, data: LockData): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Reads and parses `.pit.lock` from `projectRoot`.
|
|
38
|
+
* Returns null if the file is missing or malformed.
|
|
39
|
+
* Provides backwards compatibility by defaulting createdSession to false for old locks.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readLock(projectRoot: string): Promise<LockData | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Removes `.pit.lock` from `projectRoot`.
|
|
44
|
+
* Ignores ENOENT (idempotent).
|
|
45
|
+
*/
|
|
46
|
+
export declare function removeLock(projectRoot: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Checks the lock status for `projectRoot`.
|
|
49
|
+
*
|
|
50
|
+
* - "none" — no lock file present
|
|
51
|
+
* - "alive" — lock file exists and the PID is still running
|
|
52
|
+
* - "stale" — lock file exists but the PID is dead (ESRCH)
|
|
53
|
+
*/
|
|
54
|
+
export declare function checkLock(projectRoot: string): Promise<LockStatus>;
|
|
55
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAMxC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAclF;AAMD;;GAEG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAQD;;GAEG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAoC5E;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnE;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAexE"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as fsp from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { atomicWrite } from "./signals.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Session ID
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/**
|
|
10
|
+
* Generates a deterministic 12-character hex session ID from the resolved
|
|
11
|
+
* project path and tmux session name.
|
|
12
|
+
*
|
|
13
|
+
* Normalization:
|
|
14
|
+
* 1. Resolve symlinks via `fs.realpathSync` (falls back to `path.resolve`)
|
|
15
|
+
* 2. Strip any trailing slash
|
|
16
|
+
*/
|
|
17
|
+
export function generateSessionId(projectPath, tmuxSession) {
|
|
18
|
+
let resolved;
|
|
19
|
+
try {
|
|
20
|
+
resolved = fs.realpathSync(path.resolve(projectPath));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
resolved = path.resolve(projectPath);
|
|
24
|
+
}
|
|
25
|
+
// Strip trailing slash
|
|
26
|
+
resolved = resolved.replace(/\/$/, "");
|
|
27
|
+
return crypto
|
|
28
|
+
.createHash("sha256")
|
|
29
|
+
.update(resolved + "|" + tmuxSession)
|
|
30
|
+
.digest("hex")
|
|
31
|
+
.slice(0, 12);
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Path helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Returns the session-level directory: `/tmp/pit/<sessionId>`
|
|
38
|
+
*/
|
|
39
|
+
export function sessionDir(sessionId) {
|
|
40
|
+
return `/tmp/pit/${sessionId}`;
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Lock file CRUD
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
const LOCK_FILE = ".pit.lock";
|
|
46
|
+
/**
|
|
47
|
+
* Atomically writes `data` as JSON to `${projectRoot}/.pit.lock`.
|
|
48
|
+
*/
|
|
49
|
+
export async function writeLock(projectRoot, data) {
|
|
50
|
+
const filePath = path.join(projectRoot, LOCK_FILE);
|
|
51
|
+
await atomicWrite(filePath, JSON.stringify(data));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reads and parses `.pit.lock` from `projectRoot`.
|
|
55
|
+
* Returns null if the file is missing or malformed.
|
|
56
|
+
* Provides backwards compatibility by defaulting createdSession to false for old locks.
|
|
57
|
+
*/
|
|
58
|
+
export async function readLock(projectRoot) {
|
|
59
|
+
const filePath = path.join(projectRoot, LOCK_FILE);
|
|
60
|
+
let raw;
|
|
61
|
+
try {
|
|
62
|
+
raw = await fsp.readFile(filePath, "utf8");
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === "ENOENT") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
// Validate required fields
|
|
73
|
+
if (typeof parsed.sessionId !== "string" ||
|
|
74
|
+
typeof parsed.pid !== "number" ||
|
|
75
|
+
typeof parsed.tmuxSession !== "string" ||
|
|
76
|
+
!Array.isArray(parsed.epics) ||
|
|
77
|
+
typeof parsed.createdAt !== "string") {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Backwards compatibility: default createdSession to false if missing
|
|
81
|
+
return {
|
|
82
|
+
sessionId: parsed.sessionId,
|
|
83
|
+
pid: parsed.pid,
|
|
84
|
+
tmuxSession: parsed.tmuxSession,
|
|
85
|
+
epics: parsed.epics,
|
|
86
|
+
createdAt: parsed.createdAt,
|
|
87
|
+
createdSession: parsed.createdSession ?? false,
|
|
88
|
+
...(parsed.adapter !== undefined ? { adapter: parsed.adapter } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Removes `.pit.lock` from `projectRoot`.
|
|
97
|
+
* Ignores ENOENT (idempotent).
|
|
98
|
+
*/
|
|
99
|
+
export async function removeLock(projectRoot) {
|
|
100
|
+
const filePath = path.join(projectRoot, LOCK_FILE);
|
|
101
|
+
try {
|
|
102
|
+
await fsp.unlink(filePath);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err.code === "ENOENT") {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Checks the lock status for `projectRoot`.
|
|
113
|
+
*
|
|
114
|
+
* - "none" — no lock file present
|
|
115
|
+
* - "alive" — lock file exists and the PID is still running
|
|
116
|
+
* - "stale" — lock file exists but the PID is dead (ESRCH)
|
|
117
|
+
*/
|
|
118
|
+
export async function checkLock(projectRoot) {
|
|
119
|
+
const data = await readLock(projectRoot);
|
|
120
|
+
if (data === null) {
|
|
121
|
+
return { status: "none" };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
process.kill(data.pid, 0);
|
|
125
|
+
return { status: "alive", data };
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err.code === "ESRCH") {
|
|
129
|
+
return { status: "stale", data };
|
|
130
|
+
}
|
|
131
|
+
// EPERM means process exists but we lack permission to signal it — treat as alive
|
|
132
|
+
return { status: "alive", data };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|