@agentshouse/kit 0.2.1-cbdf76b9a90a3 → 0.2.1-cbff58027f078

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.
Files changed (142) hide show
  1. package/README.md +28 -28
  2. package/dist/acp.js +58 -7
  3. package/dist/afk.js +17 -0
  4. package/dist/agents.js +55 -32
  5. package/dist/api.js +1 -1
  6. package/dist/authority-main.js +2 -2
  7. package/dist/bridge.js +1 -1
  8. package/dist/building.js +50 -0
  9. package/dist/calls.js +71 -0
  10. package/dist/chunks.js +49 -0
  11. package/dist/clis.js +45 -40
  12. package/dist/{stream.js → connection.js} +28 -46
  13. package/dist/conversations.js +330 -125
  14. package/dist/copies.js +286 -149
  15. package/dist/end.js +11 -5
  16. package/dist/events.js +66 -8
  17. package/dist/files.js +139 -37
  18. package/dist/git.js +47 -13
  19. package/dist/home.js +0 -7
  20. package/dist/house-cli.js +95 -87
  21. package/dist/house-main.js +3 -3
  22. package/dist/instructions.js +29 -9
  23. package/dist/kit-cli.js +4 -4
  24. package/dist/kit-main.js +3 -3
  25. package/dist/login.js +4 -4
  26. package/dist/mcp.js +6 -2
  27. package/dist/parts.js +10 -0
  28. package/dist/placement.js +10 -18
  29. package/dist/relay.js +21 -0
  30. package/dist/resident.js +49 -38
  31. package/dist/scope.js +12 -4
  32. package/dist/scripts.js +255 -0
  33. package/dist/service.js +1 -1
  34. package/dist/sign-in.js +22 -21
  35. package/dist/silence.js +28 -0
  36. package/dist/skills.js +42 -97
  37. package/dist/transcription.js +83 -43
  38. package/dist/update.js +113 -124
  39. package/dist/wire.js +23 -5
  40. package/package.json +20 -21
  41. package/dist/bootstrap.json +0 -24
  42. package/dist/clis-main.js +0 -23
  43. package/dist/inputs.js +0 -63
  44. package/how-we-work.md +0 -33
  45. package/skills/ask-matt/LICENSE +0 -21
  46. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  47. package/skills/ask-matt/SKILL.md +0 -97
  48. package/skills/ask-matt/agents/openai.yaml +0 -5
  49. package/skills/code-review/LICENSE +0 -21
  50. package/skills/code-review/SKILL.md +0 -89
  51. package/skills/code-review/agents/openai.yaml +0 -3
  52. package/skills/codebase-design/DEEPENING.md +0 -37
  53. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  54. package/skills/codebase-design/LICENSE +0 -21
  55. package/skills/codebase-design/SKILL.md +0 -114
  56. package/skills/codebase-design/agents/openai.yaml +0 -3
  57. package/skills/diagnosing-bugs/LICENSE +0 -21
  58. package/skills/diagnosing-bugs/SKILL.md +0 -138
  59. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  60. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  61. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  62. package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
  63. package/skills/domain-modeling/LICENSE +0 -21
  64. package/skills/domain-modeling/SKILL.md +0 -74
  65. package/skills/domain-modeling/agents/openai.yaml +0 -3
  66. package/skills/grill-me/LICENSE +0 -21
  67. package/skills/grill-me/SKILL.md +0 -7
  68. package/skills/grill-me/agents/openai.yaml +0 -5
  69. package/skills/grill-with-docs/LICENSE +0 -21
  70. package/skills/grill-with-docs/SKILL.md +0 -7
  71. package/skills/grill-with-docs/agents/openai.yaml +0 -5
  72. package/skills/grilling/LICENSE +0 -21
  73. package/skills/grilling/SKILL.md +0 -30
  74. package/skills/grilling/agents/openai.yaml +0 -3
  75. package/skills/handoff/LICENSE +0 -21
  76. package/skills/handoff/SKILL.md +0 -16
  77. package/skills/handoff/agents/openai.yaml +0 -5
  78. package/skills/implement/LICENSE +0 -21
  79. package/skills/implement/SKILL.md +0 -17
  80. package/skills/implement/agents/openai.yaml +0 -5
  81. package/skills/implement-spec/LICENSE +0 -21
  82. package/skills/implement-spec/SKILL.md +0 -40
  83. package/skills/implement-spec/agents/openai.yaml +0 -5
  84. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  85. package/skills/improve-codebase-architecture/LICENSE +0 -21
  86. package/skills/improve-codebase-architecture/SKILL.md +0 -71
  87. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  88. package/skills/pr/CREDITS.md +0 -3
  89. package/skills/pr/LICENSE +0 -21
  90. package/skills/pr/SKILL.md +0 -170
  91. package/skills/pr/agents/openai.yaml +0 -3
  92. package/skills/prototype/LICENSE +0 -21
  93. package/skills/prototype/LOGIC.md +0 -67
  94. package/skills/prototype/SKILL.md +0 -26
  95. package/skills/prototype/UI.md +0 -112
  96. package/skills/prototype/agents/openai.yaml +0 -3
  97. package/skills/research/LICENSE +0 -21
  98. package/skills/research/SKILL.md +0 -12
  99. package/skills/research/agents/openai.yaml +0 -3
  100. package/skills/retro/LICENSE +0 -21
  101. package/skills/retro/SKILL.md +0 -44
  102. package/skills/retro/agents/openai.yaml +0 -5
  103. package/skills/tdd/LICENSE +0 -21
  104. package/skills/tdd/SKILL.md +0 -38
  105. package/skills/tdd/agents/openai.yaml +0 -3
  106. package/skills/tdd/mocking.md +0 -59
  107. package/skills/tdd/tests.md +0 -77
  108. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  109. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  110. package/skills/teach/LICENSE +0 -21
  111. package/skills/teach/MISSION-FORMAT.md +0 -31
  112. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  113. package/skills/teach/SKILL.md +0 -140
  114. package/skills/teach/agents/openai.yaml +0 -5
  115. package/skills/to-questionnaire/LICENSE +0 -21
  116. package/skills/to-questionnaire/SKILL.md +0 -54
  117. package/skills/to-questionnaire/agents/openai.yaml +0 -5
  118. package/skills/to-spec/LICENSE +0 -21
  119. package/skills/to-spec/SKILL.md +0 -75
  120. package/skills/to-spec/agents/openai.yaml +0 -5
  121. package/skills/to-tickets/LICENSE +0 -21
  122. package/skills/to-tickets/SKILL.md +0 -105
  123. package/skills/to-tickets/agents/openai.yaml +0 -5
  124. package/skills/triage/AGENT-BRIEF.md +0 -207
  125. package/skills/triage/LICENSE +0 -21
  126. package/skills/triage/OUT-OF-SCOPE.md +0 -105
  127. package/skills/triage/SKILL.md +0 -112
  128. package/skills/triage/agents/openai.yaml +0 -5
  129. package/skills/wait-what/LICENSE +0 -21
  130. package/skills/wait-what/SKILL.md +0 -7
  131. package/skills/wait-what/agents/openai.yaml +0 -5
  132. package/skills/wayfinder/LICENSE +0 -21
  133. package/skills/wayfinder/SKILL.md +0 -128
  134. package/skills/wayfinder/agents/openai.yaml +0 -5
  135. package/skills/wizard/LICENSE +0 -21
  136. package/skills/wizard/SKILL.md +0 -44
  137. package/skills/wizard/agents/openai.yaml +0 -3
  138. package/skills/wizard/template.sh +0 -223
  139. package/skills/writing-for-agents/LICENSE +0 -21
  140. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  141. package/skills/writing-for-agents/SKILL.md +0 -81
  142. package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/resident.js CHANGED
@@ -8,17 +8,19 @@ 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 { CONVERSATION_INPUTS, Conversations } from './conversations.js';
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';
15
- import { Inputs } from './inputs.js';
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 { holdStream } from './stream.js';
21
- import { Updates } from './update.js';
21
+ import { holdConnection } from './connection.js';
22
+ import { KitUpdate } from './update.js';
23
+ import { takeUnservedRelease } from './wire.js';
22
24
  import { codexLauncher } from './work.js';
23
25
  function report(error) {
24
26
  process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -47,7 +49,7 @@ async function serveOwner(copies) {
47
49
  if (!claimed(lock)) {
48
50
  lock.close();
49
51
  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`);
52
+ 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
53
  }
52
54
  const server = createServer(async (request, response) => {
53
55
  try {
@@ -109,14 +111,13 @@ function connected() {
109
111
  }
110
112
  async function started() {
111
113
  await connected();
112
- await forgetRestart();
113
114
  watch(kitHome(), () => {
114
115
  if (enrolled())
115
116
  return;
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');
@@ -126,72 +127,82 @@ export async function resident() {
126
127
  const copies = new LocalCopies(house);
127
128
  await copies.load();
128
129
  await serveOwner(copies);
130
+ await copies.swept();
129
131
  await started().catch((error) => {
130
132
  report(error);
131
133
  process.exit(1);
132
134
  });
133
135
  process.env.HOUSE_KIT_RESIDENT = String(process.pid);
134
136
  const agents = new Agents(house, () => changed());
135
- let stream = null;
137
+ let connection = null;
136
138
  let reported = false;
137
- const idle = () => inputs.idle() && conversations.idle() && agents.idle();
139
+ const idle = () => calls.idle() && conversations.idle() && agents.idle();
138
140
  const changed = () => {
139
141
  if (!idle()) {
140
142
  reported = false;
141
143
  }
142
- else if (restartAsked() && conversations.quiet()) {
143
- restart('restarting for an updated CLI');
144
+ else if (update.placedRelease() !== null && conversations.quiet()) {
145
+ restart(`Kit ${update.placedRelease()} is placed; its supervisor starts it`);
144
146
  }
145
147
  else if (!reported) {
146
148
  reported = true;
147
149
  logged(house.deliver('/kit/idle', {}, idle));
148
150
  }
149
151
  };
150
- const inputs = new Inputs(house, changed);
152
+ const calls = new Calls(house, changed);
151
153
  const conversations = new Conversations({
152
154
  house,
153
155
  agents,
154
156
  copies,
155
- inputs,
156
- send: (frame) => stream?.send(frame) ?? false,
157
+ calls,
158
+ send: (notification) => connection?.send(notification) ?? false,
157
159
  changed,
158
160
  });
159
- const signIns = new SignIns(house, agents, inputs);
160
- const updates = new Updates(inputs);
161
+ const signIns = new SignIns(house, agents, calls);
162
+ const update = new KitUpdate(calls);
163
+ if (process.env.HOUSE_KIT_SANDBOX !== '1')
164
+ takeUnservedRelease((release) => logged(update.takeRefused(release)));
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
  }
167
174
  await house.deliver('/kit/restarted', {}).catch(report);
168
- await updates.resumed().catch(report);
169
175
  logged(agents.refresh());
170
- stream = holdStream({
176
+ agents.keepCurrent();
177
+ connection = holdConnection({
171
178
  opened: () => {
172
179
  conversations.opened();
173
180
  changed();
174
181
  logged(copies.connected());
175
182
  },
176
- frame: (arrived) => {
177
- if (arrived.type === 'watching')
178
- conversations.watching(arrived.watching === true);
179
- if (arrived.type === 'authority_changed')
180
- logged(copies.changed(arrived.rooms));
181
- if (arrived.type === 'work_available' && arrived.subject === 'agents')
182
- logged(agents.refresh());
183
- if (arrived.type === 'input') {
184
- if (arrived.kind === 'sign_in')
185
- signIns.start(arrived);
186
- else if (arrived.kind === 'update')
187
- updates.start(arrived);
188
- else if (CONVERSATION_INPUTS.has(arrived.kind))
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
- }
183
+ closed: () => scripts.disconnected(),
184
+ call: (call) => {
185
+ if (call.kind === 'sign_in')
186
+ signIns.start(call);
187
+ else if (call.kind === 'update')
188
+ update.take(call);
189
+ else if (call.kind === 'run_wizard' || call.kind === 'run_function')
190
+ scripts.start(call);
191
+ else if (CONVERSATION_METHODS.has(call.kind))
192
+ conversations.call(call);
193
+ else {
194
+ const refused = { refused: unknownMethod(call.kind) };
195
+ void calls.carry(call.input_id, async () => ({ result: refused }));
194
196
  }
195
197
  },
198
+ notification: (notification) => {
199
+ if (notification.type === 'watching')
200
+ conversations.watching(notification.watching === true);
201
+ if (notification.type === 'authority_changed')
202
+ logged(copies.changed(notification.rooms));
203
+ if (notification.type === 'work_available' && notification.subject === 'agents')
204
+ logged(agents.refresh());
205
+ scripts.notified(notification);
206
+ },
196
207
  });
197
208
  }
package/dist/scope.js CHANGED
@@ -1,5 +1,5 @@
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';
@@ -122,15 +122,23 @@ function members(group) {
122
122
  }
123
123
  return pids;
124
124
  }
125
+ function besideCli(cli) {
126
+ try {
127
+ return `${realpathSync(cli)}-`;
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ }
125
133
  export class Scope {
126
134
  marker;
127
- cli;
135
+ helpers;
128
136
  group;
129
137
  session = null;
130
138
  recorded = null;
131
139
  constructor(marker, name, cli) {
132
140
  this.marker = marker;
133
- this.cli = cli;
141
+ this.helpers = besideCli(cli);
134
142
  const own = ownGroup();
135
143
  this.group = own === null ? null : join(own, name);
136
144
  try {
@@ -168,7 +176,7 @@ export class Scope {
168
176
  return true;
169
177
  if (this.recorded === null)
170
178
  return found.size > 0;
171
- return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || command.startsWith(`${this.cli}-`)));
179
+ return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || (this.helpers !== null && command.startsWith(this.helpers))));
172
180
  }
173
181
  empty() {
174
182
  return this.found()?.size === 0;
@@ -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 House Kit service could not be started: ${cause}`);
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, withoutProxy } from './clis.js';
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
- inputs;
19
- constructor(house, agents, inputs) {
19
+ calls;
20
+ constructor(house, agents, calls) {
20
21
  this.house = house;
21
22
  this.agents = agents;
22
- this.inputs = inputs;
23
+ this.calls = calls;
23
24
  }
24
- start(input) {
25
- void this.inputs.carry(input.input_id, () => this.signIn(input));
25
+ start(call) {
26
+ void this.calls.carry(call.input_id, () => this.signIn(call));
26
27
  }
27
- async signIn(input) {
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(), input.ends_in * 1000);
31
- const ended = await Promise.race([this.signedIn(input, ending.signal), endOf(ending.signal)]);
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 ${input.cli} sign-in reached the end House set for it\n`);
35
- return ended ? { ack: {} } : { ack: {}, acknowledged: () => 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) };
36
37
  }
37
- async signedIn(input, signal) {
38
- await this.login(input, signal);
38
+ async signedIn(call, signal) {
39
+ await this.login(call, signal);
39
40
  if (!signal.aborted)
40
- await this.agents.recheck(input.cli, signal);
41
+ await this.agents.recheck(call.cli, signal);
41
42
  return false;
42
43
  }
43
- async login(input, signal) {
44
+ async login(call, signal) {
44
45
  let cli;
45
46
  try {
46
- cli = await this.agents.located(input.cli, signal);
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 ${input.cli} sign-in failed: ${causeOf(error)}\n`);
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[input.cli].login;
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: withoutProxy(),
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, input.input_id, steps, running).then((content) => {
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 ${input.cli} sign-in failed: ${failed}\n`);
105
+ process.stderr.write(`kit: the ${call.cli} sign-in failed: ${failed}\n`);
105
106
  }
106
107
  }
@@ -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
+ }