@ahpd/server 0.1.0 → 0.2.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 +87 -81
- package/dist/config.d.ts +65 -0
- package/dist/config.js +75 -0
- package/dist/daemon.d.ts +26 -0
- package/dist/daemon.js +160 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +297 -0
- package/dist/pty.d.ts +12 -0
- package/dist/pty.js +56 -0
- package/package.json +15 -25
- package/dist/automations.d.ts +0 -4
- package/dist/automations.d.ts.map +0 -1
- package/dist/automations.js +0 -191
- package/dist/automations.js.map +0 -1
- package/dist/catalog.d.ts +0 -48
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js +0 -51
- package/dist/catalog.js.map +0 -1
- package/dist/changes.d.ts +0 -4
- package/dist/changes.d.ts.map +0 -1
- package/dist/changes.js +0 -601
- package/dist/changes.js.map +0 -1
- package/dist/cron.d.ts +0 -43
- package/dist/cron.d.ts.map +0 -1
- package/dist/cron.js +0 -211
- package/dist/cron.js.map +0 -1
- package/dist/git.d.ts +0 -16
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js +0 -126
- package/dist/git.js.map +0 -1
- package/dist/host.d.ts +0 -24
- package/dist/host.d.ts.map +0 -1
- package/dist/host.js +0 -5850
- package/dist/host.js.map +0 -1
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -34
- package/dist/index.js.map +0 -1
- package/dist/listen.d.ts +0 -3
- package/dist/listen.d.ts.map +0 -1
- package/dist/listen.js +0 -183
- package/dist/listen.js.map +0 -1
- package/dist/paging.d.ts +0 -38
- package/dist/paging.d.ts.map +0 -1
- package/dist/paging.js +0 -53
- package/dist/paging.js.map +0 -1
- package/dist/paths.d.ts +0 -15
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -19
- package/dist/paths.js.map +0 -1
- package/dist/resources.d.ts +0 -122
- package/dist/resources.d.ts.map +0 -1
- package/dist/resources.js +0 -494
- package/dist/resources.js.map +0 -1
- package/dist/rpc.d.ts +0 -63
- package/dist/rpc.d.ts.map +0 -1
- package/dist/rpc.js +0 -208
- package/dist/rpc.js.map +0 -1
- package/dist/scheduled.d.ts +0 -48
- package/dist/scheduled.d.ts.map +0 -1
- package/dist/scheduled.js +0 -325
- package/dist/scheduled.js.map +0 -1
- package/dist/terminals.d.ts +0 -29
- package/dist/terminals.d.ts.map +0 -1
- package/dist/terminals.js +0 -346
- package/dist/terminals.js.map +0 -1
- package/dist/tools.d.ts +0 -18
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -65
- package/dist/tools.js.map +0 -1
- package/dist/types/agent.d.ts +0 -245
- package/dist/types/agent.d.ts.map +0 -1
- package/dist/types/agent.js +0 -3
- package/dist/types/agent.js.map +0 -1
- package/dist/types/automations.d.ts +0 -164
- package/dist/types/automations.d.ts.map +0 -1
- package/dist/types/automations.js +0 -3
- package/dist/types/automations.js.map +0 -1
- package/dist/types/catalog.d.ts +0 -30
- package/dist/types/catalog.d.ts.map +0 -1
- package/dist/types/catalog.js +0 -3
- package/dist/types/catalog.js.map +0 -1
- package/dist/types/changes.d.ts +0 -272
- package/dist/types/changes.d.ts.map +0 -1
- package/dist/types/changes.js +0 -3
- package/dist/types/changes.js.map +0 -1
- package/dist/types/common.d.ts +0 -9
- package/dist/types/common.d.ts.map +0 -1
- package/dist/types/common.js +0 -3
- package/dist/types/common.js.map +0 -1
- package/dist/types/host.d.ts +0 -372
- package/dist/types/host.d.ts.map +0 -1
- package/dist/types/host.js +0 -3
- package/dist/types/host.js.map +0 -1
- package/dist/types/index.d.ts +0 -20
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -8
- package/dist/types/index.js.map +0 -1
- package/dist/types/listen.d.ts +0 -48
- package/dist/types/listen.d.ts.map +0 -1
- package/dist/types/listen.js +0 -3
- package/dist/types/listen.js.map +0 -1
- package/dist/types/paging.d.ts +0 -10
- package/dist/types/paging.d.ts.map +0 -1
- package/dist/types/paging.js +0 -3
- package/dist/types/paging.js.map +0 -1
- package/dist/types/probe.d.ts +0 -46
- package/dist/types/probe.d.ts.map +0 -1
- package/dist/types/probe.js +0 -3
- package/dist/types/probe.js.map +0 -1
- package/dist/types/resources.d.ts +0 -83
- package/dist/types/resources.d.ts.map +0 -1
- package/dist/types/resources.js +0 -3
- package/dist/types/resources.js.map +0 -1
- package/dist/types/rpc.d.ts +0 -65
- package/dist/types/rpc.d.ts.map +0 -1
- package/dist/types/rpc.js +0 -3
- package/dist/types/rpc.js.map +0 -1
- package/dist/types/session.d.ts +0 -395
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -3
- package/dist/types/session.js.map +0 -1
- package/dist/types/terminals.d.ts +0 -131
- package/dist/types/terminals.d.ts.map +0 -1
- package/dist/types/terminals.js +0 -3
- package/dist/types/terminals.js.map +0 -1
- package/dist/types/wire.d.ts +0 -62
- package/dist/types/wire.d.ts.map +0 -1
- package/dist/types/wire.js +0 -13
- package/dist/types/wire.js.map +0 -1
- package/dist/types/worktrees.d.ts +0 -86
- package/dist/types/worktrees.d.ts.map +0 -1
- package/dist/types/worktrees.js +0 -3
- package/dist/types/worktrees.js.map +0 -1
- package/dist/worktrees.d.ts +0 -27
- package/dist/worktrees.d.ts.map +0 -1
- package/dist/worktrees.js +0 -152
- package/dist/worktrees.js.map +0 -1
- package/src/automations.ts +0 -204
- package/src/catalog.ts +0 -53
- package/src/changes.ts +0 -629
- package/src/cron.ts +0 -251
- package/src/git.ts +0 -132
- package/src/host.ts +0 -5947
- package/src/index.ts +0 -40
- package/src/listen.ts +0 -240
- package/src/paging.ts +0 -56
- package/src/paths.ts +0 -19
- package/src/resources.ts +0 -513
- package/src/rpc.ts +0 -222
- package/src/scheduled.ts +0 -369
- package/src/terminals.ts +0 -353
- package/src/tools.ts +0 -64
- package/src/types/agent.ts +0 -255
- package/src/types/automations.ts +0 -164
- package/src/types/catalog.ts +0 -26
- package/src/types/changes.ts +0 -266
- package/src/types/common.ts +0 -9
- package/src/types/host.ts +0 -386
- package/src/types/index.ts +0 -27
- package/src/types/listen.ts +0 -53
- package/src/types/paging.ts +0 -11
- package/src/types/probe.ts +0 -42
- package/src/types/resources.ts +0 -107
- package/src/types/rpc.ts +0 -68
- package/src/types/session.ts +0 -415
- package/src/types/terminals.ts +0 -133
- package/src/types/wire.ts +0 -69
- package/src/types/worktrees.ts +0 -91
- package/src/worktrees.ts +0 -162
package/dist/main.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { automationsPath, configPath, loadConfig } from './config.js';
|
|
4
|
+
import { running, start, stop as stopDaemon } from './daemon.js';
|
|
5
|
+
import { pty } from './pty.js';
|
|
6
|
+
import { claude } from '@ahpd/agent-claude';
|
|
7
|
+
import { createHost, fileResources, gitBranches, gitChanges, gitWorktrees, hostTools, listen, scheduledAutomations, shellTerminals } from '@ahpd/sdk';
|
|
8
|
+
const USAGE = `ahpd - an Agent Host Protocol server, with a Claude backend
|
|
9
|
+
|
|
10
|
+
ahpd [options] run it here, in this terminal
|
|
11
|
+
ahpd start [options] run it in the background and let go of it
|
|
12
|
+
ahpd stop stop the one running in the background
|
|
13
|
+
ahpd status say whether one is, and where
|
|
14
|
+
ahpd config say where the configuration is, and what it says
|
|
15
|
+
|
|
16
|
+
--port <n> Listen here. Default 9187; 0 picks a free one.
|
|
17
|
+
--host <addr> Bind here. Default 127.0.0.1. Pass 0.0.0.0 to
|
|
18
|
+
accept from other machines, which needs a token.
|
|
19
|
+
--path <dir> A directory this host serves. Repeatable; the
|
|
20
|
+
first is the default a client gets when it
|
|
21
|
+
names none, and a directory not named here is
|
|
22
|
+
refused. Default: where the daemon started.
|
|
23
|
+
--connection-token <secret> Require this secret on every connection.
|
|
24
|
+
--connection-token-file <p> Require the secret in this file. A fresh one is
|
|
25
|
+
written if the file is not there.
|
|
26
|
+
--without-connection-token Accept any connection. Only when the port is
|
|
27
|
+
already reachable by nobody else.
|
|
28
|
+
--config-file <p> Read this instead of the file below.
|
|
29
|
+
--help, -h This
|
|
30
|
+
|
|
31
|
+
Every option above can be a key in the configuration file instead, spelled the
|
|
32
|
+
way it is here without the dashes: port, host, paths, connectionToken,
|
|
33
|
+
connectionTokenFile, withoutConnectionToken. A flag beats the file, because a
|
|
34
|
+
flag is this run and a file is every run until somebody edits it.
|
|
35
|
+
|
|
36
|
+
Clients present the token as ?tkn=<secret> on the URL, or as an
|
|
37
|
+
Authorization: Bearer <secret> header.
|
|
38
|
+
|
|
39
|
+
Point a client at it:
|
|
40
|
+
ahpc --host ws://127.0.0.1:9187
|
|
41
|
+
`;
|
|
42
|
+
function parse(argv) {
|
|
43
|
+
const options = {
|
|
44
|
+
port: 9187,
|
|
45
|
+
host: '127.0.0.1',
|
|
46
|
+
paths: [],
|
|
47
|
+
open: false,
|
|
48
|
+
help: false,
|
|
49
|
+
};
|
|
50
|
+
for (let i = 0; i < argv.length; i++) {
|
|
51
|
+
switch (argv[i]) {
|
|
52
|
+
case '--port':
|
|
53
|
+
options.port = Number(argv[++i]);
|
|
54
|
+
break;
|
|
55
|
+
case '--host':
|
|
56
|
+
options.host = String(argv[++i]);
|
|
57
|
+
break;
|
|
58
|
+
// Repeatable. One host over two projects is one catalogue and one
|
|
59
|
+
// process, which is the case a second `--path` is for; a directory this
|
|
60
|
+
// host was not told about is refused rather than served, because a host
|
|
61
|
+
// that ran the agent wherever it was told is one anybody who can reach
|
|
62
|
+
// the port can point at any directory on the machine.
|
|
63
|
+
case '--path':
|
|
64
|
+
options.paths.push(String(argv[++i]));
|
|
65
|
+
break;
|
|
66
|
+
case '--connection-token':
|
|
67
|
+
options.token = String(argv[++i]);
|
|
68
|
+
break;
|
|
69
|
+
case '--connection-token-file':
|
|
70
|
+
options.tokenFile = String(argv[++i]);
|
|
71
|
+
break;
|
|
72
|
+
case '--without-connection-token':
|
|
73
|
+
options.open = true;
|
|
74
|
+
break;
|
|
75
|
+
case '--config-file':
|
|
76
|
+
options.configFile = String(argv[++i]);
|
|
77
|
+
break;
|
|
78
|
+
case '--help':
|
|
79
|
+
case '-h':
|
|
80
|
+
options.help = true;
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
if (argv[i]?.startsWith('-')) {
|
|
84
|
+
process.stderr.write(`Unknown option ${argv[i]}. Try --help.\n`);
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/*
|
|
90
|
+
* The file, under the flags.
|
|
91
|
+
*
|
|
92
|
+
* Each source is narrower than the one below it: a flag is this run and a
|
|
93
|
+
* file is every run until somebody edits it, so the flag wins. `paths` is
|
|
94
|
+
* replaced rather than merged - a file that named two directories and a
|
|
95
|
+
* `--path` that named a third would otherwise serve three, which is not
|
|
96
|
+
* what either of them said.
|
|
97
|
+
*/
|
|
98
|
+
const file = loadConfig(options.configFile);
|
|
99
|
+
if (!argv.includes('--port') && typeof file.port === 'number')
|
|
100
|
+
options.port = file.port;
|
|
101
|
+
if (!argv.includes('--host') && typeof file.host === 'string')
|
|
102
|
+
options.host = file.host;
|
|
103
|
+
if (options.paths.length === 0 && Array.isArray(file.paths))
|
|
104
|
+
options.paths.push(...file.paths);
|
|
105
|
+
if (options.token === undefined && typeof file.connectionToken === 'string')
|
|
106
|
+
options.token = file.connectionToken;
|
|
107
|
+
if (options.tokenFile === undefined && typeof file.connectionTokenFile === 'string')
|
|
108
|
+
options.tokenFile = file.connectionTokenFile;
|
|
109
|
+
if (!options.open && file.withoutConnectionToken === true)
|
|
110
|
+
options.open = true;
|
|
111
|
+
if (options.paths.length === 0)
|
|
112
|
+
options.paths.push(process.cwd());
|
|
113
|
+
return options;
|
|
114
|
+
}
|
|
115
|
+
const stop = (message) => {
|
|
116
|
+
process.stderr.write(`${message}\n`);
|
|
117
|
+
process.exit(2);
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The secret this host will require, and where it came from.
|
|
121
|
+
*
|
|
122
|
+
* A token file that is not there is written rather than refused: the flag is
|
|
123
|
+
* how a supervisor points several processes at one secret, and requiring the
|
|
124
|
+
* person to invent one first makes the convenient spelling the unusable one.
|
|
125
|
+
*/
|
|
126
|
+
function secret(options) {
|
|
127
|
+
if (options.open) {
|
|
128
|
+
if (options.token !== undefined || options.tokenFile !== undefined) {
|
|
129
|
+
stop('--without-connection-token contradicts the token you also passed.');
|
|
130
|
+
}
|
|
131
|
+
return { from: 'no token: any connection is accepted' };
|
|
132
|
+
}
|
|
133
|
+
if (options.token !== undefined && options.tokenFile !== undefined) {
|
|
134
|
+
stop('Pass --connection-token or --connection-token-file, not both.');
|
|
135
|
+
}
|
|
136
|
+
if (options.token !== undefined) {
|
|
137
|
+
if (options.token === '')
|
|
138
|
+
stop('--connection-token was empty.');
|
|
139
|
+
return { token: options.token, from: 'token: from --connection-token' };
|
|
140
|
+
}
|
|
141
|
+
if (options.tokenFile !== undefined) {
|
|
142
|
+
if (existsSync(options.tokenFile)) {
|
|
143
|
+
const held = readFileSync(options.tokenFile, 'utf8').trim();
|
|
144
|
+
if (held === '')
|
|
145
|
+
stop(`${options.tokenFile} is empty.`);
|
|
146
|
+
return { token: held, from: `token: read from ${options.tokenFile}` };
|
|
147
|
+
}
|
|
148
|
+
const made = crypto.randomUUID().replaceAll('-', '');
|
|
149
|
+
// Owner-only, because the file is the credential.
|
|
150
|
+
writeFileSync(options.tokenFile, `${made}\n`, { mode: 0o600 });
|
|
151
|
+
return { token: made, from: `token: written to ${options.tokenFile}` };
|
|
152
|
+
}
|
|
153
|
+
// Loopback needs no secret - anything reaching it is already on this
|
|
154
|
+
// machine. Any other address does, and starting without one there would be
|
|
155
|
+
// a host on the network that anybody can drive.
|
|
156
|
+
const loopback = options.host === '127.0.0.1' || options.host === '::1' || options.host === 'localhost';
|
|
157
|
+
if (!loopback) {
|
|
158
|
+
stop(`Binding ${options.host} exposes this host beyond this machine.\n`
|
|
159
|
+
+ 'Pass --connection-token, --connection-token-file, or --without-connection-token.');
|
|
160
|
+
}
|
|
161
|
+
return { from: 'no token: loopback only' };
|
|
162
|
+
}
|
|
163
|
+
const argv = process.argv.slice(2);
|
|
164
|
+
/*
|
|
165
|
+
* The subcommands, which are about a daemon rather than being one.
|
|
166
|
+
*
|
|
167
|
+
* Answered before anything is built: `stop` and `status` need no host, and
|
|
168
|
+
* `start` is this same program run again with the rest of the line. Keeping
|
|
169
|
+
* them here means there is one place that knows how to read these options,
|
|
170
|
+
* and `start` cannot drift from what it starts.
|
|
171
|
+
*/
|
|
172
|
+
const verb = argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : undefined;
|
|
173
|
+
if (verb !== undefined) {
|
|
174
|
+
const rest = argv.slice(1);
|
|
175
|
+
if (verb === 'start') {
|
|
176
|
+
// Parsed here as well as by the child, so a bad option is refused now
|
|
177
|
+
// rather than by something that has already been let go of.
|
|
178
|
+
parse(rest);
|
|
179
|
+
try {
|
|
180
|
+
const begun = await start(rest, process.argv[1]);
|
|
181
|
+
process.stdout.write(`ahpd on ${begun.url} (pid ${String(begun.pid)}), sessions in ${begun.paths.join(', ') || process.cwd()}\n`);
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
process.stderr.write(`Could not start it: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (verb === 'stop') {
|
|
190
|
+
const stopped = stopDaemon();
|
|
191
|
+
process.stdout.write(stopped ? `Stopped ${stopped.url} (pid ${String(stopped.pid)}).\n` : 'None running.\n');
|
|
192
|
+
process.exit(stopped ? 0 : 1);
|
|
193
|
+
}
|
|
194
|
+
if (verb === 'status') {
|
|
195
|
+
const found = running();
|
|
196
|
+
if (!found) {
|
|
197
|
+
process.stdout.write('None running.\n');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(`ahpd on ${found.url} (pid ${String(found.pid)}), started ${found.startedAt}\n`);
|
|
201
|
+
if (found.paths.length > 0)
|
|
202
|
+
process.stdout.write(`sessions in ${found.paths.join(', ')}\n`);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
if (verb === 'config') {
|
|
206
|
+
const at = argv.includes('--config-file') ? argv[argv.indexOf('--config-file') + 1] : configPath();
|
|
207
|
+
process.stdout.write(`${at}\n`);
|
|
208
|
+
const found = loadConfig(argv.includes('--config-file') ? at : undefined);
|
|
209
|
+
const rows = Object.entries(found);
|
|
210
|
+
process.stdout.write(rows.length === 0
|
|
211
|
+
? ' (nothing set)\n'
|
|
212
|
+
: `${rows.map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`).join('\n')}\n`);
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}
|
|
215
|
+
process.stderr.write(`No command called ${verb}. Try --help.\n`);
|
|
216
|
+
process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
const options = parse(argv);
|
|
219
|
+
if (options.help) {
|
|
220
|
+
process.stdout.write(USAGE);
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
const { token, from } = secret(options);
|
|
224
|
+
const host = createHost({
|
|
225
|
+
path: options.paths[0],
|
|
226
|
+
// The daemon serves Claude Code. The host serves whatever it is given -
|
|
227
|
+
// see `examples/` for what a second one looks like.
|
|
228
|
+
agents: [claude({ paths: options.paths })],
|
|
229
|
+
/*
|
|
230
|
+
* What this daemon can do that the protocol cannot.
|
|
231
|
+
*
|
|
232
|
+
* All three touch the machine - a file, a subprocess, a `git` binary - and
|
|
233
|
+
* all three are handed in rather than reached for, so `createHost` stays the
|
|
234
|
+
* protocol and nothing else. A host embedded somewhere with its own notion
|
|
235
|
+
* of a file passes its own; one with no shell passes no terminals and says
|
|
236
|
+
* `-32601` when asked for one.
|
|
237
|
+
*/
|
|
238
|
+
resources: fileResources(),
|
|
239
|
+
terminals: shellTerminals(await pty()),
|
|
240
|
+
directories: gitBranches(),
|
|
241
|
+
changes: gitChanges(),
|
|
242
|
+
worktrees: gitWorktrees(),
|
|
243
|
+
/*
|
|
244
|
+
* The host's own tools, offered to every session's model.
|
|
245
|
+
*
|
|
246
|
+
* What a session cannot see from inside itself: the sessions running beside
|
|
247
|
+
* it and the terminals the person has open. Both read-only, and both facts
|
|
248
|
+
* only the host has.
|
|
249
|
+
*/
|
|
250
|
+
tools: hostTools(),
|
|
251
|
+
/*
|
|
252
|
+
* Automations, with a clock.
|
|
253
|
+
*
|
|
254
|
+
* A daemon is the case the port was written for: it is already running at
|
|
255
|
+
* nine in the morning, which is the only way an automation fires with
|
|
256
|
+
* nobody connected. Definitions live in a file beside the configuration and
|
|
257
|
+
* come back on a restart; the runs do not, because they name sessions that
|
|
258
|
+
* went when the process did.
|
|
259
|
+
*
|
|
260
|
+
* A host embedded in something that already schedules passes its own store
|
|
261
|
+
* instead, and one that should fire nothing passes `memoryAutomations()`.
|
|
262
|
+
*/
|
|
263
|
+
automations: scheduledAutomations({
|
|
264
|
+
// Beside the configuration, which is this daemon's decision to make and
|
|
265
|
+
// not the store's - see `ScheduledOptions.file`.
|
|
266
|
+
file: automationsPath(),
|
|
267
|
+
onProblem: (message) => process.stdout.write(`${message}\n`),
|
|
268
|
+
}),
|
|
269
|
+
/*
|
|
270
|
+
* When, as well as what.
|
|
271
|
+
*
|
|
272
|
+
* The daemon's own lines had no times on them, so a log read after
|
|
273
|
+
* something went wrong said what happened in order and nothing about how
|
|
274
|
+
* far apart - which is most of what is worth knowing when a process died
|
|
275
|
+
* a minute after starting. It is also what makes this log line up against
|
|
276
|
+
* a client's, since the two are separate programs and the only thing they
|
|
277
|
+
* share is a clock.
|
|
278
|
+
*
|
|
279
|
+
* The timestamp is added here rather than in `createHost`, because a host
|
|
280
|
+
* embedded in something else has its own log with its own format and
|
|
281
|
+
* `onEvent` hands it the message to do that with.
|
|
282
|
+
*/
|
|
283
|
+
onEvent: (message) => process.stdout.write(`${new Date().toISOString()} ${message}\n`),
|
|
284
|
+
});
|
|
285
|
+
// Whichever runtime this is. `listen` is the only file that knows, and it
|
|
286
|
+
// says which one it found - a daemon that silently ran somewhere unexpected
|
|
287
|
+
// would be a daemon nobody could tell apart from the one they meant to start.
|
|
288
|
+
const listener = await listen({ port: options.port, host: options.host, ...(token !== undefined ? { token } : {}) }, (peer) => host.accept(peer));
|
|
289
|
+
process.stdout.write(`ahpd on ws://${listener.host}:${listener.port} (${listener.runtime}), sessions in ${options.paths.join(', ')}\n`
|
|
290
|
+
// Where the secret came from, never the secret: stdout is a log, and a log
|
|
291
|
+
// is the one place a credential should not end up.
|
|
292
|
+
+ `${from}\n`);
|
|
293
|
+
const shutdown = () => {
|
|
294
|
+
void Promise.resolve(listener.close()).finally(() => process.exit(0));
|
|
295
|
+
};
|
|
296
|
+
process.on('SIGINT', shutdown);
|
|
297
|
+
process.on('SIGTERM', shutdown);
|
package/dist/pty.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The optional pseudoterminal binding, and how to find out whether it works. */
|
|
2
|
+
import type { SpawnPty } from '@ahpd/sdk';
|
|
3
|
+
/**
|
|
4
|
+
* The binding, if this machine has one that runs.
|
|
5
|
+
*
|
|
6
|
+
* With it, shells run under a real terminal and the shell's own OSC 133 marks
|
|
7
|
+
* turn into command boundaries. Without it they run on pipes and the state
|
|
8
|
+
* says `isPty: false`, which is what the protocol has that flag for.
|
|
9
|
+
*/
|
|
10
|
+
export declare const pty: () => Promise<{
|
|
11
|
+
pty?: SpawnPty;
|
|
12
|
+
}>;
|
package/dist/pty.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** The optional pseudoterminal binding, and how to find out whether it works. */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* By name at runtime, so the type checker is not asked for a module that may
|
|
6
|
+
* not be installed - which is the whole point of it being optional.
|
|
7
|
+
*/
|
|
8
|
+
const MODULE = 'node-pty';
|
|
9
|
+
/** This file, as the child is told to run it. */
|
|
10
|
+
const SELF = fileURLToPath(import.meta.url);
|
|
11
|
+
/**
|
|
12
|
+
* Whether this runtime can load it, asked of a process that is allowed to die.
|
|
13
|
+
*
|
|
14
|
+
* `node-pty` is native code, and native code that does not match its runtime
|
|
15
|
+
* does not throw: `dlopen` aborts the process, or the module panics on a
|
|
16
|
+
* function the runtime does not implement, and neither is something a
|
|
17
|
+
* `try/catch` around the import can hold. Bun's install of `node-pty` does
|
|
18
|
+
* exactly this, which takes the whole daemon down over a feature that is
|
|
19
|
+
* marked optional.
|
|
20
|
+
*
|
|
21
|
+
* So the import is tried in a child of the same runtime first, and only
|
|
22
|
+
* repeated here once the child has survived it. A crash is then an exit code.
|
|
23
|
+
*/
|
|
24
|
+
const loadable = () => {
|
|
25
|
+
const probe = spawnSync(process.execPath, [SELF, '--probe'], { stdio: 'ignore' });
|
|
26
|
+
return probe.status === 0;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The binding, if this machine has one that runs.
|
|
30
|
+
*
|
|
31
|
+
* With it, shells run under a real terminal and the shell's own OSC 133 marks
|
|
32
|
+
* turn into command boundaries. Without it they run on pipes and the state
|
|
33
|
+
* says `isPty: false`, which is what the protocol has that flag for.
|
|
34
|
+
*/
|
|
35
|
+
export const pty = async () => {
|
|
36
|
+
if (!loadable())
|
|
37
|
+
return {};
|
|
38
|
+
try {
|
|
39
|
+
const found = await import(/* @vite-ignore */ MODULE);
|
|
40
|
+
return typeof found.spawn === 'function' ? { pty: found.spawn } : {};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// The child `loadable` spawns. Nothing else in this file has run yet, and
|
|
47
|
+
// nothing else is imported, so what it exits with is about `node-pty` alone.
|
|
48
|
+
if (process.argv[1] === SELF && process.argv[2] === '--probe') {
|
|
49
|
+
try {
|
|
50
|
+
const found = await import(/* @vite-ignore */ MODULE);
|
|
51
|
+
process.exit(typeof found.spawn === 'function' ? 0 : 1);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahpd/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "An Agent Host Protocol server on Node, Bun or Deno. Ships with a Claude backend",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ahp",
|
|
8
8
|
"agent-host-protocol",
|
|
@@ -12,15 +12,14 @@
|
|
|
12
12
|
"protocol",
|
|
13
13
|
"json-rpc",
|
|
14
14
|
"websocket",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"daemon"
|
|
15
|
+
"cli",
|
|
16
|
+
"command-line",
|
|
17
|
+
"daemon",
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code"
|
|
18
20
|
],
|
|
19
21
|
"author": "Luiz Fernando Softov <lfs@softov.dev>",
|
|
20
22
|
"license": "MIT",
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
23
|
"homepage": "https://github.com/softov/ahpd#readme",
|
|
25
24
|
"repository": {
|
|
26
25
|
"type": "git",
|
|
@@ -30,32 +29,23 @@
|
|
|
30
29
|
"bugs": {
|
|
31
30
|
"url": "https://github.com/softov/ahpd/issues"
|
|
32
31
|
},
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"exports": {
|
|
36
|
-
".": {
|
|
37
|
-
"types": "./dist/index.d.ts",
|
|
38
|
-
"development": "./src/index.ts",
|
|
39
|
-
"default": "./dist/index.js"
|
|
40
|
-
}
|
|
32
|
+
"bin": {
|
|
33
|
+
"ahpd": "./dist/main.js"
|
|
41
34
|
},
|
|
42
35
|
"files": [
|
|
43
36
|
"dist",
|
|
44
|
-
"src",
|
|
45
37
|
"README.md"
|
|
46
38
|
],
|
|
47
39
|
"engines": {
|
|
48
40
|
"node": ">=22"
|
|
49
41
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@microsoft/agent-host-protocol": "^0.9.0",
|
|
44
|
+
"@ahpd/agent-claude": "^0.2.0",
|
|
45
|
+
"@ahpd/sdk": "^0.2.0"
|
|
53
46
|
},
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"@microsoft/agent-host-protocol": "^0.9.0"
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
59
49
|
},
|
|
60
50
|
"scripts": {}
|
|
61
51
|
}
|
package/dist/automations.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"automations.d.ts","sourceRoot":"","sources":["../src/automations.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAG1D,OAAO,KAAK,EAA6B,eAAe,EAAgB,MAAM,wBAAwB,CAAC;AAsBvG,wBAAgB,iBAAiB,IAAI,eAAe,CAkLnD"}
|
package/dist/automations.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/** Automations held in memory, run when somebody asks. */
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
/**
|
|
4
|
-
* The automations of a host that does not schedule.
|
|
5
|
-
*
|
|
6
|
-
* Deliberately half a store, and the half worth having first: definitions can
|
|
7
|
-
* be written, patched, listed and *run*, and nothing here holds a clock. An
|
|
8
|
-
* automation with a schedule trigger is accepted, kept and reported with no
|
|
9
|
-
* `nextRunAt` - which is the honest form of "this host will not fire that",
|
|
10
|
-
* and is what stops a client drawing a next-run time that will never arrive.
|
|
11
|
-
*
|
|
12
|
-
* Everything is in memory, so it goes when the process does. A store that
|
|
13
|
-
* outlives a restart is a different implementation of the same interface, and
|
|
14
|
-
* the reason the interface exists.
|
|
15
|
-
*/
|
|
16
|
-
const now = () => new Date().toISOString();
|
|
17
|
-
/** How many runs a summary list carries before it needs a cursor. */
|
|
18
|
-
const PAGE = 20;
|
|
19
|
-
export function memoryAutomations() {
|
|
20
|
-
const held = new Map();
|
|
21
|
-
/** Runs by automation, newest first. */
|
|
22
|
-
const history = new Map();
|
|
23
|
-
/** Every run by its own URI, for the channel a client watches it on. */
|
|
24
|
-
const byRun = new Map();
|
|
25
|
-
const listeners = [];
|
|
26
|
-
const said = (event) => {
|
|
27
|
-
for (const listener of listeners)
|
|
28
|
-
listener(event);
|
|
29
|
-
};
|
|
30
|
-
/** The summary form of a run, which is what an automation carries. */
|
|
31
|
-
const summary = (run) => ({
|
|
32
|
-
resource: run.resource,
|
|
33
|
-
automation: run.automation,
|
|
34
|
-
origin: run.origin,
|
|
35
|
-
lifecycle: run.lifecycle,
|
|
36
|
-
sessionCount: run.sessions.length,
|
|
37
|
-
...(run.primarySession !== undefined ? { primarySession: run.primarySession } : {}),
|
|
38
|
-
});
|
|
39
|
-
/** Rebuild the entry a client reads, so `runs` and `operations` are never stale. */
|
|
40
|
-
const entry = (automation) => {
|
|
41
|
-
const enabled = automation.definition.enabled !== false;
|
|
42
|
-
return {
|
|
43
|
-
...automation,
|
|
44
|
-
runs: (history.get(automation.resource) ?? []).slice(0, PAGE).map(summary),
|
|
45
|
-
...((history.get(automation.resource) ?? []).length > PAGE
|
|
46
|
-
? { runsNextCursor: String(PAGE) }
|
|
47
|
-
: {}),
|
|
48
|
-
// `run` only where it would do something. A disabled automation is one
|
|
49
|
-
// somebody switched off, and offering the button anyway is a control
|
|
50
|
-
// that argues with the switch beside it.
|
|
51
|
-
operations: enabled ? ['update', 'remove', 'run'] : ['update', 'remove'],
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
return {
|
|
55
|
-
list: () => [...held.values()].map(entry),
|
|
56
|
-
get: (resource) => {
|
|
57
|
-
const found = held.get(resource);
|
|
58
|
-
return found && entry(found);
|
|
59
|
-
},
|
|
60
|
-
/*
|
|
61
|
-
* What this store understands, which is nothing.
|
|
62
|
-
*
|
|
63
|
-
* This command answers with *event* triggers. Schedule triggers are
|
|
64
|
-
* protocol-defined and never listed here, and manual is not a trigger at
|
|
65
|
-
* all - the protocol says an empty trigger list is what manual-only means.
|
|
66
|
-
* So a store with no event triggers answers with none, and a client reads
|
|
67
|
-
* "this host will not fire that" from the absent `nextRunAt` instead.
|
|
68
|
-
*/
|
|
69
|
-
triggers: () => [],
|
|
70
|
-
create: (resource, definition) => {
|
|
71
|
-
const at = now();
|
|
72
|
-
const made = {
|
|
73
|
-
resource,
|
|
74
|
-
definition,
|
|
75
|
-
runs: [],
|
|
76
|
-
operations: [],
|
|
77
|
-
createdAt: at,
|
|
78
|
-
modifiedAt: at,
|
|
79
|
-
};
|
|
80
|
-
held.set(resource, made);
|
|
81
|
-
said({ automation: resource });
|
|
82
|
-
return entry(made);
|
|
83
|
-
},
|
|
84
|
-
update: (resource, changes) => {
|
|
85
|
-
const found = held.get(resource);
|
|
86
|
-
if (!found)
|
|
87
|
-
return undefined;
|
|
88
|
-
// A patch: absent keys are left alone, which is the whole difference
|
|
89
|
-
// between this and a write. A client sending the whole definition back
|
|
90
|
-
// would otherwise revert whatever another client changed meanwhile.
|
|
91
|
-
const after = {
|
|
92
|
-
...found,
|
|
93
|
-
definition: { ...found.definition, ...changes },
|
|
94
|
-
modifiedAt: now(),
|
|
95
|
-
};
|
|
96
|
-
held.set(resource, after);
|
|
97
|
-
said({ automation: resource });
|
|
98
|
-
return entry(after);
|
|
99
|
-
},
|
|
100
|
-
remove: (resource) => {
|
|
101
|
-
if (!held.delete(resource))
|
|
102
|
-
return false;
|
|
103
|
-
for (const run of history.get(resource) ?? [])
|
|
104
|
-
byRun.delete(run.resource);
|
|
105
|
-
history.delete(resource);
|
|
106
|
-
said({ removed: resource });
|
|
107
|
-
return true;
|
|
108
|
-
},
|
|
109
|
-
run: async (resource, origin, start) => {
|
|
110
|
-
const found = held.get(resource);
|
|
111
|
-
if (!found)
|
|
112
|
-
return undefined;
|
|
113
|
-
if (found.definition.enabled === false)
|
|
114
|
-
return undefined;
|
|
115
|
-
const template = (typeof found.definition.session === 'object' && found.definition.session !== null
|
|
116
|
-
? found.definition.session
|
|
117
|
-
: {});
|
|
118
|
-
const message = (typeof found.definition.message === 'object' && found.definition.message !== null
|
|
119
|
-
? found.definition.message
|
|
120
|
-
: {});
|
|
121
|
-
const run = {
|
|
122
|
-
resource: `ahp-automation-run:/${randomUUID()}`,
|
|
123
|
-
automation: resource,
|
|
124
|
-
origin,
|
|
125
|
-
lifecycle: { status: 'pending', createdAt: now() },
|
|
126
|
-
sessions: [],
|
|
127
|
-
};
|
|
128
|
-
byRun.set(run.resource, run);
|
|
129
|
-
const past = history.get(resource) ?? [];
|
|
130
|
-
// Newest first, which is the order a client shows them in.
|
|
131
|
-
history.set(resource, [run, ...past]);
|
|
132
|
-
said({ automation: resource, run: run.resource });
|
|
133
|
-
const directories = Array.isArray(template.workingDirectories) ? template.workingDirectories : [];
|
|
134
|
-
const where = typeof directories[0] === 'string'
|
|
135
|
-
? directories[0].replace(/^file:\/\//, '')
|
|
136
|
-
: undefined;
|
|
137
|
-
const options = {
|
|
138
|
-
...(typeof template.provider === 'string' ? { provider: template.provider } : {}),
|
|
139
|
-
...(where !== undefined ? { workingDirectory: where } : {}),
|
|
140
|
-
...(typeof template.config === 'object' && template.config !== null
|
|
141
|
-
? { config: template.config }
|
|
142
|
-
: {}),
|
|
143
|
-
text: typeof message.text === 'string' ? message.text : String(found.definition.title ?? ''),
|
|
144
|
-
origin: { kind: 'automation', automation: resource, run: run.resource },
|
|
145
|
-
};
|
|
146
|
-
try {
|
|
147
|
-
const session = await start(options);
|
|
148
|
-
run.sessions = [session];
|
|
149
|
-
run.primarySession = session;
|
|
150
|
-
run.lifecycle = { ...run.lifecycle, status: 'running', startedAt: now() };
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
// Failed, and why. A run that vanished would be indistinguishable from
|
|
154
|
-
// one that never started.
|
|
155
|
-
run.lifecycle = {
|
|
156
|
-
...run.lifecycle,
|
|
157
|
-
status: 'failed',
|
|
158
|
-
endedAt: now(),
|
|
159
|
-
error: { message: error instanceof Error ? error.message : String(error) },
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
said({ automation: resource, run: run.resource });
|
|
163
|
-
return run;
|
|
164
|
-
},
|
|
165
|
-
runOf: (resource) => byRun.get(resource),
|
|
166
|
-
unlink: (resource, session) => {
|
|
167
|
-
const run = byRun.get(resource);
|
|
168
|
-
if (run === undefined || !run.sessions.includes(session))
|
|
169
|
-
return false;
|
|
170
|
-
run.sessions = run.sessions.filter((one) => one !== session);
|
|
171
|
-
// The protocol says removing the primary clears it. A run pointing at a
|
|
172
|
-
// session that is gone is a run a client opens onto nothing.
|
|
173
|
-
if (run.primarySession === session)
|
|
174
|
-
delete run.primarySession;
|
|
175
|
-
said({ automation: run.automation, run: run.resource });
|
|
176
|
-
return true;
|
|
177
|
-
},
|
|
178
|
-
runs: (resource, cursor) => {
|
|
179
|
-
const all = history.get(resource) ?? [];
|
|
180
|
-
const from = cursor === undefined ? 0 : Number(cursor);
|
|
181
|
-
const at = Number.isFinite(from) && from >= 0 ? from : 0;
|
|
182
|
-
const page = all.slice(at, at + PAGE);
|
|
183
|
-
return {
|
|
184
|
-
items: page.map(summary),
|
|
185
|
-
...(at + PAGE < all.length ? { nextCursor: String(at + PAGE) } : {}),
|
|
186
|
-
};
|
|
187
|
-
},
|
|
188
|
-
onChanged: (observer) => { listeners.push(observer); },
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
//# sourceMappingURL=automations.js.map
|
package/dist/automations.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","sourceRoot":"","sources":["../src/automations.ts"],"names":[],"mappings":"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"}
|