aicodeman 1.12.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -15
- package/dist/ai-checker-base.d.ts +2 -0
- package/dist/ai-checker-base.d.ts.map +1 -1
- package/dist/ai-checker-base.js +31 -3
- package/dist/ai-checker-base.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +142 -14
- package/dist/cli.js.map +1 -1
- package/dist/config/agent-wait.d.ts +73 -0
- package/dist/config/agent-wait.d.ts.map +1 -0
- package/dist/config/agent-wait.js +99 -0
- package/dist/config/agent-wait.js.map +1 -0
- package/dist/config/service-names.d.ts +23 -0
- package/dist/config/service-names.d.ts.map +1 -0
- package/dist/config/service-names.js +29 -0
- package/dist/config/service-names.js.map +1 -0
- package/dist/daemon-control.d.ts +126 -0
- package/dist/daemon-control.d.ts.map +1 -0
- package/dist/daemon-control.js +433 -0
- package/dist/daemon-control.js.map +1 -0
- package/dist/hooks-config.d.ts +28 -6
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +214 -28
- package/dist/hooks-config.js.map +1 -1
- package/dist/service-installer.d.ts +96 -0
- package/dist/service-installer.d.ts.map +1 -0
- package/dist/service-installer.js +333 -0
- package/dist/service-installer.js.map +1 -0
- package/dist/session-cli-builder.d.ts.map +1 -1
- package/dist/session-cli-builder.js +6 -2
- package/dist/session-cli-builder.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +5 -1
- package/dist/tmux-manager.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.2e9ffd31.js → app.b24f795e.js} +5 -7
- package/dist/web/public/app.b24f795e.js.br +0 -0
- package/dist/web/public/app.b24f795e.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/i18n.da34cfaf.js +1 -0
- package/dist/web/public/i18n.da34cfaf.js.br +0 -0
- package/dist/web/public/i18n.da34cfaf.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +13 -6
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.32cacde5.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
- package/dist/web/public/settings-ui.ea63d5db.js +67 -0
- package/dist/web/public/settings-ui.ea63d5db.js.br +0 -0
- package/dist/web/public/settings-ui.ea63d5db.js.gz +0 -0
- package/dist/web/public/styles.b10e5edd.css +1 -0
- package/dist/web/public/styles.b10e5edd.css.br +0 -0
- package/dist/web/public/styles.b10e5edd.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.78c71f3a.js → terminal-ui.d5ae6dcf.js} +2 -2
- package/dist/web/public/terminal-ui.d5ae6dcf.js.br +0 -0
- package/dist/web/public/terminal-ui.d5ae6dcf.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +1 -2
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
- package/dist/web/routes/hook-event-routes.js +22 -0
- package/dist/web/routes/hook-event-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +4 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +499 -17
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +53 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +59 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/self-update.d.ts.map +1 -1
- package/dist/web/self-update.js +4 -4
- package/dist/web/self-update.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +14 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +28 -0
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/dist/web/session-wait-registry.d.ts +407 -0
- package/dist/web/session-wait-registry.d.ts.map +1 -0
- package/dist/web/session-wait-registry.js +895 -0
- package/dist/web/session-wait-registry.js.map +1 -0
- package/package.json +2 -1
- package/skills/codeman/SKILL.md +274 -0
- package/skills/codeman/reference/endpoints.md +214 -0
- package/skills/codeman/reference/recipes.md +249 -0
- package/dist/web/public/app.2e9ffd31.js.br +0 -0
- package/dist/web/public/app.2e9ffd31.js.gz +0 -0
- package/dist/web/public/i18n.0fbea500.js +0 -1
- package/dist/web/public/i18n.0fbea500.js.br +0 -0
- package/dist/web/public/i18n.0fbea500.js.gz +0 -0
- package/dist/web/public/settings-ui.01a1ae7d.js +0 -67
- package/dist/web/public/settings-ui.01a1ae7d.js.br +0 -0
- package/dist/web/public/settings-ui.01a1ae7d.js.gz +0 -0
- package/dist/web/public/styles.50cd52bd.css +0 -1
- package/dist/web/public/styles.50cd52bd.css.br +0 -0
- package/dist/web/public/styles.50cd52bd.css.gz +0 -0
- package/dist/web/public/terminal-ui.78c71f3a.js.br +0 -0
- package/dist/web/public/terminal-ui.78c71f3a.js.gz +0 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Detached `codeman web` control: start (-d), stop, status.
|
|
3
|
+
*
|
|
4
|
+
* Backs `codeman web -d`, `codeman web --stop` and `codeman web --status`. The
|
|
5
|
+
* server itself is unchanged; this module re-launches the SAME entry script in a
|
|
6
|
+
* new session (`detached: true` calls setsid), so the child has no controlling
|
|
7
|
+
* terminal and no shell job entry. That is what actually makes it outlive the
|
|
8
|
+
* shell: `nohup` does not, because Node re-arms SIGHUP to its default disposition
|
|
9
|
+
* even when it inherits "ignore", and `cli.ts` installs a SIGHUP handler that
|
|
10
|
+
* shuts the server down gracefully (issue #231).
|
|
11
|
+
*
|
|
12
|
+
* Two rules shape the rest of the module:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Never start a second server on one data dir.** `~/.codeman` and the
|
|
15
|
+
* `tmux -L codeman` socket are process-wide (config/instance.ts), so a second
|
|
16
|
+
* instance discovers and attaches PTYs to the first one's live sessions and
|
|
17
|
+
* starts resizing them. A double `-d` therefore has to be a hard error, which
|
|
18
|
+
* means checking both the pidfile AND the port before spawning.
|
|
19
|
+
* 2. **Never report success we have not seen.** The parent polls `/api/status`
|
|
20
|
+
* until the child answers (or dies) before printing a URL. A port clash or a
|
|
21
|
+
* missing dependency otherwise looks exactly like a clean start.
|
|
22
|
+
*
|
|
23
|
+
* Pure helpers (arg building, URL building, pidfile parsing, the process-identity
|
|
24
|
+
* check) are exported separately so they can be unit-tested without spawning.
|
|
25
|
+
*
|
|
26
|
+
* @module daemon-control
|
|
27
|
+
*/
|
|
28
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
29
|
+
import { appendFileSync, closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
30
|
+
import http from 'node:http';
|
|
31
|
+
import https from 'node:https';
|
|
32
|
+
import { dataPath } from './config/instance.js';
|
|
33
|
+
import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
|
|
34
|
+
/** How long to wait for a freshly spawned server to answer `/api/status`. */
|
|
35
|
+
const START_TIMEOUT_MS = 30_000;
|
|
36
|
+
/** How long to wait for a SIGTERM'd server to actually exit before giving up. */
|
|
37
|
+
const STOP_TIMEOUT_MS = 15_000;
|
|
38
|
+
/** Poll interval while waiting for either of the above. */
|
|
39
|
+
const POLL_INTERVAL_MS = 250;
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// Pure helpers
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
/** Rebuild the `web` argv for the child, dropping the daemon flags themselves. */
|
|
44
|
+
export function buildWebArgs(options) {
|
|
45
|
+
const args = ['web', '--host', options.host, '--port', String(options.port)];
|
|
46
|
+
if (options.https)
|
|
47
|
+
args.push('--https');
|
|
48
|
+
if (options.titleHostname)
|
|
49
|
+
args.push('--title-hostname', options.titleHostname);
|
|
50
|
+
if (options.allowUnauthenticatedNetwork)
|
|
51
|
+
args.push('--allow-unauthenticated-network');
|
|
52
|
+
if (options.multiuser)
|
|
53
|
+
args.push('--multiuser');
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Connectable address for this bind. A wildcard bind is not itself connectable,
|
|
58
|
+
* so `0.0.0.0` / `::` become loopback; a bare IPv6 literal gets bracketed.
|
|
59
|
+
*/
|
|
60
|
+
export function buildBaseUrl(options) {
|
|
61
|
+
const protocol = options.https ? 'https' : 'http';
|
|
62
|
+
let host = options.host.trim();
|
|
63
|
+
if (host === '0.0.0.0' || host === '::' || host === '')
|
|
64
|
+
host = '127.0.0.1';
|
|
65
|
+
if (host.includes(':') && !host.startsWith('['))
|
|
66
|
+
host = `[${host}]`;
|
|
67
|
+
return `${protocol}://${host}:${options.port}`;
|
|
68
|
+
}
|
|
69
|
+
/** The endpoint polled for readiness. */
|
|
70
|
+
export function buildStatusUrl(options) {
|
|
71
|
+
return `${buildBaseUrl(options)}/api/status`;
|
|
72
|
+
}
|
|
73
|
+
/** Parse a pidfile body. Rejects garbage, and pid 1 (init is never ours). */
|
|
74
|
+
export function parsePidFileContents(text) {
|
|
75
|
+
const trimmed = text.trim();
|
|
76
|
+
if (!/^\d+$/.test(trimmed))
|
|
77
|
+
return null;
|
|
78
|
+
const pid = Number.parseInt(trimmed, 10);
|
|
79
|
+
if (!Number.isSafeInteger(pid) || pid <= 1)
|
|
80
|
+
return null;
|
|
81
|
+
return pid;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Does this command line look like a Codeman web server?
|
|
85
|
+
*
|
|
86
|
+
* Pids are recycled, and a stale pidfile pointing at whatever inherited the
|
|
87
|
+
* number is a live footgun: `codeman web --stop` must not SIGTERM an unrelated
|
|
88
|
+
* process. Both the npm bin (`codeman`/`aicodeman`) and the direct entry
|
|
89
|
+
* (`node dist/index.js web`, `tsx src/index.ts web`) have to match.
|
|
90
|
+
*/
|
|
91
|
+
export function looksLikeCodemanWeb(command) {
|
|
92
|
+
if (!command)
|
|
93
|
+
return false;
|
|
94
|
+
if (!/(^|\s)web(\s|$)/.test(command))
|
|
95
|
+
return false;
|
|
96
|
+
return /(^|[/\s])(ai)?codeman(\s|$)/.test(command) || /index\.(js|ts)(\s|$)/.test(command);
|
|
97
|
+
}
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// Paths
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Resolved at call time, not module load: tests swap `HOME` per file, and the
|
|
103
|
+
* data dir is derived from it (see test/setup.ts).
|
|
104
|
+
*/
|
|
105
|
+
export function pidFilePath() {
|
|
106
|
+
return dataPath('web.pid');
|
|
107
|
+
}
|
|
108
|
+
/** Where a detached server's stdout/stderr is appended. */
|
|
109
|
+
export function logFilePath() {
|
|
110
|
+
return dataPath('web.log');
|
|
111
|
+
}
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Process probing
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
/** Signal 0 liveness check. EPERM means the pid exists but is not ours. */
|
|
116
|
+
export function isProcessAlive(pid) {
|
|
117
|
+
try {
|
|
118
|
+
process.kill(pid, 0);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
return err.code === 'EPERM';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Full command line of a pid, or null. `-o command=` is portable to macOS. */
|
|
126
|
+
export function readProcessCommand(pid) {
|
|
127
|
+
try {
|
|
128
|
+
const out = execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
|
|
129
|
+
encoding: 'utf-8',
|
|
130
|
+
timeout: EXEC_TIMEOUT_MS,
|
|
131
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
132
|
+
});
|
|
133
|
+
return out.trim() || null;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Read the pidfile, returning null when it is missing, empty or malformed. */
|
|
140
|
+
export function readPidFile() {
|
|
141
|
+
const file = pidFilePath();
|
|
142
|
+
if (!existsSync(file))
|
|
143
|
+
return null;
|
|
144
|
+
try {
|
|
145
|
+
return parsePidFileContents(readFileSync(file, 'utf-8'));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function removePidFile() {
|
|
152
|
+
try {
|
|
153
|
+
unlinkSync(pidFilePath());
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* already gone */
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Pid of a live Codeman web server recorded in the pidfile, or null. */
|
|
160
|
+
export function readLivePid() {
|
|
161
|
+
const pid = readPidFile();
|
|
162
|
+
if (pid === null)
|
|
163
|
+
return null;
|
|
164
|
+
if (!isProcessAlive(pid))
|
|
165
|
+
return null;
|
|
166
|
+
// A recycled pid is not ours. `ps` can also legitimately fail (containers with
|
|
167
|
+
// no procps); treat "cannot tell" as ours rather than orphaning the pidfile.
|
|
168
|
+
const command = readProcessCommand(pid);
|
|
169
|
+
if (command !== null && !looksLikeCodemanWeb(command))
|
|
170
|
+
return null;
|
|
171
|
+
return pid;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Probe `/api/status`. Self-signed certs are accepted (`--https` generates one),
|
|
175
|
+
* and 401 counts as up because `CODEMAN_PASSWORD` gates that route. The body is
|
|
176
|
+
* checked so an unrelated service squatting on the port is not read as success.
|
|
177
|
+
*/
|
|
178
|
+
export function probeServer(url, timeoutMs = 2000) {
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
let settled = false;
|
|
181
|
+
const done = (result) => {
|
|
182
|
+
if (settled)
|
|
183
|
+
return;
|
|
184
|
+
settled = true;
|
|
185
|
+
resolve(result);
|
|
186
|
+
};
|
|
187
|
+
let target;
|
|
188
|
+
try {
|
|
189
|
+
target = new URL(url);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
done({ up: false });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const transport = target.protocol === 'https:' ? https : http;
|
|
196
|
+
const req = transport.request({
|
|
197
|
+
protocol: target.protocol,
|
|
198
|
+
hostname: target.hostname,
|
|
199
|
+
port: target.port,
|
|
200
|
+
path: target.pathname,
|
|
201
|
+
method: 'GET',
|
|
202
|
+
rejectUnauthorized: false,
|
|
203
|
+
timeout: timeoutMs,
|
|
204
|
+
headers: { Accept: 'application/json' },
|
|
205
|
+
}, (res) => {
|
|
206
|
+
if (res.statusCode === 401) {
|
|
207
|
+
res.resume();
|
|
208
|
+
done({ up: true });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let body = '';
|
|
212
|
+
res.setEncoding('utf-8');
|
|
213
|
+
res.on('data', (chunk) => {
|
|
214
|
+
if (body.length < 4096)
|
|
215
|
+
body += chunk;
|
|
216
|
+
});
|
|
217
|
+
res.on('end', () => {
|
|
218
|
+
if (!body.includes('"success"')) {
|
|
219
|
+
done({ up: false });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
let version;
|
|
223
|
+
try {
|
|
224
|
+
version = JSON.parse(body).data?.version;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
/* body was truncated at 4KB; up is still true */
|
|
228
|
+
}
|
|
229
|
+
done({ up: true, version });
|
|
230
|
+
});
|
|
231
|
+
res.on('error', () => done({ up: false }));
|
|
232
|
+
});
|
|
233
|
+
req.on('timeout', () => {
|
|
234
|
+
req.destroy();
|
|
235
|
+
done({ up: false });
|
|
236
|
+
});
|
|
237
|
+
req.on('error', () => done({ up: false }));
|
|
238
|
+
req.end();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function sleep(ms) {
|
|
242
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
243
|
+
}
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
|
+
// Start / stop / status
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
/**
|
|
248
|
+
* The script to relaunch. `process.execArgv` is carried over with it so a dev
|
|
249
|
+
* run under tsx (whose execArgv holds the tsx loader flags) re-launches through
|
|
250
|
+
* tsx instead of handing a `.ts` file to bare node.
|
|
251
|
+
*/
|
|
252
|
+
function entryScript() {
|
|
253
|
+
const script = process.argv[1];
|
|
254
|
+
if (!script)
|
|
255
|
+
throw new Error('cannot determine the codeman entry script to relaunch');
|
|
256
|
+
return script;
|
|
257
|
+
}
|
|
258
|
+
/** Marks one launch in the append-only log so a tail cannot mix two runs. */
|
|
259
|
+
const LOG_SEPARATOR = '=== codeman web start';
|
|
260
|
+
/**
|
|
261
|
+
* Last few lines of the daemon log, for reporting a failed start. The log is
|
|
262
|
+
* append-only across launches, so the tail starts at the last separator when
|
|
263
|
+
* there is one: otherwise a crash report is padded with the previous run's
|
|
264
|
+
* cheerful startup banner.
|
|
265
|
+
*/
|
|
266
|
+
export function tailLog(maxLines = 15) {
|
|
267
|
+
try {
|
|
268
|
+
const lines = readFileSync(logFilePath(), 'utf-8').trimEnd().split('\n');
|
|
269
|
+
const start = lines.map((line) => line.startsWith(LOG_SEPARATOR)).lastIndexOf(true);
|
|
270
|
+
const current = start === -1 ? lines : lines.slice(start + 1);
|
|
271
|
+
return current.slice(-maxLines).join('\n');
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return '';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Spawn a detached `codeman web` and wait until it answers before returning.
|
|
279
|
+
* Refuses when a server is already up on this data dir (see rule 1 in the module
|
|
280
|
+
* docblock).
|
|
281
|
+
*/
|
|
282
|
+
export async function startDaemon(options) {
|
|
283
|
+
const logPath = logFilePath();
|
|
284
|
+
const url = buildBaseUrl(options);
|
|
285
|
+
const statusUrl = buildStatusUrl(options);
|
|
286
|
+
const existingPid = readLivePid();
|
|
287
|
+
if (existingPid !== null) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
reason: 'already-running',
|
|
291
|
+
pid: existingPid,
|
|
292
|
+
logPath,
|
|
293
|
+
message: `a Codeman server is already running (pid ${existingPid}). Stop it with \`codeman web --stop\` first.`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const alreadyServing = await probeServer(statusUrl, 1500);
|
|
297
|
+
if (alreadyServing.up) {
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
reason: 'already-running',
|
|
301
|
+
logPath,
|
|
302
|
+
url,
|
|
303
|
+
message: `something is already serving ${url}. Two servers on one data dir attach to each other's tmux sessions, so refusing to start.`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// A pidfile that survived a crash: the process is gone, so it is just litter.
|
|
307
|
+
if (readPidFile() !== null)
|
|
308
|
+
removePidFile();
|
|
309
|
+
const args = buildWebArgs(options);
|
|
310
|
+
try {
|
|
311
|
+
appendFileSync(logPath, `\n${LOG_SEPARATOR} ${new Date().toISOString()} ===\n`, 'utf-8');
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
/* the spawn below reports a genuinely unwritable log */
|
|
315
|
+
}
|
|
316
|
+
const logFd = openSync(logPath, 'a');
|
|
317
|
+
let child;
|
|
318
|
+
try {
|
|
319
|
+
child = spawn(process.execPath, [...process.execArgv, entryScript(), ...args], {
|
|
320
|
+
detached: true,
|
|
321
|
+
stdio: ['ignore', logFd, logFd],
|
|
322
|
+
env: process.env,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
closeSync(logFd);
|
|
327
|
+
}
|
|
328
|
+
let exited = false;
|
|
329
|
+
child.on('exit', () => {
|
|
330
|
+
exited = true;
|
|
331
|
+
});
|
|
332
|
+
child.on('error', () => {
|
|
333
|
+
exited = true;
|
|
334
|
+
});
|
|
335
|
+
const pid = child.pid;
|
|
336
|
+
if (pid === undefined) {
|
|
337
|
+
return { ok: false, reason: 'exited', logPath, message: 'failed to spawn the server process' };
|
|
338
|
+
}
|
|
339
|
+
writeFileSync(pidFilePath(), `${pid}\n`, 'utf-8');
|
|
340
|
+
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
341
|
+
while (Date.now() < deadline) {
|
|
342
|
+
if (exited) {
|
|
343
|
+
removePidFile();
|
|
344
|
+
child.unref();
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
reason: 'exited',
|
|
348
|
+
logPath,
|
|
349
|
+
message: `the server exited during startup. Last lines of ${logPath}:\n${tailLog()}`,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const probe = await probeServer(statusUrl, 1000);
|
|
353
|
+
if (probe.up) {
|
|
354
|
+
child.unref();
|
|
355
|
+
return { ok: true, pid, url, logPath };
|
|
356
|
+
}
|
|
357
|
+
await sleep(POLL_INTERVAL_MS);
|
|
358
|
+
}
|
|
359
|
+
child.unref();
|
|
360
|
+
return {
|
|
361
|
+
ok: false,
|
|
362
|
+
reason: 'timeout',
|
|
363
|
+
pid,
|
|
364
|
+
url,
|
|
365
|
+
logPath,
|
|
366
|
+
message: `the server did not answer ${url} within ${START_TIMEOUT_MS / 1000}s. It may still be starting; check ${logPath}.`,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** SIGTERM the recorded server and wait for it to actually exit. */
|
|
370
|
+
export async function stopDaemon(options) {
|
|
371
|
+
const pid = readPidFile();
|
|
372
|
+
if (pid === null) {
|
|
373
|
+
const probe = await probeServer(buildStatusUrl(options), 1500);
|
|
374
|
+
if (probe.up) {
|
|
375
|
+
return {
|
|
376
|
+
ok: false,
|
|
377
|
+
reason: 'no-pidfile-but-responding',
|
|
378
|
+
message: 'a server is responding but there is no pidfile, so it was not started with `-d`. If it is a service use `codeman service uninstall` (or stop the unit); otherwise `pkill -f "index.js web"`.',
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return { ok: true, reason: 'not-running', message: 'no daemon is running; nothing to stop' };
|
|
382
|
+
}
|
|
383
|
+
if (!isProcessAlive(pid)) {
|
|
384
|
+
removePidFile();
|
|
385
|
+
return { ok: true, pid, message: `stale pidfile removed (pid ${pid} was not running)` };
|
|
386
|
+
}
|
|
387
|
+
const command = readProcessCommand(pid);
|
|
388
|
+
if (command !== null && !looksLikeCodemanWeb(command)) {
|
|
389
|
+
return {
|
|
390
|
+
ok: false,
|
|
391
|
+
reason: 'foreign-pid',
|
|
392
|
+
pid,
|
|
393
|
+
message: `pid ${pid} is not a Codeman server (${command}). Refusing to signal it; delete ${pidFilePath()} if it is stale.`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// SIGTERM, never SIGKILL: cli.ts flushes state on the way out.
|
|
397
|
+
try {
|
|
398
|
+
process.kill(pid, 'SIGTERM');
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
return { ok: false, reason: 'foreign-pid', pid, message: `could not signal pid ${pid}: ${String(err)}` };
|
|
402
|
+
}
|
|
403
|
+
const deadline = Date.now() + STOP_TIMEOUT_MS;
|
|
404
|
+
while (Date.now() < deadline) {
|
|
405
|
+
if (!isProcessAlive(pid)) {
|
|
406
|
+
removePidFile();
|
|
407
|
+
return { ok: true, pid };
|
|
408
|
+
}
|
|
409
|
+
await sleep(POLL_INTERVAL_MS);
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
reason: 'timeout',
|
|
414
|
+
pid,
|
|
415
|
+
message: `pid ${pid} did not exit within ${STOP_TIMEOUT_MS / 1000}s. Force it with \`kill -9 ${pid}\` if you are sure.`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/** Report on both halves: the recorded process, and whether the port answers. */
|
|
419
|
+
export async function daemonStatus(options) {
|
|
420
|
+
const url = buildBaseUrl(options);
|
|
421
|
+
const pid = readPidFile();
|
|
422
|
+
const probe = await probeServer(buildStatusUrl(options), 2000);
|
|
423
|
+
return {
|
|
424
|
+
pid,
|
|
425
|
+
running: readLivePid() !== null,
|
|
426
|
+
responding: probe.up,
|
|
427
|
+
version: probe.version,
|
|
428
|
+
url,
|
|
429
|
+
pidFile: pidFilePath(),
|
|
430
|
+
logPath: logFilePath(),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
//# sourceMappingURL=daemon-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-control.js","sourceRoot":"","sources":["../src/daemon-control.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks-config.d.ts
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* Key exports:
|
|
11
11
|
* - `generateHooksConfig()` — returns hooks object for settings.local.json
|
|
12
12
|
* - `writeHooksConfig(casePath)` — writes hooks + env config to disk
|
|
13
|
+
* - `ensureCodemanHooks(casePath)` — safely installs/updates hooks for a managed case
|
|
13
14
|
* - `updateCaseEnvVars(casePath, envVars)` — merges env vars into settings
|
|
14
15
|
*
|
|
15
16
|
* Hook events generated: `idle_prompt`, `permission_prompt`, `elicitation_dialog`,
|
|
16
17
|
* `stop`, `teammate_idle`, `task_completed`
|
|
17
18
|
*
|
|
18
|
-
* Hook categories: `Notification` (3 matchers), `Stop` (1), `
|
|
19
|
-
* `TaskCompleted` (1), `PostToolUse` (1 self-contained
|
|
19
|
+
* Hook categories: `Notification` (3 matchers), `Stop` (1), `SubagentStop` (1),
|
|
20
|
+
* `TeammateIdle` (1), `TaskCompleted` (1), `PostToolUse` (1 self-contained
|
|
21
|
+
* background Bash rewake)
|
|
20
22
|
*
|
|
21
23
|
* @dependencies types (HookEventType), config/auth-config (HOOK_TIMEOUT_SECONDS)
|
|
22
24
|
* @consumedby web/server (session creation), session-cli-builder (env setup)
|
|
@@ -27,8 +29,10 @@
|
|
|
27
29
|
* Inline Node helper for Claude Code's `asyncRewake` hook.
|
|
28
30
|
*
|
|
29
31
|
* A background Bash tool returns immediately with a task ID, then Claude writes
|
|
30
|
-
* its completion as a queue-operation in the transcript.
|
|
31
|
-
*
|
|
32
|
+
* its completion as a queue-operation in the top-level transcript. Subagent hooks
|
|
33
|
+
* receive their own transcript path even though their completion is parent-owned,
|
|
34
|
+
* so the helper watches both paths. Watching durable records avoids injecting
|
|
35
|
+
* terminal input (which could submit a user's draft).
|
|
32
36
|
* The helper is embedded in settings via `node -e`, so it has no script path
|
|
33
37
|
* that can go stale after an install or plugin-cache cleanup.
|
|
34
38
|
*
|
|
@@ -38,6 +42,12 @@
|
|
|
38
42
|
* forever. The ppid check misses subreaper setups; the deadline is the backstop.
|
|
39
43
|
*/
|
|
40
44
|
export declare function generateBackgroundWakeScript(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Keep a Claude subagent alive while its Monitor or background Bash work is live.
|
|
47
|
+
* Claude otherwise can publish the worker's last progress sentence as an Agent
|
|
48
|
+
* result when one watcher ends, even if other tracked tasks are still running.
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateSubagentStopGuardScript(): string;
|
|
41
51
|
/**
|
|
42
52
|
* Generates the hooks section for .claude/settings.local.json
|
|
43
53
|
*
|
|
@@ -72,6 +82,15 @@ export declare function updateCaseModel(casePath: string, model: string | null):
|
|
|
72
82
|
* Merges with existing file content, only touching the `hooks` key.
|
|
73
83
|
*/
|
|
74
84
|
export declare function writeHooksConfig(casePath: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Ensures an explicitly managed case has the current Codeman hooks.
|
|
87
|
+
*
|
|
88
|
+
* Unlike `refreshStaleCodemanHooks`, this may add Codeman handlers to a valid
|
|
89
|
+
* user-owned settings file. It is therefore reserved for case quick-starts,
|
|
90
|
+
* where the user has explicitly asked Codeman to manage that workspace. A
|
|
91
|
+
* malformed existing file is left untouched rather than replaced.
|
|
92
|
+
*/
|
|
93
|
+
export declare function ensureCodemanHooks(casePath: string): Promise<void>;
|
|
75
94
|
/**
|
|
76
95
|
* Self-heal a case's Codeman-owned hooks block.
|
|
77
96
|
*
|
|
@@ -79,8 +98,11 @@ export declare function writeHooksConfig(casePath: string): Promise<void>;
|
|
|
79
98
|
* X-Codeman-Hook-Secret header was added (COD-54, 2026-06-10) keep hook curls in their
|
|
80
99
|
* settings.local.json that POST to /api/hook-event WITHOUT the secret — which, once the
|
|
81
100
|
* gate requires it unconditionally (COD-91), silently 401 on a password-protected install.
|
|
82
|
-
* Older Codeman blocks also lack the background Bash async-rewake hook
|
|
83
|
-
* stale shape
|
|
101
|
+
* Older Codeman blocks also lack the current background Bash async-rewake hook or the
|
|
102
|
+
* SubagentStop guard. A further stale shape: hook curls without `-k`, which exit 60 on
|
|
103
|
+
* every --https/tailscale install (the cert is self-signed), swallowed by the hooks'
|
|
104
|
+
* own `|| true` — all six hook events die silently. Refresh any of these stale shapes
|
|
105
|
+
* on launch so existing cases heal.
|
|
84
106
|
*
|
|
85
107
|
* Deliberately surgical: regenerates ONLY when settings.local.json already contains
|
|
86
108
|
* Codeman's own hook curls (they target `/api/hook-event`) and they are stale. No-op
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-config.d.ts","sourceRoot":"","sources":["../src/hooks-config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hooks-config.d.ts","sourceRoot":"","sources":["../src/hooks-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAmCH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CA4GrD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,IAAI,MAAM,CA0ExD;AAiBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAiF1E;AA0ED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvG;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BxG;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3F;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBtE;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B9E;AAKD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAalD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B7F"}
|