@agentshouse/kit 0.2.1-cbabe8ad337ff → 0.2.1-cc6246211e00c
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 +42 -41
- package/dist/acp.js +38 -17
- package/dist/agents.js +111 -24
- package/dist/api.js +7 -3
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +11 -25
- package/dist/building.js +46 -0
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +87 -22
- package/dist/connection.js +126 -0
- package/dist/conversations.js +410 -156
- package/dist/copies.js +185 -123
- package/dist/end.js +23 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +6 -2
- package/dist/files.js +139 -31
- package/dist/git.js +13 -11
- package/dist/home.js +96 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -177
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +111 -23
- package/dist/logout.js +0 -2
- package/dist/mcp.js +4 -2
- package/dist/parts.js +34 -16
- package/dist/placement.js +39 -0
- package/dist/relay.js +33 -2
- package/dist/resident.js +106 -44
- package/dist/scope.js +3 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +22 -39
- package/dist/skills.js +49 -35
- package/dist/transcription.js +85 -45
- package/dist/translate.js +2 -8
- package/dist/update.js +98 -53
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +22 -0
- package/dist/work.js +3 -5
- package/package.json +20 -20
- package/dist/bootstrap.json +0 -24
- package/dist/stream.js +0 -72
- package/how-we-work.md +0 -23
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/relay.js
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
const PROXY = /^https?_proxy$/i;
|
|
3
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
4
|
+
let selected;
|
|
5
|
+
let restoreProxy;
|
|
6
|
+
export function selectRelay(relay) {
|
|
7
|
+
selected = relay === undefined || relay === '' ? null : relay;
|
|
8
|
+
proxyUnlessRelayed();
|
|
9
|
+
}
|
|
10
|
+
export function relayOrigin() {
|
|
11
|
+
if (selected !== undefined)
|
|
12
|
+
return selected ?? undefined;
|
|
13
|
+
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
14
|
+
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
15
|
+
}
|
|
16
|
+
function unlessRelayed() {
|
|
17
|
+
if (relayOrigin() === undefined)
|
|
18
|
+
return { ...process.env };
|
|
19
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
20
|
+
}
|
|
21
|
+
export function proxyUnlessRelayed() {
|
|
22
|
+
restoreProxy?.();
|
|
23
|
+
restoreProxy = setGlobalProxyFromEnv(unlessRelayed());
|
|
24
|
+
}
|
|
25
|
+
export function childEnvironment() {
|
|
26
|
+
const environment = unlessRelayed();
|
|
27
|
+
if (!Object.keys(environment).some((name) => PROXY.test(name)))
|
|
28
|
+
return environment;
|
|
29
|
+
const bypassed = [environment.NO_PROXY ?? environment.no_proxy, ...LOOPBACK].filter((entry) => entry !== undefined && entry !== '').join(',');
|
|
30
|
+
return { ...environment, NO_PROXY: bypassed, no_proxy: bypassed };
|
|
31
|
+
}
|
|
1
32
|
export function relayed(target) {
|
|
2
33
|
const url = new URL(target);
|
|
3
|
-
const relay =
|
|
4
|
-
if (relay === undefined
|
|
34
|
+
const relay = relayOrigin();
|
|
35
|
+
if (relay === undefined)
|
|
5
36
|
return url;
|
|
6
37
|
const through = new URL(`${url.host}${url.pathname}${url.search}`, `${relay}/`);
|
|
7
38
|
if (url.protocol === 'wss:' || url.protocol === 'ws:')
|
package/dist/resident.js
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { once } from 'node:events';
|
|
2
2
|
import { watch } from 'node:fs';
|
|
3
3
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { createServer } from 'node:http';
|
|
4
|
+
import { createServer, get } from 'node:http';
|
|
5
5
|
import { dirname, extname, join } from 'node:path';
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { Agents } from './agents.js';
|
|
8
9
|
import { house } from './api.js';
|
|
9
10
|
import { received } from './bridge.js';
|
|
10
|
-
import { Conversations } from './conversations.js';
|
|
11
|
+
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
11
12
|
import { LocalCopies } from './copies.js';
|
|
12
|
-
import {
|
|
13
|
+
import { restart } from './end.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, readEnrolment, restartAsked } from './home.js';
|
|
15
|
+
import { Calls } from './calls.js';
|
|
13
16
|
import { killDescendants } from './scope.js';
|
|
14
17
|
import { quoted } from './shell.js';
|
|
15
18
|
import { SignIns } from './sign-in.js';
|
|
16
19
|
import { placeSkillSet } from './skills.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
20
|
+
import { holdConnection } from './connection.js';
|
|
21
|
+
import { KitUpdate } from './update.js';
|
|
22
|
+
import { WebWizards } from './web-wizard.js';
|
|
19
23
|
import { codexLauncher } from './work.js';
|
|
20
24
|
function report(error) {
|
|
21
25
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -28,26 +32,66 @@ async function installShim(path, module) {
|
|
|
28
32
|
await mkdir(dirname(path), { recursive: true });
|
|
29
33
|
await writeFile(path, `#!/bin/sh\nexec ${quoted(process.execPath)} ${quoted(main)} "$@"\n`, { mode: 0o755 });
|
|
30
34
|
}
|
|
35
|
+
function claimed(lock) {
|
|
36
|
+
try {
|
|
37
|
+
lock.exec('BEGIN EXCLUSIVE');
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.errcode === 5)
|
|
42
|
+
return false;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
31
46
|
async function serveOwner(copies) {
|
|
32
|
-
const
|
|
33
|
-
|
|
47
|
+
const lock = new DatabaseSync(ownerLock());
|
|
48
|
+
if (!claimed(lock)) {
|
|
49
|
+
lock.close();
|
|
50
|
+
const running = await runningResident();
|
|
51
|
+
throw new Error(`${running === null ? 'another Kit process' : `Kit process ${running}`} already runs this Environment from Kit home ${kitHome()}; stop it first to run another Kit process here`);
|
|
52
|
+
}
|
|
34
53
|
const server = createServer(async (request, response) => {
|
|
35
54
|
try {
|
|
36
|
-
|
|
55
|
+
const text = await received(request);
|
|
56
|
+
let answered;
|
|
57
|
+
if (request.url === '/resident') {
|
|
58
|
+
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ pid: process.pid }));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (request.url === '/git/fetch') {
|
|
62
|
+
answered = await copies.fetch();
|
|
63
|
+
}
|
|
64
|
+
else if (request.url === '/git/push') {
|
|
65
|
+
const { cwd, commit } = JSON.parse(text);
|
|
66
|
+
answered = await copies.push(cwd, commit, copies.owner);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
37
69
|
response.writeHead(404).end();
|
|
38
70
|
return;
|
|
39
71
|
}
|
|
40
|
-
|
|
41
|
-
const pushed = await copies.push(cwd, commit, copies.owner);
|
|
42
|
-
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(pushed));
|
|
72
|
+
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(answered));
|
|
43
73
|
}
|
|
44
74
|
catch (error) {
|
|
45
75
|
response.writeHead(502).end(error instanceof Error ? error.message : String(error));
|
|
46
76
|
}
|
|
47
77
|
});
|
|
48
|
-
server.
|
|
78
|
+
server.on('close', () => lock.close());
|
|
79
|
+
await rm(ownerSocket(), { force: true });
|
|
80
|
+
server.listen(ownerSocket());
|
|
49
81
|
await once(server, 'listening');
|
|
50
82
|
}
|
|
83
|
+
function runningResident() {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
get({ socketPath: ownerSocket(), path: '/resident' }, (answer) => {
|
|
86
|
+
received(answer).then((text) => resolve(JSON.parse(text).pid), reject);
|
|
87
|
+
}).on('error', (error) => {
|
|
88
|
+
if (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')
|
|
89
|
+
resolve(null);
|
|
90
|
+
else
|
|
91
|
+
reject(error);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
51
95
|
function connected() {
|
|
52
96
|
return new Promise((resolve) => {
|
|
53
97
|
const watcher = watch(kitHome(), () => {
|
|
@@ -64,44 +108,58 @@ function connected() {
|
|
|
64
108
|
resolve();
|
|
65
109
|
});
|
|
66
110
|
}
|
|
67
|
-
|
|
111
|
+
async function started() {
|
|
68
112
|
await connected();
|
|
113
|
+
await forgetRestart();
|
|
69
114
|
watch(kitHome(), () => {
|
|
70
115
|
if (enrolled())
|
|
71
116
|
return;
|
|
72
117
|
killDescendants();
|
|
73
118
|
process.exit(1);
|
|
74
119
|
});
|
|
75
|
-
await placeSkillSet();
|
|
120
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
76
121
|
await mkdir(agentBase(), { recursive: true });
|
|
77
|
-
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
78
|
-
const agents = new Agents(house);
|
|
79
|
-
const copies = new LocalCopies(house);
|
|
80
|
-
await copies.load();
|
|
81
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
82
123
|
await installShim(codexLauncher(), 'codex-main');
|
|
124
|
+
}
|
|
125
|
+
export async function resident() {
|
|
126
|
+
await mkdir(kitHome(), { recursive: true, mode: 0o700 });
|
|
127
|
+
const copies = new LocalCopies(house);
|
|
128
|
+
await copies.load();
|
|
83
129
|
await serveOwner(copies);
|
|
84
|
-
|
|
130
|
+
await started().catch((error) => {
|
|
131
|
+
report(error);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
135
|
+
const agents = new Agents(house, () => changed());
|
|
136
|
+
let connection = null;
|
|
85
137
|
let reported = false;
|
|
86
|
-
const idle = () => conversations.idle() &&
|
|
138
|
+
const idle = () => calls.idle() && conversations.idle() && agents.idle();
|
|
87
139
|
const changed = () => {
|
|
88
140
|
if (!idle()) {
|
|
89
141
|
reported = false;
|
|
90
142
|
}
|
|
143
|
+
else if (restartAsked() && conversations.quiet()) {
|
|
144
|
+
restart('restarting for an updated CLI');
|
|
145
|
+
}
|
|
91
146
|
else if (!reported) {
|
|
92
147
|
reported = true;
|
|
93
148
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
94
149
|
}
|
|
95
150
|
};
|
|
151
|
+
const calls = new Calls(house, changed);
|
|
96
152
|
const conversations = new Conversations({
|
|
97
153
|
house,
|
|
98
154
|
agents,
|
|
99
155
|
copies,
|
|
100
|
-
|
|
156
|
+
calls,
|
|
157
|
+
send: (notification) => connection?.send(notification) ?? false,
|
|
101
158
|
changed,
|
|
102
159
|
});
|
|
103
|
-
const signIns = new SignIns(house, agents,
|
|
104
|
-
const
|
|
160
|
+
const signIns = new SignIns(house, agents, calls);
|
|
161
|
+
const update = new KitUpdate(calls);
|
|
162
|
+
const wizards = new WebWizards(calls, (notification) => connection?.send(notification) ?? false);
|
|
105
163
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
106
164
|
process.once(signal, () => {
|
|
107
165
|
killDescendants();
|
|
@@ -109,33 +167,37 @@ export async function resident() {
|
|
|
109
167
|
});
|
|
110
168
|
}
|
|
111
169
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
112
|
-
await
|
|
170
|
+
await update.resumed().catch(report);
|
|
113
171
|
logged(agents.refresh());
|
|
114
|
-
|
|
172
|
+
connection = holdConnection({
|
|
115
173
|
opened: () => {
|
|
116
174
|
conversations.opened();
|
|
117
175
|
changed();
|
|
118
|
-
logged(copies.
|
|
176
|
+
logged(copies.connected());
|
|
119
177
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
signIns.start(arrived);
|
|
134
|
-
else if (arrived.kind === 'update')
|
|
135
|
-
updates.start(arrived);
|
|
136
|
-
else
|
|
137
|
-
conversations.input(arrived);
|
|
178
|
+
closed: () => wizards.disconnected(),
|
|
179
|
+
call: (call) => {
|
|
180
|
+
if (call.kind === 'sign_in')
|
|
181
|
+
signIns.start(call);
|
|
182
|
+
else if (call.kind === 'update')
|
|
183
|
+
update.start(call);
|
|
184
|
+
else if (call.kind === 'run_wizard')
|
|
185
|
+
wizards.start(call);
|
|
186
|
+
else if (CONVERSATION_METHODS.has(call.kind))
|
|
187
|
+
conversations.call(call);
|
|
188
|
+
else {
|
|
189
|
+
const refused = { refused: `This Kit does not know ${call.kind}; update the Kit` };
|
|
190
|
+
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
138
191
|
}
|
|
139
192
|
},
|
|
193
|
+
notification: (notification) => {
|
|
194
|
+
if (notification.type === 'watching')
|
|
195
|
+
conversations.watching(notification.watching === true);
|
|
196
|
+
if (notification.type === 'authority_changed')
|
|
197
|
+
logged(copies.changed(notification.rooms));
|
|
198
|
+
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
199
|
+
logged(agents.refresh());
|
|
200
|
+
wizards.notified(notification);
|
|
201
|
+
},
|
|
140
202
|
});
|
|
141
203
|
}
|
package/dist/scope.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
const LISTING_BYTES = 1 << 28;
|
|
5
5
|
const CGROUPS = '/sys/fs/cgroup';
|
|
6
6
|
const GONE = new Set(['ENOENT', 'ESRCH']);
|
|
7
|
+
const EXITED = new Set(['Z', 'X']);
|
|
7
8
|
function failure(error) {
|
|
8
9
|
return error.code ?? '';
|
|
9
10
|
}
|
|
@@ -24,6 +25,8 @@ function read(pid) {
|
|
|
24
25
|
try {
|
|
25
26
|
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
26
27
|
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
28
|
+
if (EXITED.has(fields[0]))
|
|
29
|
+
return null;
|
|
27
30
|
const command = readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\0', ' ');
|
|
28
31
|
return { parent: Number(fields[1]), session: fields[3], command };
|
|
29
32
|
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { kitHome } from './home.js';
|
|
5
|
+
const UNIT = 'house-kit.service';
|
|
6
|
+
// launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
|
|
7
|
+
const MANAGER_MS = 10_000;
|
|
8
|
+
function manage(command, args) {
|
|
9
|
+
return spawnSync(command, args, { encoding: 'utf8', timeout: MANAGER_MS });
|
|
10
|
+
}
|
|
11
|
+
function start(command, args) {
|
|
12
|
+
const started = manage(command, args);
|
|
13
|
+
if (started.status === 0)
|
|
14
|
+
return;
|
|
15
|
+
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
16
|
+
throw new Error(`the Kit service could not be started: ${cause}`);
|
|
17
|
+
}
|
|
18
|
+
function physical(path) {
|
|
19
|
+
return existsSync(path) ? realpathSync(path) : resolve(path);
|
|
20
|
+
}
|
|
21
|
+
function systemdHome(environment) {
|
|
22
|
+
for (const [, quoted, bare] of environment.matchAll(/"((?:[^"\\]|\\.)*)"|(\S+)/g)) {
|
|
23
|
+
const assignment = bare ?? quoted.replace(/\\(.)/g, '$1');
|
|
24
|
+
if (assignment.startsWith('HOUSE_KIT_HOME='))
|
|
25
|
+
return assignment.slice('HOUSE_KIT_HOME='.length);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function runsThisHome(served) {
|
|
30
|
+
return served !== undefined && physical(served) === physical(kitHome());
|
|
31
|
+
}
|
|
32
|
+
export function startService() {
|
|
33
|
+
if (process.platform === 'darwin') {
|
|
34
|
+
const service = `gui/${process.getuid()}/house-kit`;
|
|
35
|
+
const printed = manage('launchctl', ['print', service]);
|
|
36
|
+
if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
|
|
37
|
+
return false;
|
|
38
|
+
if (!runsThisHome(/^\t+HOUSE_KIT_HOME => (.+)$/m.exec(printed.stdout)?.[1]))
|
|
39
|
+
return false;
|
|
40
|
+
start('launchctl', ['kickstart', service]);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
for (const scope of [['--user'], []]) {
|
|
44
|
+
if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
|
|
45
|
+
continue;
|
|
46
|
+
const environment = manage('systemctl', [...scope, 'show', '--property=Environment', '--value', UNIT]).stdout;
|
|
47
|
+
if (!runsThisHome(systemdHome(environment)))
|
|
48
|
+
continue;
|
|
49
|
+
if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
|
|
50
|
+
return false;
|
|
51
|
+
start('systemctl', [...scope, 'start', UNIT]);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
package/dist/sign-in.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import { CLIS
|
|
3
|
+
import { CLIS } from './clis.js';
|
|
4
|
+
import { childEnvironment } from './relay.js';
|
|
4
5
|
import { killTree } from './scope.js';
|
|
5
6
|
import { holdSecretInput } from './secret-input.js';
|
|
6
7
|
const ESCAPE = /\u001b\[[\d;]*m/g;
|
|
@@ -9,72 +10,54 @@ const CODE = /^[A-Z0-9]+-[A-Z0-9]+$/;
|
|
|
9
10
|
function causeOf(error) {
|
|
10
11
|
return error instanceof Error ? error.message : String(error);
|
|
11
12
|
}
|
|
12
|
-
function logged(error) {
|
|
13
|
-
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
14
|
-
}
|
|
15
13
|
function endOf(signal) {
|
|
16
14
|
return new Promise((resolve) => signal.addEventListener('abort', () => resolve(true), { once: true }));
|
|
17
15
|
}
|
|
18
16
|
export class SignIns {
|
|
19
17
|
house;
|
|
20
18
|
agents;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(house, agents, changed) {
|
|
19
|
+
calls;
|
|
20
|
+
constructor(house, agents, calls) {
|
|
24
21
|
this.house = house;
|
|
25
22
|
this.agents = agents;
|
|
26
|
-
this.
|
|
27
|
-
}
|
|
28
|
-
idle() {
|
|
29
|
-
return this.running.size === 0;
|
|
23
|
+
this.calls = calls;
|
|
30
24
|
}
|
|
31
|
-
start(
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
this.running.add(input.input_id);
|
|
35
|
-
this.changed();
|
|
36
|
-
this.signIn(input)
|
|
37
|
-
.catch(logged)
|
|
38
|
-
.finally(() => {
|
|
39
|
-
this.running.delete(input.input_id);
|
|
40
|
-
this.changed();
|
|
41
|
-
});
|
|
25
|
+
start(call) {
|
|
26
|
+
void this.calls.carry(call.input_id, () => this.signIn(call));
|
|
42
27
|
}
|
|
43
|
-
async signIn(
|
|
28
|
+
async signIn(call) {
|
|
44
29
|
const ending = new AbortController();
|
|
45
30
|
// House stops waiting for a sign-in ten minutes after it wrote it and sends the seconds left, so its install and login end with the page.
|
|
46
|
-
const timer = setTimeout(() => ending.abort(),
|
|
47
|
-
const ended = await Promise.race([this.signedIn(
|
|
31
|
+
const timer = setTimeout(() => ending.abort(), call.ends_in * 1000);
|
|
32
|
+
const ended = await Promise.race([this.signedIn(call, ending.signal), endOf(ending.signal)]);
|
|
48
33
|
clearTimeout(timer);
|
|
49
34
|
if (ended)
|
|
50
|
-
process.stderr.write(`kit: the ${
|
|
51
|
-
|
|
52
|
-
if (!ended)
|
|
53
|
-
await this.agents.reread(input.cli);
|
|
35
|
+
process.stderr.write(`kit: the ${call.cli} sign-in reached the end House set for it\n`);
|
|
36
|
+
return ended ? { result: {} } : { result: {}, returned: () => this.agents.reread(call.cli) };
|
|
54
37
|
}
|
|
55
|
-
async signedIn(
|
|
56
|
-
await this.login(
|
|
38
|
+
async signedIn(call, signal) {
|
|
39
|
+
await this.login(call, signal);
|
|
57
40
|
if (!signal.aborted)
|
|
58
|
-
await this.agents.recheck(
|
|
41
|
+
await this.agents.recheck(call.cli, signal);
|
|
59
42
|
return false;
|
|
60
43
|
}
|
|
61
|
-
async login(
|
|
44
|
+
async login(call, signal) {
|
|
62
45
|
let cli;
|
|
63
46
|
try {
|
|
64
|
-
cli = await this.agents.located(
|
|
47
|
+
cli = await this.agents.located(call.cli, signal);
|
|
65
48
|
}
|
|
66
49
|
catch (error) {
|
|
67
50
|
if (!signal.aborted)
|
|
68
|
-
process.stderr.write(`kit: the ${
|
|
51
|
+
process.stderr.write(`kit: the ${call.cli} sign-in failed: ${causeOf(error)}\n`);
|
|
69
52
|
return;
|
|
70
53
|
}
|
|
71
54
|
if (signal.aborted)
|
|
72
55
|
return;
|
|
73
|
-
const login = CLIS[
|
|
56
|
+
const login = CLIS[call.cli].login;
|
|
74
57
|
const child = spawn(cli, login.args, {
|
|
75
58
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
76
59
|
detached: true,
|
|
77
|
-
env:
|
|
60
|
+
env: childEnvironment(),
|
|
78
61
|
});
|
|
79
62
|
const running = () => child.exitCode === null && child.signalCode === null;
|
|
80
63
|
const stop = () => killTree(child);
|
|
@@ -102,7 +85,7 @@ export class SignIns {
|
|
|
102
85
|
return;
|
|
103
86
|
held = true;
|
|
104
87
|
const steps = [{ kind: 'visit', label: 'Open this link and sign in', url: link }, step];
|
|
105
|
-
void holdSecretInput(this.house,
|
|
88
|
+
void holdSecretInput(this.house, call.input_id, steps, running).then((content) => {
|
|
106
89
|
if (!running())
|
|
107
90
|
return;
|
|
108
91
|
if (content === null)
|
|
@@ -119,6 +102,6 @@ export class SignIns {
|
|
|
119
102
|
});
|
|
120
103
|
signal.removeEventListener('abort', stop);
|
|
121
104
|
if (failed !== null && !signal.aborted)
|
|
122
|
-
process.stderr.write(`kit: the ${
|
|
105
|
+
process.stderr.write(`kit: the ${call.cli} sign-in failed: ${failed}\n`);
|
|
123
106
|
}
|
|
124
107
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cp, lstat, mkdir, readdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { cp, lstat, mkdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
|
3
2
|
import { homedir } from 'node:os';
|
|
4
3
|
import { join, relative } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { GUIDE, listSkills } from '@agentshouse/skills';
|
|
5
|
+
import { parse } from 'yaml';
|
|
6
|
+
import { KIT_RELEASE } from './clis.js';
|
|
7
7
|
import { kitHome, readConfiguration, readHome, writeHome } from './home.js';
|
|
8
|
-
import { refusalOf } from './refusals.js';
|
|
9
|
-
export const HOW_WE_WORK = '/rooms/private/how-we-work.md';
|
|
10
|
-
const SKILL_SET = fileURLToPath(new URL('../skills/', import.meta.url));
|
|
11
|
-
const HOW_WE_WORK_TEXT = readFileSync(new URL('../how-we-work.md', import.meta.url), 'utf8');
|
|
12
8
|
const RECORD = 'skill-set.json';
|
|
13
|
-
const
|
|
14
|
-
const
|
|
9
|
+
const ENTRY = 'SKILL.md';
|
|
10
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---\n/u;
|
|
11
|
+
const ORIGIN = '<origin>';
|
|
15
12
|
function skillsDirectory() {
|
|
16
13
|
return join(homedir(), '.agents', 'skills');
|
|
17
14
|
}
|
|
@@ -29,50 +26,67 @@ async function present(path) {
|
|
|
29
26
|
throw error;
|
|
30
27
|
}
|
|
31
28
|
}
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
function skillName(text) {
|
|
30
|
+
const front = FRONTMATTER.exec(text);
|
|
31
|
+
const fields = (front === null ? null : parse(front[1]));
|
|
32
|
+
return typeof fields?.name === 'string' && typeof fields.description === 'string' ? fields.name : undefined;
|
|
33
|
+
}
|
|
34
|
+
function skillSet(origin) {
|
|
35
|
+
const upstream = listSkills().map(({ name, directory }) => ({
|
|
36
|
+
name,
|
|
37
|
+
place: (destination) => cp(directory, destination, { recursive: true }),
|
|
38
|
+
}));
|
|
39
|
+
const pages = GUIDE.flatMap(({ text }) => {
|
|
40
|
+
const name = skillName(text);
|
|
41
|
+
if (name === undefined)
|
|
42
|
+
return [];
|
|
43
|
+
const place = async (destination) => {
|
|
44
|
+
await mkdir(destination);
|
|
45
|
+
await writeFile(join(destination, ENTRY), text.replaceAll(ORIGIN, origin));
|
|
46
|
+
};
|
|
47
|
+
return [{ name, place }];
|
|
48
|
+
});
|
|
49
|
+
return [...upstream, ...pages].sort((left, right) => (left.name < right.name ? -1 : 1));
|
|
50
|
+
}
|
|
51
|
+
async function install(origin) {
|
|
52
|
+
const placed = [];
|
|
53
|
+
for (const skill of skillSet(origin)) {
|
|
54
|
+
if (!(await present(join(skillsDirectory(), skill.name))) && !(await present(join(claudeDirectory(), skill.name)))) {
|
|
55
|
+
placed.push(skill);
|
|
37
56
|
}
|
|
38
57
|
}
|
|
58
|
+
const names = placed.map((skill) => skill.name);
|
|
39
59
|
await writeHome(RECORD, { names });
|
|
40
60
|
await mkdir(skillsDirectory(), { recursive: true });
|
|
41
61
|
await mkdir(claudeDirectory(), { recursive: true });
|
|
42
62
|
const skills = await realpath(skillsDirectory());
|
|
43
63
|
const claude = await realpath(claudeDirectory());
|
|
44
|
-
for (const name of
|
|
45
|
-
await
|
|
64
|
+
for (const { name, place } of placed) {
|
|
65
|
+
await place(join(skillsDirectory(), name));
|
|
46
66
|
if (claude !== skills)
|
|
47
67
|
await symlink(relative(claude, join(skills, name)), join(claudeDirectory(), name));
|
|
48
68
|
}
|
|
49
|
-
await writeHome(RECORD, { version:
|
|
69
|
+
await writeHome(RECORD, { version: KIT_RELEASE, origin, names });
|
|
70
|
+
}
|
|
71
|
+
export async function placeSkillSet(origin) {
|
|
72
|
+
try {
|
|
73
|
+
await replaceSkillSet(origin);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
77
|
+
}
|
|
50
78
|
}
|
|
51
|
-
|
|
79
|
+
async function replaceSkillSet(origin) {
|
|
52
80
|
const accepted = (await readConfiguration()).skills;
|
|
53
81
|
const installed = await readHome(RECORD);
|
|
54
|
-
if (accepted && installed?.version ===
|
|
82
|
+
if (accepted && installed?.version === KIT_RELEASE && installed.origin === origin)
|
|
55
83
|
return;
|
|
56
84
|
for (const name of installed?.names ?? []) {
|
|
57
85
|
await rm(join(claudeDirectory(), name), { recursive: true, force: true });
|
|
58
86
|
await rm(join(skillsDirectory(), name), { recursive: true, force: true });
|
|
59
87
|
}
|
|
60
88
|
if (accepted)
|
|
61
|
-
await install();
|
|
89
|
+
await install(origin);
|
|
62
90
|
else
|
|
63
91
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
64
92
|
}
|
|
65
|
-
export async function createHowWeWork(bridge) {
|
|
66
|
-
if ((await readHome(RECORD)) === null || (await present(join(kitHome(), CREATED))))
|
|
67
|
-
return;
|
|
68
|
-
const created = await bridge.tool('edit', { changes: [{ op: 'create', path: HOW_WE_WORK, content: HOW_WE_WORK_TEXT }] });
|
|
69
|
-
if (created.isError === true) {
|
|
70
|
-
const text = created.content.map((content) => content.text).join('\n');
|
|
71
|
-
const refusal = refusalOf(text);
|
|
72
|
-
if (UNWRITABLE.has(refusal?.code ?? ''))
|
|
73
|
-
return;
|
|
74
|
-
if (refusal?.code !== 'edit_conflict' || !refusal.conflicts.includes('path_exists'))
|
|
75
|
-
throw new Error(text);
|
|
76
|
-
}
|
|
77
|
-
await writeFile(join(kitHome(), CREATED), '');
|
|
78
|
-
}
|