@ahpd/server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +2 -0
- package/dist/main.js +50 -6
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Config {
|
|
|
25
25
|
* beside this configuration, or `memory` until the process ends.
|
|
26
26
|
*/
|
|
27
27
|
sessions?: 'file' | 'memory';
|
|
28
|
+
/** A file every frame is appended to, both directions, as JSON lines. */
|
|
29
|
+
wire?: string;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Where this tool's files live.
|
package/dist/main.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { automationsPath, configPath, loadConfig, sessionsPath } from './config.js';
|
|
2
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { automationsPath, configPath, daemonLog, loadConfig, sessionsPath } from './config.js';
|
|
4
4
|
import { version } from './version.js';
|
|
5
5
|
import { running, start, stop as stopDaemon } from './daemon.js';
|
|
6
6
|
import { pty } from './pty.js';
|
|
7
7
|
import { claude } from '@ahpd/agent-claude';
|
|
8
|
-
import { createHost, fileResources, gitBranches, gitChanges, gitWorktrees, hostTools, listen, fileSessions, memoryAutomations, memorySessions, scheduledAutomations, shellTerminals } from '@ahpd/sdk';
|
|
8
|
+
import { createHost, fileResources, gitBranches, gitChanges, gitWorktrees, githubPullRequests, hostTools, listen, fileSessions, memoryAutomations, memorySessions, scheduledAutomations, shellTerminals } from '@ahpd/sdk';
|
|
9
9
|
const USAGE = `ahpd - an Agent Host Protocol server, with a Claude backend
|
|
10
10
|
|
|
11
11
|
ahpd [options] run it here, in this terminal
|
|
@@ -35,12 +35,16 @@ const USAGE = `ahpd - an Agent Host Protocol server, with a Claude backend
|
|
|
35
35
|
session's settings go. file, the default,
|
|
36
36
|
keeps them beside the configuration; memory
|
|
37
37
|
forgets them when this process ends.
|
|
38
|
+
--wire <file> Append every frame, both directions, to this
|
|
39
|
+
file as JSON lines: { at, from, peer, frame }.
|
|
40
|
+
pnpm wire -- <file> checks it against the
|
|
41
|
+
protocol schema.
|
|
38
42
|
--version, -v What version this is
|
|
39
43
|
--help, -h This
|
|
40
44
|
|
|
41
45
|
Every option above can be a key in the configuration file instead, spelled the
|
|
42
46
|
way it is here without the dashes: port, host, paths, connectionToken,
|
|
43
|
-
connectionTokenFile, withoutConnectionToken, automations, sessions. A flag beats the file, because a
|
|
47
|
+
connectionTokenFile, withoutConnectionToken, automations, sessions, wire. A flag beats the file, because a
|
|
44
48
|
flag is this run and a file is every run until somebody edits it.
|
|
45
49
|
|
|
46
50
|
Clients present the token as ?tkn=<secret> on the URL, or as an
|
|
@@ -104,6 +108,9 @@ function parse(argv) {
|
|
|
104
108
|
stop(`--sessions takes file or memory, not ${said}.`);
|
|
105
109
|
break;
|
|
106
110
|
}
|
|
111
|
+
case '--wire':
|
|
112
|
+
options.wire = String(argv[++i]);
|
|
113
|
+
break;
|
|
107
114
|
case '--help':
|
|
108
115
|
case '-h':
|
|
109
116
|
options.help = true;
|
|
@@ -147,6 +154,8 @@ function parse(argv) {
|
|
|
147
154
|
if (!argv.includes('--sessions') && (file.sessions === 'file' || file.sessions === 'memory')) {
|
|
148
155
|
options.sessions = file.sessions;
|
|
149
156
|
}
|
|
157
|
+
if (!argv.includes('--wire') && typeof file.wire === 'string')
|
|
158
|
+
options.wire = file.wire;
|
|
150
159
|
if (options.paths.length === 0)
|
|
151
160
|
options.paths.push(process.cwd());
|
|
152
161
|
return options;
|
|
@@ -298,6 +307,7 @@ const host = createHost({
|
|
|
298
307
|
directories: gitBranches(),
|
|
299
308
|
changes: gitChanges(),
|
|
300
309
|
worktrees: gitWorktrees(),
|
|
310
|
+
github: githubPullRequests(),
|
|
301
311
|
/*
|
|
302
312
|
* The host's own tools, offered to every session's model.
|
|
303
313
|
*
|
|
@@ -357,18 +367,52 @@ const host = createHost({
|
|
|
357
367
|
* `onEvent` hands it the message to do that with.
|
|
358
368
|
*/
|
|
359
369
|
onEvent: (message) => process.stdout.write(`${new Date().toISOString()} ${message}\n`),
|
|
370
|
+
/*
|
|
371
|
+
* What the window's diagnostics get from this daemon.
|
|
372
|
+
*
|
|
373
|
+
* The version out of the manifest, the log a detached daemon writes to and
|
|
374
|
+
* the wire capture when there is one, and a shutdown that is the same
|
|
375
|
+
* signal handler `ahpd stop` reaches through `SIGTERM`.
|
|
376
|
+
*/
|
|
377
|
+
diagnostics: {
|
|
378
|
+
version: version(),
|
|
379
|
+
logs: () => [daemonLog(), ...(options.wire === undefined ? [] : [options.wire])],
|
|
380
|
+
shutdown: () => { process.kill(process.pid, 'SIGTERM'); },
|
|
381
|
+
},
|
|
360
382
|
});
|
|
361
383
|
// Whichever runtime this is. `listen` is the only file that knows, and it
|
|
362
384
|
// says which one it found - a daemon that silently ran somewhere unexpected
|
|
363
385
|
// would be a daemon nobody could tell apart from the one they meant to start.
|
|
364
|
-
|
|
386
|
+
/*
|
|
387
|
+
* The wire, written down as it happens.
|
|
388
|
+
*
|
|
389
|
+
* One line per frame, appended synchronously so the file is whole at the
|
|
390
|
+
* moment anything else is read: a capture that lags the crash it is meant to
|
|
391
|
+
* explain is no capture. `frame` is the message parsed, so `jq` reads the
|
|
392
|
+
* file; a frame that is not JSON is kept as the string it was, because a
|
|
393
|
+
* client that sent one is exactly what a capture is for.
|
|
394
|
+
*/
|
|
395
|
+
const tap = options.wire === undefined ? undefined : (() => {
|
|
396
|
+
const at = options.wire;
|
|
397
|
+
writeFileSync(at, '');
|
|
398
|
+
return (from, text, peer) => {
|
|
399
|
+
let frame = text;
|
|
400
|
+
try {
|
|
401
|
+
frame = JSON.parse(text);
|
|
402
|
+
}
|
|
403
|
+
catch { /* kept as text */ }
|
|
404
|
+
appendFileSync(at, `${JSON.stringify({ at: new Date().toISOString(), from, peer, frame })}\n`);
|
|
405
|
+
};
|
|
406
|
+
})();
|
|
407
|
+
const listener = await listen({ port: options.port, host: options.host, ...(token !== undefined ? { token } : {}), ...(tap ? { tap } : {}) }, (peer) => host.accept(peer));
|
|
365
408
|
process.stdout.write(`ahpd on ws://${listener.host}:${listener.port} (${listener.runtime}), sessions in ${options.paths.join(', ')}\n`
|
|
366
409
|
// Its own line rather than the end of the one above, which `daemon.ts`
|
|
367
410
|
// reads the session directories off with a regular expression.
|
|
368
411
|
+ `automations ${memory ? 'in memory, schedules do not fire' : `in ${automationsPath()}, schedules fire`}\n`
|
|
369
412
|
// Where the secret came from, never the secret: stdout is a log, and a log
|
|
370
413
|
// is the one place a credential should not end up.
|
|
371
|
-
+ `${from}\n`
|
|
414
|
+
+ `${from}\n`
|
|
415
|
+
+ (options.wire === undefined ? '' : `wire to ${options.wire}\n`));
|
|
372
416
|
const shutdown = () => {
|
|
373
417
|
void Promise.resolve(listener.close()).finally(() => process.exit(0));
|
|
374
418
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahpd/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An Agent Host Protocol server on Node, Bun or Deno. Ships with a Claude backend",
|
|
6
6
|
"keywords": [
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@microsoft/agent-host-protocol": "^0.9.0",
|
|
44
|
-
"@ahpd/agent-claude": "^0.
|
|
45
|
-
"@ahpd/sdk": "^0.
|
|
44
|
+
"@ahpd/agent-claude": "^0.5.0",
|
|
45
|
+
"@ahpd/sdk": "^0.5.0"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|