@agentshouse/kit 0.2.1-cb345f4157685 → 0.2.1-cb66cb2fd0696
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 +58 -7
- package/dist/agents.js +85 -40
- package/dist/api.js +5 -5
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/clis.js +34 -40
- package/dist/{stream.js → connection.js} +31 -49
- package/dist/conversations.js +423 -139
- package/dist/copies.js +36 -43
- package/dist/end.js +14 -4
- package/dist/events.js +29 -8
- package/dist/files.js +139 -37
- package/dist/git.js +3 -3
- package/dist/home.js +0 -7
- package/dist/house-cli.js +95 -87
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +6 -2
- package/dist/placement.js +21 -20
- package/dist/relay.js +21 -0
- package/dist/resident.js +45 -38
- package/dist/scope.js +15 -4
- package/dist/scripts.js +255 -0
- package/dist/service.js +1 -1
- package/dist/sign-in.js +22 -21
- package/dist/silence.js +28 -0
- package/dist/skills.js +42 -97
- package/dist/transcription.js +83 -43
- package/dist/translate.js +2 -8
- package/dist/update.js +91 -125
- package/dist/wire.js +19 -3
- package/package.json +20 -21
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/inputs.js +0 -37
- 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
|
@@ -8,17 +8,18 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { Agents } from './agents.js';
|
|
9
9
|
import { house } from './api.js';
|
|
10
10
|
import { received } from './bridge.js';
|
|
11
|
-
import {
|
|
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,
|
|
15
|
-
import {
|
|
13
|
+
import { restart, unknownMethod } from './end.js';
|
|
14
|
+
import { agentBase, enrolled, kitHome, ownerLock, ownerSocket, readEnrolment } from './home.js';
|
|
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';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
21
|
+
import { holdConnection } from './connection.js';
|
|
22
|
+
import { KitUpdate } from './update.js';
|
|
22
23
|
import { codexLauncher } from './work.js';
|
|
23
24
|
function report(error) {
|
|
24
25
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -47,7 +48,7 @@ async function serveOwner(copies) {
|
|
|
47
48
|
if (!claimed(lock)) {
|
|
48
49
|
lock.close();
|
|
49
50
|
const running = await runningResident();
|
|
50
|
-
throw new Error(`${running === null ? 'another Kit' : `Kit ${running}`} already runs this Environment from Kit home ${kitHome()}; stop it first to run another here`);
|
|
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`);
|
|
51
52
|
}
|
|
52
53
|
const server = createServer(async (request, response) => {
|
|
53
54
|
try {
|
|
@@ -109,14 +110,13 @@ function connected() {
|
|
|
109
110
|
}
|
|
110
111
|
async function started() {
|
|
111
112
|
await connected();
|
|
112
|
-
await forgetRestart();
|
|
113
113
|
watch(kitHome(), () => {
|
|
114
114
|
if (enrolled())
|
|
115
115
|
return;
|
|
116
116
|
killDescendants();
|
|
117
117
|
process.exit(1);
|
|
118
118
|
});
|
|
119
|
-
await placeSkillSet();
|
|
119
|
+
await placeSkillSet((await readEnrolment()).house);
|
|
120
120
|
await mkdir(agentBase(), { recursive: true });
|
|
121
121
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
122
122
|
await installShim(codexLauncher(), 'codex-main');
|
|
@@ -132,66 +132,73 @@ export async function resident() {
|
|
|
132
132
|
});
|
|
133
133
|
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
134
134
|
const agents = new Agents(house, () => changed());
|
|
135
|
-
let
|
|
135
|
+
let connection = null;
|
|
136
136
|
let reported = false;
|
|
137
|
-
const idle = () =>
|
|
137
|
+
const idle = () => calls.idle() && conversations.idle() && agents.idle();
|
|
138
138
|
const changed = () => {
|
|
139
139
|
if (!idle()) {
|
|
140
140
|
reported = false;
|
|
141
141
|
}
|
|
142
|
-
else if (
|
|
143
|
-
restart(
|
|
142
|
+
else if (update.placedRelease() !== null && conversations.quiet()) {
|
|
143
|
+
restart(`Kit ${update.placedRelease()} is placed; its supervisor starts it`);
|
|
144
144
|
}
|
|
145
145
|
else if (!reported) {
|
|
146
146
|
reported = true;
|
|
147
147
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
|
-
const
|
|
150
|
+
const calls = new Calls(house, changed);
|
|
151
151
|
const conversations = new Conversations({
|
|
152
152
|
house,
|
|
153
153
|
agents,
|
|
154
154
|
copies,
|
|
155
|
-
|
|
156
|
-
send: (
|
|
155
|
+
calls,
|
|
156
|
+
send: (notification) => connection?.send(notification) ?? false,
|
|
157
157
|
changed,
|
|
158
158
|
});
|
|
159
|
-
const signIns = new SignIns(house, agents,
|
|
160
|
-
const
|
|
159
|
+
const signIns = new SignIns(house, agents, calls);
|
|
160
|
+
const update = new KitUpdate(calls);
|
|
161
|
+
removeInputs();
|
|
162
|
+
const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
|
|
161
163
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
162
164
|
process.once(signal, () => {
|
|
163
165
|
killDescendants();
|
|
166
|
+
removeInputs();
|
|
164
167
|
process.kill(process.pid, signal);
|
|
165
168
|
});
|
|
166
169
|
}
|
|
167
170
|
await house.deliver('/kit/restarted', {}).catch(report);
|
|
168
|
-
await updates.resumed().catch(report);
|
|
169
171
|
logged(agents.refresh());
|
|
170
|
-
|
|
172
|
+
agents.keepCurrent();
|
|
173
|
+
connection = holdConnection({
|
|
171
174
|
opened: () => {
|
|
172
175
|
conversations.opened();
|
|
173
176
|
changed();
|
|
174
177
|
logged(copies.connected());
|
|
175
178
|
},
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
conversations.input(arrived);
|
|
190
|
-
else {
|
|
191
|
-
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
192
|
-
void inputs.carry(String(arrived.input_id), async () => ({ ack: refused }));
|
|
193
|
-
}
|
|
179
|
+
closed: () => scripts.disconnected(),
|
|
180
|
+
call: (call) => {
|
|
181
|
+
if (call.kind === 'sign_in')
|
|
182
|
+
signIns.start(call);
|
|
183
|
+
else if (call.kind === 'update')
|
|
184
|
+
update.take(call);
|
|
185
|
+
else if (call.kind === 'run_wizard' || call.kind === 'run_function')
|
|
186
|
+
scripts.start(call);
|
|
187
|
+
else if (CONVERSATION_METHODS.has(call.kind))
|
|
188
|
+
conversations.call(call);
|
|
189
|
+
else {
|
|
190
|
+
const refused = { refused: unknownMethod(call.kind) };
|
|
191
|
+
void calls.carry(call.input_id, async () => ({ result: refused }));
|
|
194
192
|
}
|
|
195
193
|
},
|
|
194
|
+
notification: (notification) => {
|
|
195
|
+
if (notification.type === 'watching')
|
|
196
|
+
conversations.watching(notification.watching === true);
|
|
197
|
+
if (notification.type === 'authority_changed')
|
|
198
|
+
logged(copies.changed(notification.rooms));
|
|
199
|
+
if (notification.type === 'work_available' && notification.subject === 'agents')
|
|
200
|
+
logged(agents.refresh());
|
|
201
|
+
scripts.notified(notification);
|
|
202
|
+
},
|
|
196
203
|
});
|
|
197
204
|
}
|
package/dist/scope.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { mkdirSync, readdirSync, readFileSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync, realpathSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
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
|
}
|
|
@@ -119,15 +122,23 @@ function members(group) {
|
|
|
119
122
|
}
|
|
120
123
|
return pids;
|
|
121
124
|
}
|
|
125
|
+
function besideCli(cli) {
|
|
126
|
+
try {
|
|
127
|
+
return `${realpathSync(cli)}-`;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
122
133
|
export class Scope {
|
|
123
134
|
marker;
|
|
124
|
-
|
|
135
|
+
helpers;
|
|
125
136
|
group;
|
|
126
137
|
session = null;
|
|
127
138
|
recorded = null;
|
|
128
139
|
constructor(marker, name, cli) {
|
|
129
140
|
this.marker = marker;
|
|
130
|
-
this.
|
|
141
|
+
this.helpers = besideCli(cli);
|
|
131
142
|
const own = ownGroup();
|
|
132
143
|
this.group = own === null ? null : join(own, name);
|
|
133
144
|
try {
|
|
@@ -165,7 +176,7 @@ export class Scope {
|
|
|
165
176
|
return true;
|
|
166
177
|
if (this.recorded === null)
|
|
167
178
|
return found.size > 0;
|
|
168
|
-
return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || command.startsWith(
|
|
179
|
+
return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || (this.helpers !== null && command.startsWith(this.helpers))));
|
|
169
180
|
}
|
|
170
181
|
empty() {
|
|
171
182
|
return this.found()?.size === 0;
|
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/service.js
CHANGED
|
@@ -13,7 +13,7 @@ function start(command, args) {
|
|
|
13
13
|
if (started.status === 0)
|
|
14
14
|
return;
|
|
15
15
|
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
16
|
-
throw new Error(`the
|
|
16
|
+
throw new Error(`the Kit service could not be started: ${cause}`);
|
|
17
17
|
}
|
|
18
18
|
function physical(path) {
|
|
19
19
|
return existsSync(path) ? realpathSync(path) : resolve(path);
|
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;
|
|
@@ -15,48 +16,48 @@ function endOf(signal) {
|
|
|
15
16
|
export class SignIns {
|
|
16
17
|
house;
|
|
17
18
|
agents;
|
|
18
|
-
|
|
19
|
-
constructor(house, agents,
|
|
19
|
+
calls;
|
|
20
|
+
constructor(house, agents, calls) {
|
|
20
21
|
this.house = house;
|
|
21
22
|
this.agents = agents;
|
|
22
|
-
this.
|
|
23
|
+
this.calls = calls;
|
|
23
24
|
}
|
|
24
|
-
start(
|
|
25
|
-
void this.
|
|
25
|
+
start(call) {
|
|
26
|
+
void this.calls.carry(call.input_id, () => this.signIn(call));
|
|
26
27
|
}
|
|
27
|
-
async signIn(
|
|
28
|
+
async signIn(call) {
|
|
28
29
|
const ending = new AbortController();
|
|
29
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.
|
|
30
|
-
const timer = setTimeout(() => ending.abort(),
|
|
31
|
-
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)]);
|
|
32
33
|
clearTimeout(timer);
|
|
33
34
|
if (ended)
|
|
34
|
-
process.stderr.write(`kit: the ${
|
|
35
|
-
return ended ? {
|
|
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) };
|
|
36
37
|
}
|
|
37
|
-
async signedIn(
|
|
38
|
-
await this.login(
|
|
38
|
+
async signedIn(call, signal) {
|
|
39
|
+
await this.login(call, signal);
|
|
39
40
|
if (!signal.aborted)
|
|
40
|
-
await this.agents.recheck(
|
|
41
|
+
await this.agents.recheck(call.cli, signal);
|
|
41
42
|
return false;
|
|
42
43
|
}
|
|
43
|
-
async login(
|
|
44
|
+
async login(call, signal) {
|
|
44
45
|
let cli;
|
|
45
46
|
try {
|
|
46
|
-
cli = await this.agents.located(
|
|
47
|
+
cli = await this.agents.located(call.cli, signal);
|
|
47
48
|
}
|
|
48
49
|
catch (error) {
|
|
49
50
|
if (!signal.aborted)
|
|
50
|
-
process.stderr.write(`kit: the ${
|
|
51
|
+
process.stderr.write(`kit: the ${call.cli} sign-in failed: ${causeOf(error)}\n`);
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
if (signal.aborted)
|
|
54
55
|
return;
|
|
55
|
-
const login = CLIS[
|
|
56
|
+
const login = CLIS[call.cli].login;
|
|
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);
|
|
@@ -84,7 +85,7 @@ export class SignIns {
|
|
|
84
85
|
return;
|
|
85
86
|
held = true;
|
|
86
87
|
const steps = [{ kind: 'visit', label: 'Open this link and sign in', url: link }, step];
|
|
87
|
-
void holdSecretInput(this.house,
|
|
88
|
+
void holdSecretInput(this.house, call.input_id, steps, running).then((content) => {
|
|
88
89
|
if (!running())
|
|
89
90
|
return;
|
|
90
91
|
if (content === null)
|
|
@@ -101,6 +102,6 @@ export class SignIns {
|
|
|
101
102
|
});
|
|
102
103
|
signal.removeEventListener('abort', stop);
|
|
103
104
|
if (failed !== null && !signal.aborted)
|
|
104
|
-
process.stderr.write(`kit: the ${
|
|
105
|
+
process.stderr.write(`kit: the ${call.cli} sign-in failed: ${failed}\n`);
|
|
105
106
|
}
|
|
106
107
|
}
|
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
|
+
}
|