@ahpd/server 0.1.0 → 0.3.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 +88 -81
- package/dist/config.d.ts +72 -0
- package/dist/config.js +75 -0
- package/dist/daemon.d.ts +31 -0
- package/dist/daemon.js +162 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +335 -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/src/types/worktrees.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/** A working tree of its own, so two sessions in one repository do not collide. */
|
|
2
|
-
|
|
3
|
-
/** What to make, and from where. */
|
|
4
|
-
export interface Worktree {
|
|
5
|
-
/** The repository to make it from. A path, not a URI. */
|
|
6
|
-
repository: string;
|
|
7
|
-
/** The branch to start from. */
|
|
8
|
-
base: string;
|
|
9
|
-
/**
|
|
10
|
-
* The branch to create and check out, or nothing to check out `base` itself.
|
|
11
|
-
*
|
|
12
|
-
* Absent is what `worktreeCreateNewBranch: false` means: the session
|
|
13
|
-
* continues an existing branch rather than starting one. Git refuses a
|
|
14
|
-
* second worktree on a branch already checked out somewhere, which is the
|
|
15
|
-
* right refusal - two sessions on one branch is the collision worktrees
|
|
16
|
-
* exist to prevent.
|
|
17
|
-
*/
|
|
18
|
-
branch?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Whether the created branch tracks the upstream of the one it started from.
|
|
21
|
-
*
|
|
22
|
-
* Off unless asked. A tracking branch pushes at its upstream by default, so
|
|
23
|
-
* a `git push` inside a session's worktree would go at the branch it was
|
|
24
|
-
* started *from* - which is the one mistake in here that reaches a shared
|
|
25
|
-
* repository.
|
|
26
|
-
*/
|
|
27
|
-
track?: boolean;
|
|
28
|
-
/** Where to put it. */
|
|
29
|
-
path: string;
|
|
30
|
-
/**
|
|
31
|
-
* Git-ignored files to copy in, as glob patterns relative to the repository.
|
|
32
|
-
*
|
|
33
|
-
* A checkout carries what git tracks and nothing else, so a worktree of an
|
|
34
|
-
* ordinary project has no `.env`, no `node_modules`, and none of the local
|
|
35
|
-
* configuration the thing needs to run. Without these the isolation works
|
|
36
|
-
* and the session inside it cannot build - which is a failure the person
|
|
37
|
-
* meets after choosing it rather than while choosing it.
|
|
38
|
-
*/
|
|
39
|
-
include?: string[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Making and unmaking git worktrees.
|
|
44
|
-
*
|
|
45
|
-
* A port for the reason `DirectoryFacts` and `ChangesetSource` are ports: it
|
|
46
|
-
* spawns `git`, which is a binary rather than a runtime API and may not be
|
|
47
|
-
* installed. A host given none advertises no `isolation` at all, which is the
|
|
48
|
-
* honest answer - a client draws the control a host says it has.
|
|
49
|
-
*
|
|
50
|
-
* Deliberately not part of `ChangesetSource`. That port describes changes; a
|
|
51
|
-
* worktree is a directory, and the two happen to share an implementation
|
|
52
|
-
* rather than a subject.
|
|
53
|
-
*/
|
|
54
|
-
export interface Worktrees {
|
|
55
|
-
/**
|
|
56
|
-
* The repository root above this directory, if it is in one.
|
|
57
|
-
*
|
|
58
|
-
* The root and not the directory: a worktree is made from the repository,
|
|
59
|
-
* and a session pointed at `src/` inside one is still a session in it.
|
|
60
|
-
* Undefined means there is no repository here and no isolation to offer.
|
|
61
|
-
*/
|
|
62
|
-
repository(dir: string): Promise<string | undefined>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The branches this repository has, most useful first.
|
|
66
|
-
*
|
|
67
|
-
* What a client draws the base-branch picker from. An empty list is a real
|
|
68
|
-
* answer - a repository with no commits has no branches - and leaves the
|
|
69
|
-
* control with nothing to choose but the default.
|
|
70
|
-
*/
|
|
71
|
-
branches(repository: string): Promise<string[]>;
|
|
72
|
-
|
|
73
|
-
/** Make one. Answers the path it made, which is where the session runs. */
|
|
74
|
-
create(worktree: Worktree): Promise<void>;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Whether anything in it is uncommitted.
|
|
78
|
-
*
|
|
79
|
-
* Asked before it is removed, and the answer decides whether it is: work
|
|
80
|
-
* nobody committed is work this daemon cannot judge the value of.
|
|
81
|
-
*/
|
|
82
|
-
dirty(path: string): Promise<boolean>;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Take one away, and the branch with it if nothing else points at it.
|
|
86
|
-
*
|
|
87
|
-
* Only ever called for a worktree this host made and only when `dirty`
|
|
88
|
-
* answered no.
|
|
89
|
-
*/
|
|
90
|
-
remove(repository: string, path: string, branch?: string): Promise<void>;
|
|
91
|
-
}
|
package/src/worktrees.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/** Git worktrees, so two sessions in one repository do not edit under each other. */
|
|
2
|
-
|
|
3
|
-
import { execFile } from 'node:child_process';
|
|
4
|
-
import { basename, dirname, join } from 'node:path';
|
|
5
|
-
import { cp, mkdir, readdir, stat } from 'node:fs/promises';
|
|
6
|
-
import type { Worktree, Worktrees } from './types/worktrees.js';
|
|
7
|
-
|
|
8
|
-
/** Run git, and answer what it said. Rejects with git's own words. */
|
|
9
|
-
const git = (dir: string, args: string[], ms = 60_000): Promise<string> =>
|
|
10
|
-
new Promise((resolve, reject) => {
|
|
11
|
-
execFile('git', ['-C', dir, ...args], { timeout: ms, maxBuffer: 8 << 20 }, (error, out, bad) => {
|
|
12
|
-
if (!error) return resolve(out.toString());
|
|
13
|
-
// git's own message, not node's: "fatal: a branch named x already
|
|
14
|
-
// exists" is something a person can act on and `Command failed` is not.
|
|
15
|
-
const said = bad.toString().trim() || error.message;
|
|
16
|
-
reject(new Error(said));
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Where a repository's worktrees live: `<repo>.worktrees`, beside it.
|
|
22
|
-
*
|
|
23
|
-
* Beside rather than inside, because a worktree inside its own repository is
|
|
24
|
-
* a directory git then has to be told to ignore, and every tool that walks the
|
|
25
|
-
* tree finds the same project twice. The name is the one VS Code's host uses,
|
|
26
|
-
* so a worktree either of them makes is one the other finds.
|
|
27
|
-
*/
|
|
28
|
-
export const worktreesOf = (repository: string): string =>
|
|
29
|
-
join(dirname(repository), `${basename(repository)}.worktrees`);
|
|
30
|
-
|
|
31
|
-
/** The directory name for a branch: no slashes, since it has to be one segment. */
|
|
32
|
-
export const worktreeFor = (branch: string): string =>
|
|
33
|
-
branch.replace(/^agents\//, '').replace(/\//g, '-');
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Git worktrees, as a host's `Worktrees` port.
|
|
37
|
-
*
|
|
38
|
-
* Deliberately not part of `createHost`: it spawns `git`, which is a binary
|
|
39
|
-
* rather than a runtime API and may not be installed at all. A host without it
|
|
40
|
-
* offers no isolation and every session runs in the folder it was pointed at,
|
|
41
|
-
* which is what this daemon did before there was a choice.
|
|
42
|
-
*
|
|
43
|
-
* ```ts
|
|
44
|
-
* createHost({ path, agents: [claude({ paths })], worktrees: gitWorktrees() });
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function gitWorktrees(): Worktrees {
|
|
48
|
-
return {
|
|
49
|
-
repository: async (dir) => {
|
|
50
|
-
const found = await git(dir, ['rev-parse', '--show-toplevel'], 5_000).catch(() => undefined);
|
|
51
|
-
const root = found?.trim();
|
|
52
|
-
return root === undefined || root === '' ? undefined : root;
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
branches: async (repository) => {
|
|
56
|
-
/*
|
|
57
|
-
* Local branches by most recent commit, then the remote ones.
|
|
58
|
-
*
|
|
59
|
-
* Ordered rather than alphabetical because the useful answer is almost
|
|
60
|
-
* always near the top of the first list, and a picker that opens on
|
|
61
|
-
* `archive/2019-cleanup` is one somebody has to search.
|
|
62
|
-
*/
|
|
63
|
-
const said = await git(
|
|
64
|
-
repository,
|
|
65
|
-
['for-each-ref', '--sort=-committerdate', '--format=%(refname:short)', 'refs/heads', 'refs/remotes'],
|
|
66
|
-
10_000,
|
|
67
|
-
).catch(() => '');
|
|
68
|
-
const seen = new Set<string>();
|
|
69
|
-
for (const line of said.split('\n')) {
|
|
70
|
-
const name = line.trim();
|
|
71
|
-
// `origin/HEAD` is a symbolic ref rather than a branch, and checking
|
|
72
|
-
// it out is a detached head instead of the branch somebody meant.
|
|
73
|
-
if (name === '' || name.endsWith('/HEAD')) continue;
|
|
74
|
-
seen.add(name.replace(/^origin\//, ''));
|
|
75
|
-
}
|
|
76
|
-
return [...seen];
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
create: async (worktree: Worktree) => {
|
|
80
|
-
await mkdir(dirname(worktree.path), { recursive: true });
|
|
81
|
-
/*
|
|
82
|
-
* A new branch, or the one that was chosen.
|
|
83
|
-
*
|
|
84
|
-
* `--no-track` unless asked, because the new branch is this session's
|
|
85
|
-
* own: tracking would make it push to the base branch's upstream by
|
|
86
|
-
* default, so a `git push` inside a worktree session would go at the
|
|
87
|
-
* branch it was started *from*. That is the one mistake in here that
|
|
88
|
-
* reaches a shared repository.
|
|
89
|
-
*
|
|
90
|
-
* No branch at all is `worktreeCreateNewBranch: false` - the session
|
|
91
|
-
* continues `base` rather than starting something. Git refuses a second
|
|
92
|
-
* worktree on a branch already checked out, and that refusal is right.
|
|
93
|
-
*/
|
|
94
|
-
await git(worktree.repository, worktree.branch === undefined
|
|
95
|
-
? ['worktree', 'add', worktree.path, worktree.base]
|
|
96
|
-
: [
|
|
97
|
-
'worktree', 'add', worktree.track === true ? '--track' : '--no-track',
|
|
98
|
-
'-b', worktree.branch, worktree.path, worktree.base,
|
|
99
|
-
]);
|
|
100
|
-
for (const pattern of worktree.include ?? []) {
|
|
101
|
-
// Best effort, one pattern at a time: a `.env` that is not there is
|
|
102
|
-
// the ordinary case, and a session that refused to start over a
|
|
103
|
-
// missing optional file would be worse than one without it.
|
|
104
|
-
await copy(worktree.repository, worktree.path, pattern).catch(() => undefined);
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
dirty: async (path) => {
|
|
109
|
-
// `--porcelain` says nothing at all about a clean tree, which makes the
|
|
110
|
-
// empty answer the reliable one. Untracked files count: a file the
|
|
111
|
-
// agent wrote and never added is exactly the work worth keeping.
|
|
112
|
-
const said = await git(path, ['status', '--porcelain'], 10_000).catch(() => 'unknown');
|
|
113
|
-
return said.trim() !== '';
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
remove: async (repository, path, branch) => {
|
|
117
|
-
await git(repository, ['worktree', 'remove', path]);
|
|
118
|
-
/*
|
|
119
|
-
* The branch too, and only the one this host made.
|
|
120
|
-
*
|
|
121
|
-
* Named by the caller rather than derived from the directory: a branch
|
|
122
|
-
* prefix a client asked for changes the name, and guessing it wrong
|
|
123
|
-
* either deletes nothing or - far worse - names somebody else's. `-d`
|
|
124
|
-
* refuses to delete a branch carrying commits nothing else has, which
|
|
125
|
-
* is the same judgement the dirty check makes about uncommitted work.
|
|
126
|
-
*/
|
|
127
|
-
if (branch !== undefined) await git(repository, ['branch', '-d', branch]).catch(() => undefined);
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Copy one git-ignored path into the new worktree, keeping its place.
|
|
134
|
-
*
|
|
135
|
-
* A glob only in the shallow sense the reference uses it: a literal path, or
|
|
136
|
-
* one trailing `*` in the last segment. A full matcher here would be a
|
|
137
|
-
* dependency and a surprise - the patterns clients send are `.env`,
|
|
138
|
-
* `.env.local` and `node_modules`.
|
|
139
|
-
*/
|
|
140
|
-
const copy = async (from: string, to: string, pattern: string): Promise<void> => {
|
|
141
|
-
if (pattern.includes('..') || pattern.startsWith('/')) return;
|
|
142
|
-
const star = pattern.lastIndexOf('*');
|
|
143
|
-
if (star === -1) {
|
|
144
|
-
await cp(join(from, pattern), join(to, pattern), { recursive: true, errorOnExist: false });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const at = pattern.lastIndexOf('/');
|
|
148
|
-
const dir = at === -1 ? '' : pattern.slice(0, at);
|
|
149
|
-
const leaf = at === -1 ? pattern : pattern.slice(at + 1);
|
|
150
|
-
if (leaf.indexOf('*') !== leaf.lastIndexOf('*')) return;
|
|
151
|
-
const [before, after] = leaf.split('*');
|
|
152
|
-
const entries = await readdir(join(from, dir)).catch(() => [] as string[]);
|
|
153
|
-
for (const entry of entries) {
|
|
154
|
-
if (!entry.startsWith(before ?? '') || !entry.endsWith(after ?? '')) continue;
|
|
155
|
-
const source = join(from, dir, entry);
|
|
156
|
-
// Directories as well as files: `node_modules` is the pattern that makes
|
|
157
|
-
// the difference between a worktree that builds and one that does not.
|
|
158
|
-
const what = await stat(source).catch(() => undefined);
|
|
159
|
-
if (!what) continue;
|
|
160
|
-
await cp(source, join(to, dir, entry), { recursive: what.isDirectory(), errorOnExist: false });
|
|
161
|
-
}
|
|
162
|
-
};
|