@agentshouse/kit 0.2.1-c2997cbe74597 → 0.2.1-c2eef9f27f3ab
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 +28 -28
- package/dist/acp.js +38 -17
- package/dist/agents.js +52 -19
- package/dist/api.js +5 -5
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +4 -7
- package/dist/building.js +46 -0
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +1 -1
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +45 -13
- package/dist/{stream.js → connection.js} +33 -30
- package/dist/conversations.js +394 -153
- package/dist/copies.js +77 -50
- package/dist/end.js +9 -4
- package/dist/events.js +6 -2
- package/dist/files.js +139 -37
- package/dist/git.js +5 -6
- package/dist/home.js +27 -13
- package/dist/house-cli.js +96 -88
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +28 -7
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +15 -8
- package/dist/logout.js +0 -2
- package/dist/mcp.js +4 -2
- package/dist/placement.js +9 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +88 -37
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +22 -39
- package/dist/skills.js +12 -93
- package/dist/transcription.js +83 -43
- package/dist/translate.js +2 -8
- package/dist/update.js +71 -48
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +19 -3
- package/dist/work.js +3 -5
- package/package.json +16 -17
- package/dist/bootstrap.json +0 -24
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- 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 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- 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/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- 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 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- 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 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- 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 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- 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 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- 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 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/resident.js
CHANGED
|
@@ -1,22 +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 {
|
|
11
|
+
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
11
12
|
import { LocalCopies } from './copies.js';
|
|
12
13
|
import { restart } from './end.js';
|
|
13
|
-
import { agentBase, enrolled, forgetRestart, kitHome, restartAsked } from './home.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
15
|
+
import { Calls } from './calls.js';
|
|
14
16
|
import { killDescendants } from './scope.js';
|
|
15
17
|
import { quoted } from './shell.js';
|
|
16
18
|
import { SignIns } from './sign-in.js';
|
|
17
19
|
import { placeSkillSet } from './skills.js';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
+
import { holdConnection } from './connection.js';
|
|
21
|
+
import { KitUpdate } from './update.js';
|
|
22
|
+
import { WebWizards } from './web-wizard.js';
|
|
20
23
|
import { codexLauncher } from './work.js';
|
|
21
24
|
function report(error) {
|
|
22
25
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -29,13 +32,32 @@ async function installShim(path, module) {
|
|
|
29
32
|
await mkdir(dirname(path), { recursive: true });
|
|
30
33
|
await writeFile(path, `#!/bin/sh\nexec ${quoted(process.execPath)} ${quoted(main)} "$@"\n`, { mode: 0o755 });
|
|
31
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
|
+
}
|
|
32
46
|
async function serveOwner(copies) {
|
|
33
|
-
const
|
|
34
|
-
|
|
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
|
+
}
|
|
35
53
|
const server = createServer(async (request, response) => {
|
|
36
54
|
try {
|
|
37
55
|
const text = await received(request);
|
|
38
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
|
+
}
|
|
39
61
|
if (request.url === '/git/fetch') {
|
|
40
62
|
answered = await copies.fetch();
|
|
41
63
|
}
|
|
@@ -53,9 +75,23 @@ async function serveOwner(copies) {
|
|
|
53
75
|
response.writeHead(502).end(error instanceof Error ? error.message : String(error));
|
|
54
76
|
}
|
|
55
77
|
});
|
|
56
|
-
server.
|
|
78
|
+
server.on('close', () => lock.close());
|
|
79
|
+
await rm(ownerSocket(), { force: true });
|
|
80
|
+
server.listen(ownerSocket());
|
|
57
81
|
await once(server, 'listening');
|
|
58
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
|
+
}
|
|
59
95
|
function connected() {
|
|
60
96
|
return new Promise((resolve) => {
|
|
61
97
|
const watcher = watch(kitHome(), () => {
|
|
@@ -72,7 +108,7 @@ function connected() {
|
|
|
72
108
|
resolve();
|
|
73
109
|
});
|
|
74
110
|
}
|
|
75
|
-
|
|
111
|
+
async function started() {
|
|
76
112
|
await connected();
|
|
77
113
|
await forgetRestart();
|
|
78
114
|
watch(kitHome(), () => {
|
|
@@ -83,16 +119,23 @@ export async function resident() {
|
|
|
83
119
|
});
|
|
84
120
|
await placeSkillSet();
|
|
85
121
|
await mkdir(agentBase(), { recursive: true });
|
|
86
|
-
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
87
|
-
const agents = new Agents(house, () => changed());
|
|
88
|
-
const copies = new LocalCopies(house);
|
|
89
|
-
await copies.load();
|
|
90
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
91
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();
|
|
92
129
|
await serveOwner(copies);
|
|
93
|
-
|
|
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;
|
|
94
137
|
let reported = false;
|
|
95
|
-
const idle = () =>
|
|
138
|
+
const idle = () => calls.idle() && conversations.idle() && agents.idle();
|
|
96
139
|
const changed = () => {
|
|
97
140
|
if (!idle()) {
|
|
98
141
|
reported = false;
|
|
@@ -105,15 +148,18 @@ export async function resident() {
|
|
|
105
148
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
106
149
|
}
|
|
107
150
|
};
|
|
151
|
+
const calls = new Calls(house, changed);
|
|
108
152
|
const conversations = new Conversations({
|
|
109
153
|
house,
|
|
110
154
|
agents,
|
|
111
155
|
copies,
|
|
112
|
-
|
|
156
|
+
calls,
|
|
157
|
+
send: (notification) => connection?.send(notification) ?? false,
|
|
113
158
|
changed,
|
|
114
159
|
});
|
|
115
|
-
const signIns = new SignIns(house, agents,
|
|
116
|
-
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);
|
|
117
163
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
118
164
|
process.once(signal, () => {
|
|
119
165
|
killDescendants();
|
|
@@ -121,32 +167,37 @@ export async function resident() {
|
|
|
121
167
|
});
|
|
122
168
|
}
|
|
123
169
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
124
|
-
await
|
|
170
|
+
await update.resumed().catch(report);
|
|
125
171
|
logged(agents.refresh());
|
|
126
|
-
|
|
172
|
+
connection = holdConnection({
|
|
127
173
|
opened: () => {
|
|
128
174
|
conversations.opened();
|
|
129
175
|
changed();
|
|
130
176
|
logged(copies.connected());
|
|
131
177
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
else {
|
|
146
|
-
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
147
|
-
logged(house.deliver(`/kit/inputs/${arrived.input_id}/ack`, refused).finally(changed));
|
|
148
|
-
}
|
|
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 }));
|
|
149
191
|
}
|
|
150
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
|
+
},
|
|
151
202
|
});
|
|
152
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
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
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';
|
|
2
5
|
const UNIT = 'house-kit.service';
|
|
3
6
|
// launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
|
|
4
7
|
const MANAGER_MS = 10_000;
|
|
@@ -10,7 +13,21 @@ function start(command, args) {
|
|
|
10
13
|
if (started.status === 0)
|
|
11
14
|
return;
|
|
12
15
|
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
13
|
-
throw new Error(`the
|
|
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());
|
|
14
31
|
}
|
|
15
32
|
export function startService() {
|
|
16
33
|
if (process.platform === 'darwin') {
|
|
@@ -18,12 +35,17 @@ export function startService() {
|
|
|
18
35
|
const printed = manage('launchctl', ['print', service]);
|
|
19
36
|
if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
|
|
20
37
|
return false;
|
|
38
|
+
if (!runsThisHome(/^\t+HOUSE_KIT_HOME => (.+)$/m.exec(printed.stdout)?.[1]))
|
|
39
|
+
return false;
|
|
21
40
|
start('launchctl', ['kickstart', service]);
|
|
22
41
|
return true;
|
|
23
42
|
}
|
|
24
43
|
for (const scope of [['--user'], []]) {
|
|
25
44
|
if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
|
|
26
45
|
continue;
|
|
46
|
+
const environment = manage('systemctl', [...scope, 'show', '--property=Environment', '--value', UNIT]).stdout;
|
|
47
|
+
if (!runsThisHome(systemdHome(environment)))
|
|
48
|
+
continue;
|
|
27
49
|
if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
|
|
28
50
|
return false;
|
|
29
51
|
start('systemctl', [...scope, 'start', UNIT]);
|
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,31 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { cp, lstat, mkdir, readdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
1
|
+
import { cp, lstat, mkdir, realpath, rm, symlink } from 'node:fs/promises';
|
|
4
2
|
import { homedir } from 'node:os';
|
|
5
3
|
import { join, relative } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { KIT_VERSION } from './clis.js';
|
|
4
|
+
import { listSkills } from '@agentshouse/skills';
|
|
5
|
+
import { KIT_RELEASE } from './clis.js';
|
|
9
6
|
import { kitHome, readConfiguration, readHome, writeHome } from './home.js';
|
|
10
|
-
import { refusalOf } from './refusals.js';
|
|
11
|
-
const HOW_WE_WORK = '/rooms/private/how-we-work.md';
|
|
12
|
-
const SKILL_SET = fileURLToPath(new URL('../skills/', import.meta.url));
|
|
13
|
-
const HOW_WE_WORK_TEXT = readFileSync(new URL('../how-we-work.md', import.meta.url), 'utf8');
|
|
14
|
-
const SHIPPED_HOW_WE_WORK = [
|
|
15
|
-
'ae4dda60da7b174f22dc5fa9bf07fc27c25864932a74230de4f9f509ddd91f41',
|
|
16
|
-
'0bdcd43e1603ba6673a3cae56d70bbf90657cbeefd124e91a47be2ca1cbd6c93',
|
|
17
|
-
'4106a87ec66da6f7b2a10e74d18e6559d68d5f4fe7415dae68f5c7bdb9081968',
|
|
18
|
-
'ac814a2197038f037b657bb719340395f89f319ac9fcdd386629c3fe2cec68f1',
|
|
19
|
-
'29951ee394aad9979eecfed3e5dc32c2c86e2226b6a380db5dbd4f305f4d1156',
|
|
20
|
-
];
|
|
21
7
|
const RECORD = 'skill-set.json';
|
|
22
|
-
const WRITTEN = 'how-we-work.json';
|
|
23
|
-
const UNWRITABLE = new Set(['room_not_found', 'operation_denied']);
|
|
24
|
-
const PRESENT = new Set(['path_exists']);
|
|
25
|
-
const CHANGED = new Set(['base_revision_stale', 'path_absent']);
|
|
26
|
-
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
27
|
-
const FAILED = /^exit: [1-9]\d*$/;
|
|
28
|
-
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
29
8
|
function skillsDirectory() {
|
|
30
9
|
return join(homedir(), '.agents', 'skills');
|
|
31
10
|
}
|
|
@@ -44,23 +23,24 @@ async function present(path) {
|
|
|
44
23
|
}
|
|
45
24
|
}
|
|
46
25
|
async function install() {
|
|
47
|
-
const
|
|
48
|
-
for (const
|
|
49
|
-
if (!(await present(join(skillsDirectory(), name))) && !(await present(join(claudeDirectory(), name)))) {
|
|
50
|
-
|
|
26
|
+
const placed = [];
|
|
27
|
+
for (const skill of listSkills()) {
|
|
28
|
+
if (!(await present(join(skillsDirectory(), skill.name))) && !(await present(join(claudeDirectory(), skill.name)))) {
|
|
29
|
+
placed.push(skill);
|
|
51
30
|
}
|
|
52
31
|
}
|
|
32
|
+
const names = placed.map((skill) => skill.name);
|
|
53
33
|
await writeHome(RECORD, { names });
|
|
54
34
|
await mkdir(skillsDirectory(), { recursive: true });
|
|
55
35
|
await mkdir(claudeDirectory(), { recursive: true });
|
|
56
36
|
const skills = await realpath(skillsDirectory());
|
|
57
37
|
const claude = await realpath(claudeDirectory());
|
|
58
|
-
for (const name of
|
|
59
|
-
await cp(
|
|
38
|
+
for (const { name, directory } of placed) {
|
|
39
|
+
await cp(directory, join(skillsDirectory(), name), { recursive: true });
|
|
60
40
|
if (claude !== skills)
|
|
61
41
|
await symlink(relative(claude, join(skills, name)), join(claudeDirectory(), name));
|
|
62
42
|
}
|
|
63
|
-
await writeHome(RECORD, { version:
|
|
43
|
+
await writeHome(RECORD, { version: KIT_RELEASE, names });
|
|
64
44
|
}
|
|
65
45
|
export async function placeSkillSet() {
|
|
66
46
|
try {
|
|
@@ -73,7 +53,7 @@ export async function placeSkillSet() {
|
|
|
73
53
|
async function replaceSkillSet() {
|
|
74
54
|
const accepted = (await readConfiguration()).skills;
|
|
75
55
|
const installed = await readHome(RECORD);
|
|
76
|
-
if (accepted && installed?.version ===
|
|
56
|
+
if (accepted && installed?.version === KIT_RELEASE)
|
|
77
57
|
return;
|
|
78
58
|
for (const name of installed?.names ?? []) {
|
|
79
59
|
await rm(join(claudeDirectory(), name), { recursive: true, force: true });
|
|
@@ -84,64 +64,3 @@ async function replaceSkillSet() {
|
|
|
84
64
|
else
|
|
85
65
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
86
66
|
}
|
|
87
|
-
function sha256(text) {
|
|
88
|
-
return createHash('sha256').update(text).digest('hex');
|
|
89
|
-
}
|
|
90
|
-
function cutOf(lines) {
|
|
91
|
-
const cut = CUT.exec(lines[0] ?? '');
|
|
92
|
-
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
93
|
-
}
|
|
94
|
-
export async function readHowWeWork(bridge) {
|
|
95
|
-
let document = '';
|
|
96
|
-
for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
|
|
97
|
-
const read = await bridge.tool('shell', { command });
|
|
98
|
-
const text = read.content.map((content) => content.text).join('\n');
|
|
99
|
-
const lines = text.split('\n');
|
|
100
|
-
if (read.isError === true || (FAILED.test(lines[0] ?? '') && lines[1]?.startsWith('stderr: '))) {
|
|
101
|
-
const refusal = read.isError === true ? text : lines.slice(1).join('\n').replace(/^stderr: /gm, '');
|
|
102
|
-
if (UNREADABLE.test(refusal))
|
|
103
|
-
return '';
|
|
104
|
-
throw new Error(refusal);
|
|
105
|
-
}
|
|
106
|
-
const cut = cutOf(lines);
|
|
107
|
-
document += lines.slice(cut === null ? 0 : 1).join('\n');
|
|
108
|
-
command = cut?.[2];
|
|
109
|
-
}
|
|
110
|
-
return document;
|
|
111
|
-
}
|
|
112
|
-
async function currentRevision(bridge) {
|
|
113
|
-
const inspected = await bridge.tool('inspect', { path: HOW_WE_WORK });
|
|
114
|
-
const text = inspected.content.map((content) => content.text).join('\n');
|
|
115
|
-
if (inspected.isError === true)
|
|
116
|
-
throw new Error(text);
|
|
117
|
-
return parse(text).revisions[0].revision;
|
|
118
|
-
}
|
|
119
|
-
async function edit(bridge, change, kept) {
|
|
120
|
-
const edited = await bridge.tool('edit', { changes: [{ ...change, path: HOW_WE_WORK, content: HOW_WE_WORK_TEXT }] });
|
|
121
|
-
if (edited.isError !== true)
|
|
122
|
-
return 'written';
|
|
123
|
-
const text = edited.content.map((content) => content.text).join('\n');
|
|
124
|
-
const refusal = refusalOf(text);
|
|
125
|
-
if (UNWRITABLE.has(refusal?.code ?? ''))
|
|
126
|
-
return 'unwritable';
|
|
127
|
-
if (refusal?.code === 'edit_conflict' && refusal.conflicts.some((conflict) => kept.has(conflict)))
|
|
128
|
-
return 'kept';
|
|
129
|
-
throw new Error(text);
|
|
130
|
-
}
|
|
131
|
-
export async function createHowWeWork(bridge) {
|
|
132
|
-
if (!(await readConfiguration()).skills || (await readHome(RECORD))?.version === undefined)
|
|
133
|
-
return;
|
|
134
|
-
const current = sha256(HOW_WE_WORK_TEXT);
|
|
135
|
-
if ((await readHome(WRITTEN)) === current)
|
|
136
|
-
return;
|
|
137
|
-
const created = await edit(bridge, { op: 'create' }, PRESENT);
|
|
138
|
-
if (created === 'unwritable')
|
|
139
|
-
return;
|
|
140
|
-
if (created === 'kept') {
|
|
141
|
-
const base = await currentRevision(bridge);
|
|
142
|
-
const held = sha256(await readHowWeWork(bridge));
|
|
143
|
-
if (held !== current && SHIPPED_HOW_WE_WORK.includes(held) && (await edit(bridge, { op: 'replace', base }, CHANGED)) === 'unwritable')
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
await writeHome(WRITTEN, current);
|
|
147
|
-
}
|