@agentshouse/kit 0.2.1-c845e172df1af → 0.2.1-c849d8d4ab62f
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 +6 -6
- package/dist/acp.js +57 -6
- package/dist/agents.js +6 -9
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +39 -15
- package/dist/connection.js +20 -29
- package/dist/conversations.js +235 -81
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +53 -40
- package/dist/git.js +2 -2
- package/dist/house-cli.js +31 -59
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -6
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +41 -96
- package/dist/transcription.js +83 -43
- package/dist/update.js +51 -8
- package/package.json +19 -20
- 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
|
@@ -10,10 +10,11 @@ import { house } from './api.js';
|
|
|
10
10
|
import { received } from './bridge.js';
|
|
11
11
|
import { CONVERSATION_METHODS, Conversations } from './conversations.js';
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
|
-
import { restart } from './end.js';
|
|
14
|
-
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
13
|
+
import { restart, unknownMethod } from './end.js';
|
|
14
|
+
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, readEnrolment, restartAsked } from './home.js';
|
|
15
15
|
import { Calls } from './calls.js';
|
|
16
16
|
import { killDescendants } from './scope.js';
|
|
17
|
+
import { removeInputs, Scripts } from './scripts.js';
|
|
17
18
|
import { quoted } from './shell.js';
|
|
18
19
|
import { SignIns } from './sign-in.js';
|
|
19
20
|
import { placeSkillSet } from './skills.js';
|
|
@@ -116,7 +117,7 @@ async function started() {
|
|
|
116
117
|
killDescendants();
|
|
117
118
|
process.exit(1);
|
|
118
119
|
});
|
|
119
|
-
await placeSkillSet();
|
|
120
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
120
121
|
await mkdir(agentBase(), { recursive: true });
|
|
121
122
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
122
123
|
await installShim(codexLauncher(), 'codex-main');
|
|
@@ -142,6 +143,9 @@ export async function resident() {
|
|
|
142
143
|
else if (restartAsked() && conversations.quiet()) {
|
|
143
144
|
restart('restarting for an updated CLI');
|
|
144
145
|
}
|
|
146
|
+
else if (update.held() && conversations.quiet()) {
|
|
147
|
+
update.apply(connection.pause());
|
|
148
|
+
}
|
|
145
149
|
else if (!reported) {
|
|
146
150
|
reported = true;
|
|
147
151
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
@@ -157,10 +161,13 @@ export async function resident() {
|
|
|
157
161
|
changed,
|
|
158
162
|
});
|
|
159
163
|
const signIns = new SignIns(house, agents, calls);
|
|
160
|
-
const update = new KitUpdate(calls);
|
|
164
|
+
const update = new KitUpdate(calls, changed);
|
|
165
|
+
removeInputs();
|
|
166
|
+
const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
|
|
161
167
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
162
168
|
process.once(signal, () => {
|
|
163
169
|
killDescendants();
|
|
170
|
+
removeInputs();
|
|
164
171
|
process.kill(process.pid, signal);
|
|
165
172
|
});
|
|
166
173
|
}
|
|
@@ -173,15 +180,18 @@ export async function resident() {
|
|
|
173
180
|
changed();
|
|
174
181
|
logged(copies.connected());
|
|
175
182
|
},
|
|
183
|
+
closed: () => scripts.disconnected(),
|
|
176
184
|
call: (call) => {
|
|
177
185
|
if (call.kind === 'sign_in')
|
|
178
186
|
signIns.start(call);
|
|
179
187
|
else if (call.kind === 'update')
|
|
180
|
-
update.
|
|
188
|
+
update.hold(call);
|
|
189
|
+
else if (call.kind === 'run_wizard' || call.kind === 'run_function')
|
|
190
|
+
scripts.start(call);
|
|
181
191
|
else if (CONVERSATION_METHODS.has(call.kind))
|
|
182
192
|
conversations.call(call);
|
|
183
193
|
else {
|
|
184
|
-
const refused = { refused:
|
|
194
|
+
const refused = { refused: unknownMethod(call.kind) };
|
|
185
195
|
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
186
196
|
}
|
|
187
197
|
},
|
|
@@ -192,6 +202,7 @@ export async function resident() {
|
|
|
192
202
|
logged(copies.changed(notification.rooms));
|
|
193
203
|
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
194
204
|
logged(agents.refresh());
|
|
205
|
+
scripts.notified(notification);
|
|
195
206
|
},
|
|
196
207
|
});
|
|
197
208
|
}
|
package/dist/scripts.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { spawn as spawnPiped } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { once } from 'node:events';
|
|
4
|
+
import { rmSync } from 'node:fs';
|
|
5
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { constants } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { loginPath, onLoginPath } from './clis.js';
|
|
9
|
+
import { kitHome } from './home.js';
|
|
10
|
+
import { childEnvironment } from './relay.js';
|
|
11
|
+
import { killTree } from './scope.js';
|
|
12
|
+
const TERMINAL = 'xterm-256color';
|
|
13
|
+
const FIRST_SIZE = { cols: 80, rows: 24 };
|
|
14
|
+
const SIGNALLED = 128;
|
|
15
|
+
const NEWLINE = 0x0a;
|
|
16
|
+
const NO_BASH = "bash is not on the login shell's PATH";
|
|
17
|
+
const LOST = { refused: 'the Kit lost its connection to House' };
|
|
18
|
+
function causeOf(error) {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
function failed(plan, cause) {
|
|
22
|
+
process.stderr.write(`kit: ${plan.named} could not start: ${cause}\n`);
|
|
23
|
+
return { refused: `the script could not start: ${cause}` };
|
|
24
|
+
}
|
|
25
|
+
function planned(call) {
|
|
26
|
+
if (call.kind === 'run_wizard')
|
|
27
|
+
return { named: 'a Web Wizard', script: call.script, terminal: 'page', function: null };
|
|
28
|
+
return {
|
|
29
|
+
named: 'an Environment function',
|
|
30
|
+
script: call.script,
|
|
31
|
+
terminal: call.terminal ? 'at once' : null,
|
|
32
|
+
function: { input: call.input, timeCap: call.time_cap, outputCap: call.output_cap },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function inputs() {
|
|
36
|
+
return join(kitHome(), 'inputs');
|
|
37
|
+
}
|
|
38
|
+
async function inputFile(value) {
|
|
39
|
+
await mkdir(inputs(), { recursive: true, mode: 0o700 });
|
|
40
|
+
const path = join(inputs(), `${randomUUID()}.json`);
|
|
41
|
+
await writeFile(path, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
export function removeInputs() {
|
|
45
|
+
rmSync(inputs(), { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
function struck(line, typed) {
|
|
48
|
+
return typed.some((typedLine) => line.includes(typedLine));
|
|
49
|
+
}
|
|
50
|
+
class KeptOutput {
|
|
51
|
+
cap;
|
|
52
|
+
typedText = '';
|
|
53
|
+
text = '';
|
|
54
|
+
bytes = 0;
|
|
55
|
+
line = '';
|
|
56
|
+
lineBytes = 0;
|
|
57
|
+
dropping = false;
|
|
58
|
+
cut = false;
|
|
59
|
+
constructor(cap) {
|
|
60
|
+
this.cap = cap;
|
|
61
|
+
}
|
|
62
|
+
typed(data) {
|
|
63
|
+
this.typedText += data;
|
|
64
|
+
}
|
|
65
|
+
add(data) {
|
|
66
|
+
const pieces = data.split('\n');
|
|
67
|
+
const rest = pieces.pop();
|
|
68
|
+
for (const piece of pieces)
|
|
69
|
+
this.ended(piece);
|
|
70
|
+
this.extend(rest);
|
|
71
|
+
}
|
|
72
|
+
kept() {
|
|
73
|
+
const typed = this.typedLines(true);
|
|
74
|
+
this.text = [...this.text.split(/(?<=\n)/), this.line].filter((line) => line !== '' && !struck(line, typed)).join('');
|
|
75
|
+
this.bytes = Buffer.byteLength(this.text);
|
|
76
|
+
this.trim();
|
|
77
|
+
return { output: this.text, cut: this.cut };
|
|
78
|
+
}
|
|
79
|
+
typedLines(all) {
|
|
80
|
+
const lines = this.typedText.split(/\r\n|\r|\n/);
|
|
81
|
+
if (!all)
|
|
82
|
+
lines.pop();
|
|
83
|
+
return lines.filter((line) => line !== '');
|
|
84
|
+
}
|
|
85
|
+
ended(piece) {
|
|
86
|
+
const line = `${this.line}${piece}\n`;
|
|
87
|
+
const dropped = this.dropping;
|
|
88
|
+
this.line = '';
|
|
89
|
+
this.lineBytes = 0;
|
|
90
|
+
this.dropping = false;
|
|
91
|
+
if (dropped || struck(line, this.typedLines(false)))
|
|
92
|
+
return;
|
|
93
|
+
this.text += line;
|
|
94
|
+
this.bytes += Buffer.byteLength(line);
|
|
95
|
+
if (this.bytes > 2 * this.cap)
|
|
96
|
+
this.trim();
|
|
97
|
+
}
|
|
98
|
+
extend(piece) {
|
|
99
|
+
if (this.dropping)
|
|
100
|
+
return;
|
|
101
|
+
this.line += piece;
|
|
102
|
+
this.lineBytes += Buffer.byteLength(piece);
|
|
103
|
+
if (this.lineBytes <= this.cap)
|
|
104
|
+
return;
|
|
105
|
+
this.text = '';
|
|
106
|
+
this.bytes = 0;
|
|
107
|
+
this.line = '';
|
|
108
|
+
this.lineBytes = 0;
|
|
109
|
+
this.dropping = true;
|
|
110
|
+
this.cut = true;
|
|
111
|
+
}
|
|
112
|
+
trim() {
|
|
113
|
+
if (this.bytes <= this.cap)
|
|
114
|
+
return;
|
|
115
|
+
const bytes = Buffer.from(this.text);
|
|
116
|
+
const start = bytes.indexOf(NEWLINE, bytes.length - this.cap - 1) + 1;
|
|
117
|
+
this.text = bytes.subarray(start).toString('utf8');
|
|
118
|
+
this.bytes = bytes.length - start;
|
|
119
|
+
this.cut = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export class Scripts {
|
|
123
|
+
calls;
|
|
124
|
+
send;
|
|
125
|
+
runs = new Map();
|
|
126
|
+
constructor(calls, send) {
|
|
127
|
+
this.calls = calls;
|
|
128
|
+
this.send = send;
|
|
129
|
+
}
|
|
130
|
+
start(call) {
|
|
131
|
+
void this.calls.carry(call.input_id, () => this.run(call.input_id, planned(call)));
|
|
132
|
+
}
|
|
133
|
+
notified(notification) {
|
|
134
|
+
const run = this.runs.get(String(notification.wizard));
|
|
135
|
+
if (run === undefined)
|
|
136
|
+
return;
|
|
137
|
+
if (notification.type === 'wizard_input')
|
|
138
|
+
this.typed(run, String(notification.data));
|
|
139
|
+
if (notification.type === 'wizard_size')
|
|
140
|
+
this.resized(run, { cols: Number(notification.cols), rows: Number(notification.rows) });
|
|
141
|
+
if (notification.type === 'wizard_end' && run.plan.terminal === 'page')
|
|
142
|
+
this.end(run, {});
|
|
143
|
+
}
|
|
144
|
+
disconnected() {
|
|
145
|
+
for (const run of this.runs.values())
|
|
146
|
+
this.end(run, LOST);
|
|
147
|
+
}
|
|
148
|
+
async run(id, plan) {
|
|
149
|
+
const size = plan.terminal === 'page' ? null : FIRST_SIZE;
|
|
150
|
+
const output = plan.function === null ? null : new KeptOutput(plan.function.outputCap);
|
|
151
|
+
const run = { plan, input: null, output, process: null, pipes: [], terminal: null, size, typed: [], ended: null, sized: Promise.withResolvers() };
|
|
152
|
+
if (size !== null)
|
|
153
|
+
run.sized.resolve();
|
|
154
|
+
this.runs.set(id, run);
|
|
155
|
+
try {
|
|
156
|
+
return { result: await this.ran(id, run), release: plan.function !== null };
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.runs.delete(id);
|
|
160
|
+
if (run.input !== null)
|
|
161
|
+
await rm(run.input, { force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async ran(id, run) {
|
|
165
|
+
const { plan, output } = run;
|
|
166
|
+
let exited;
|
|
167
|
+
try {
|
|
168
|
+
const { spawn } = await import('@lydell/node-pty');
|
|
169
|
+
const shell = await onLoginPath('bash');
|
|
170
|
+
if (shell === null)
|
|
171
|
+
return failed(plan, NO_BASH);
|
|
172
|
+
const path = await loginPath();
|
|
173
|
+
await run.sized.promise;
|
|
174
|
+
if (plan.function !== null)
|
|
175
|
+
run.input = await inputFile(plan.function.input);
|
|
176
|
+
if (run.ended !== null)
|
|
177
|
+
return run.ended;
|
|
178
|
+
const environment = { ...childEnvironment(), PATH: path, ...(run.input === null ? {} : { HOUSE_INPUT: run.input }) };
|
|
179
|
+
if (plan.terminal === null) {
|
|
180
|
+
({ exited } = await this.piped(plan.script, shell, environment, run));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const terminal = spawn(shell, ['-c', plan.script], { name: TERMINAL, ...run.size, cwd: process.cwd(), env: environment });
|
|
184
|
+
run.process = terminal;
|
|
185
|
+
run.terminal = terminal;
|
|
186
|
+
terminal.onData((data) => {
|
|
187
|
+
this.send({ type: 'wizard_output', wizard: id, data });
|
|
188
|
+
output?.add(data);
|
|
189
|
+
});
|
|
190
|
+
for (const data of run.typed.splice(0))
|
|
191
|
+
terminal.write(data);
|
|
192
|
+
exited = new Promise((resolve) => terminal.onExit(({ exitCode, signal }) => resolve(signal ? SIGNALLED + signal : exitCode)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return failed(plan, causeOf(error));
|
|
197
|
+
}
|
|
198
|
+
let timedOut = false;
|
|
199
|
+
// House sends a function run's time cap in seconds, and a run still going at its cap ends with its process group and the output pipes it holds.
|
|
200
|
+
const capped = plan.function === null ? undefined : setTimeout(() => {
|
|
201
|
+
timedOut = true;
|
|
202
|
+
this.stop(run);
|
|
203
|
+
}, plan.function.timeCap * 1000);
|
|
204
|
+
const code = await exited;
|
|
205
|
+
clearTimeout(capped);
|
|
206
|
+
if (run.ended !== null)
|
|
207
|
+
return run.ended;
|
|
208
|
+
if (output === null)
|
|
209
|
+
return { exit_code: code };
|
|
210
|
+
const kept = output.kept();
|
|
211
|
+
return timedOut ? { timed_out: true, ...kept } : { exit_code: code, ...kept };
|
|
212
|
+
}
|
|
213
|
+
async piped(script, shell, environment, run) {
|
|
214
|
+
const child = spawnPiped(shell, ['-c', script], { cwd: process.cwd(), env: environment, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
215
|
+
const exited = new Promise((resolve) => child.once('close', (code, signal) => resolve(signal === null ? code : SIGNALLED + constants.signals[signal])));
|
|
216
|
+
await once(child, 'spawn');
|
|
217
|
+
run.process = { pid: child.pid };
|
|
218
|
+
run.pipes = [child.stdout, child.stderr];
|
|
219
|
+
for (const pipe of run.pipes) {
|
|
220
|
+
pipe.setEncoding('utf8');
|
|
221
|
+
pipe.on('data', (data) => run.output.add(data));
|
|
222
|
+
}
|
|
223
|
+
if (run.ended !== null)
|
|
224
|
+
this.stop(run);
|
|
225
|
+
return { exited };
|
|
226
|
+
}
|
|
227
|
+
typed(run, data) {
|
|
228
|
+
run.output?.typed(data);
|
|
229
|
+
if (run.terminal === null)
|
|
230
|
+
run.typed.push(data);
|
|
231
|
+
else
|
|
232
|
+
run.terminal.write(data);
|
|
233
|
+
}
|
|
234
|
+
resized(run, size) {
|
|
235
|
+
run.size = size;
|
|
236
|
+
run.sized.resolve();
|
|
237
|
+
try {
|
|
238
|
+
run.terminal?.resize(size.cols, size.rows);
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
end(run, result) {
|
|
243
|
+
if (run.ended !== null)
|
|
244
|
+
return;
|
|
245
|
+
run.ended = result;
|
|
246
|
+
run.sized.resolve();
|
|
247
|
+
this.stop(run);
|
|
248
|
+
}
|
|
249
|
+
stop(run) {
|
|
250
|
+
if (run.process !== null)
|
|
251
|
+
killTree(run.process);
|
|
252
|
+
for (const pipe of run.pipes)
|
|
253
|
+
pipe.destroy();
|
|
254
|
+
}
|
|
255
|
+
}
|
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;
|
|
@@ -56,7 +57,7 @@ export class SignIns {
|
|
|
56
57
|
const child = spawn(cli, login.args, {
|
|
57
58
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
59
|
detached: true,
|
|
59
|
-
env:
|
|
60
|
+
env: childEnvironment(),
|
|
60
61
|
});
|
|
61
62
|
const running = () => child.exitCode === null && child.signalCode === null;
|
|
62
63
|
const stop = () => killTree(child);
|
package/dist/silence.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const SYSTEM_CLOCK = {
|
|
2
|
+
later: (delay, callback) => setTimeout(callback, delay),
|
|
3
|
+
clear: (timer) => clearTimeout(timer),
|
|
4
|
+
};
|
|
5
|
+
export class Silence {
|
|
6
|
+
timer = null;
|
|
7
|
+
interval;
|
|
8
|
+
silent;
|
|
9
|
+
clock;
|
|
10
|
+
constructor(interval, silent, clock = SYSTEM_CLOCK) {
|
|
11
|
+
this.interval = interval;
|
|
12
|
+
this.silent = silent;
|
|
13
|
+
this.clock = clock;
|
|
14
|
+
}
|
|
15
|
+
heard() {
|
|
16
|
+
this.stop();
|
|
17
|
+
this.timer = this.clock.later(this.interval, () => {
|
|
18
|
+
this.timer = null;
|
|
19
|
+
this.silent();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
stop() {
|
|
23
|
+
if (this.timer === null)
|
|
24
|
+
return;
|
|
25
|
+
this.clock.clear(this.timer);
|
|
26
|
+
this.timer = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/skills.js
CHANGED
|
@@ -1,31 +1,14 @@
|
|
|
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, writeFile } from 'node:fs/promises';
|
|
4
2
|
import { homedir } from 'node:os';
|
|
5
3
|
import { join, relative } from 'node:path';
|
|
6
|
-
import {
|
|
4
|
+
import { GUIDE, listSkills } from '@agentshouse/skills';
|
|
7
5
|
import { parse } from 'yaml';
|
|
8
6
|
import { KIT_RELEASE } from './clis.js';
|
|
9
7
|
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
8
|
const RECORD = 'skill-set.json';
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
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: (.+)$/;
|
|
9
|
+
const ENTRY = 'SKILL.md';
|
|
10
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---\n/u;
|
|
11
|
+
const ORIGIN = '<origin>';
|
|
29
12
|
function skillsDirectory() {
|
|
30
13
|
return join(homedir(), '.agents', 'skills');
|
|
31
14
|
}
|
|
@@ -43,105 +26,67 @@ async function present(path) {
|
|
|
43
26
|
throw error;
|
|
44
27
|
}
|
|
45
28
|
}
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
58
|
+
const names = placed.map((skill) => skill.name);
|
|
53
59
|
await writeHome(RECORD, { names });
|
|
54
60
|
await mkdir(skillsDirectory(), { recursive: true });
|
|
55
61
|
await mkdir(claudeDirectory(), { recursive: true });
|
|
56
62
|
const skills = await realpath(skillsDirectory());
|
|
57
63
|
const claude = await realpath(claudeDirectory());
|
|
58
|
-
for (const name of
|
|
59
|
-
await
|
|
64
|
+
for (const { name, place } of placed) {
|
|
65
|
+
await place(join(skillsDirectory(), name));
|
|
60
66
|
if (claude !== skills)
|
|
61
67
|
await symlink(relative(claude, join(skills, name)), join(claudeDirectory(), name));
|
|
62
68
|
}
|
|
63
|
-
await writeHome(RECORD, { version: KIT_RELEASE, names });
|
|
69
|
+
await writeHome(RECORD, { version: KIT_RELEASE, origin, names });
|
|
64
70
|
}
|
|
65
|
-
export async function placeSkillSet() {
|
|
71
|
+
export async function placeSkillSet(origin) {
|
|
66
72
|
try {
|
|
67
|
-
await replaceSkillSet();
|
|
73
|
+
await replaceSkillSet(origin);
|
|
68
74
|
}
|
|
69
75
|
catch (error) {
|
|
70
76
|
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
|
-
async function replaceSkillSet() {
|
|
79
|
+
async function replaceSkillSet(origin) {
|
|
74
80
|
const accepted = (await readConfiguration()).skills;
|
|
75
81
|
const installed = await readHome(RECORD);
|
|
76
|
-
if (accepted && installed?.version === KIT_RELEASE)
|
|
82
|
+
if (accepted && installed?.version === KIT_RELEASE && installed.origin === origin)
|
|
77
83
|
return;
|
|
78
84
|
for (const name of installed?.names ?? []) {
|
|
79
85
|
await rm(join(claudeDirectory(), name), { recursive: true, force: true });
|
|
80
86
|
await rm(join(skillsDirectory(), name), { recursive: true, force: true });
|
|
81
87
|
}
|
|
82
88
|
if (accepted)
|
|
83
|
-
await install();
|
|
89
|
+
await install(origin);
|
|
84
90
|
else
|
|
85
91
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
86
92
|
}
|
|
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
|
-
}
|