@agentshouse/kit 0.2.1-c5d51f1e4ce19 → 0.2.1-c5e51b4e66131

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 (143) hide show
  1. package/README.md +28 -28
  2. package/dist/acp.js +85 -18
  3. package/dist/agents.js +38 -17
  4. package/dist/api.js +5 -5
  5. package/dist/app-git.js +10 -26
  6. package/dist/authority-main.js +2 -2
  7. package/dist/bridge.js +4 -7
  8. package/dist/building.js +46 -0
  9. package/dist/calls.js +63 -0
  10. package/dist/chatgpt.js +1 -1
  11. package/dist/clis-main.js +2 -2
  12. package/dist/clis.js +47 -15
  13. package/dist/{stream.js → connection.js} +45 -52
  14. package/dist/conversations.js +391 -152
  15. package/dist/copies.js +77 -50
  16. package/dist/end.js +9 -4
  17. package/dist/events.js +4 -3
  18. package/dist/files.js +139 -37
  19. package/dist/git.js +5 -6
  20. package/dist/home.js +27 -13
  21. package/dist/house-cli.js +96 -88
  22. package/dist/house-main.js +3 -3
  23. package/dist/instructions.js +29 -8
  24. package/dist/kit-cli.js +4 -4
  25. package/dist/kit-main.js +3 -3
  26. package/dist/login.js +18 -9
  27. package/dist/logout.js +0 -2
  28. package/dist/mcp.js +6 -2
  29. package/dist/placement.js +9 -18
  30. package/dist/relay.js +21 -0
  31. package/dist/resident.js +92 -38
  32. package/dist/scope.js +3 -0
  33. package/dist/service.js +23 -1
  34. package/dist/sign-in.js +22 -39
  35. package/dist/silence.js +28 -0
  36. package/dist/skills.js +42 -97
  37. package/dist/transcription.js +83 -43
  38. package/dist/translate.js +2 -8
  39. package/dist/update.js +89 -49
  40. package/dist/web-wizard.js +101 -0
  41. package/dist/wire.js +19 -3
  42. package/dist/work.js +3 -5
  43. package/package.json +20 -21
  44. package/dist/bootstrap.json +0 -24
  45. package/how-we-work.md +0 -33
  46. package/skills/ask-matt/LICENSE +0 -21
  47. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  48. package/skills/ask-matt/SKILL.md +0 -97
  49. package/skills/ask-matt/agents/openai.yaml +0 -5
  50. package/skills/code-review/LICENSE +0 -21
  51. package/skills/code-review/SKILL.md +0 -89
  52. package/skills/code-review/agents/openai.yaml +0 -3
  53. package/skills/codebase-design/DEEPENING.md +0 -37
  54. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  55. package/skills/codebase-design/LICENSE +0 -21
  56. package/skills/codebase-design/SKILL.md +0 -114
  57. package/skills/codebase-design/agents/openai.yaml +0 -3
  58. package/skills/diagnosing-bugs/LICENSE +0 -21
  59. package/skills/diagnosing-bugs/SKILL.md +0 -138
  60. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  61. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  62. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  63. package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
  64. package/skills/domain-modeling/LICENSE +0 -21
  65. package/skills/domain-modeling/SKILL.md +0 -74
  66. package/skills/domain-modeling/agents/openai.yaml +0 -3
  67. package/skills/grill-me/LICENSE +0 -21
  68. package/skills/grill-me/SKILL.md +0 -7
  69. package/skills/grill-me/agents/openai.yaml +0 -5
  70. package/skills/grill-with-docs/LICENSE +0 -21
  71. package/skills/grill-with-docs/SKILL.md +0 -7
  72. package/skills/grill-with-docs/agents/openai.yaml +0 -5
  73. package/skills/grilling/LICENSE +0 -21
  74. package/skills/grilling/SKILL.md +0 -30
  75. package/skills/grilling/agents/openai.yaml +0 -3
  76. package/skills/handoff/LICENSE +0 -21
  77. package/skills/handoff/SKILL.md +0 -16
  78. package/skills/handoff/agents/openai.yaml +0 -5
  79. package/skills/implement/LICENSE +0 -21
  80. package/skills/implement/SKILL.md +0 -17
  81. package/skills/implement/agents/openai.yaml +0 -5
  82. package/skills/implement-spec/LICENSE +0 -21
  83. package/skills/implement-spec/SKILL.md +0 -40
  84. package/skills/implement-spec/agents/openai.yaml +0 -5
  85. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  86. package/skills/improve-codebase-architecture/LICENSE +0 -21
  87. package/skills/improve-codebase-architecture/SKILL.md +0 -71
  88. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  89. package/skills/pr/CREDITS.md +0 -3
  90. package/skills/pr/LICENSE +0 -21
  91. package/skills/pr/SKILL.md +0 -170
  92. package/skills/pr/agents/openai.yaml +0 -3
  93. package/skills/prototype/LICENSE +0 -21
  94. package/skills/prototype/LOGIC.md +0 -67
  95. package/skills/prototype/SKILL.md +0 -26
  96. package/skills/prototype/UI.md +0 -112
  97. package/skills/prototype/agents/openai.yaml +0 -3
  98. package/skills/research/LICENSE +0 -21
  99. package/skills/research/SKILL.md +0 -12
  100. package/skills/research/agents/openai.yaml +0 -3
  101. package/skills/retro/LICENSE +0 -21
  102. package/skills/retro/SKILL.md +0 -44
  103. package/skills/retro/agents/openai.yaml +0 -5
  104. package/skills/tdd/LICENSE +0 -21
  105. package/skills/tdd/SKILL.md +0 -38
  106. package/skills/tdd/agents/openai.yaml +0 -3
  107. package/skills/tdd/mocking.md +0 -59
  108. package/skills/tdd/tests.md +0 -77
  109. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  110. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  111. package/skills/teach/LICENSE +0 -21
  112. package/skills/teach/MISSION-FORMAT.md +0 -31
  113. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  114. package/skills/teach/SKILL.md +0 -140
  115. package/skills/teach/agents/openai.yaml +0 -5
  116. package/skills/to-questionnaire/LICENSE +0 -21
  117. package/skills/to-questionnaire/SKILL.md +0 -54
  118. package/skills/to-questionnaire/agents/openai.yaml +0 -5
  119. package/skills/to-spec/LICENSE +0 -21
  120. package/skills/to-spec/SKILL.md +0 -75
  121. package/skills/to-spec/agents/openai.yaml +0 -5
  122. package/skills/to-tickets/LICENSE +0 -21
  123. package/skills/to-tickets/SKILL.md +0 -105
  124. package/skills/to-tickets/agents/openai.yaml +0 -5
  125. package/skills/triage/AGENT-BRIEF.md +0 -207
  126. package/skills/triage/LICENSE +0 -21
  127. package/skills/triage/OUT-OF-SCOPE.md +0 -105
  128. package/skills/triage/SKILL.md +0 -112
  129. package/skills/triage/agents/openai.yaml +0 -5
  130. package/skills/wait-what/LICENSE +0 -21
  131. package/skills/wait-what/SKILL.md +0 -7
  132. package/skills/wait-what/agents/openai.yaml +0 -5
  133. package/skills/wayfinder/LICENSE +0 -21
  134. package/skills/wayfinder/SKILL.md +0 -128
  135. package/skills/wayfinder/agents/openai.yaml +0 -5
  136. package/skills/wizard/LICENSE +0 -21
  137. package/skills/wizard/SKILL.md +0 -44
  138. package/skills/wizard/agents/openai.yaml +0 -3
  139. package/skills/wizard/template.sh +0 -223
  140. package/skills/writing-for-agents/LICENSE +0 -21
  141. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  142. package/skills/writing-for-agents/SKILL.md +0 -81
  143. 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 { CONVERSATION_INPUTS, Conversations } from './conversations.js';
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, readEnrolment, 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 { holdStream } from './stream.js';
19
- import { Updates } from './update.js';
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 socketPath = join(kitHome(), 'owner.sock');
34
- await rm(socketPath, { force: true });
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.listen(socketPath);
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
- export async function resident() {
111
+ async function started() {
76
112
  await connected();
77
113
  await forgetRestart();
78
114
  watch(kitHome(), () => {
@@ -81,18 +117,25 @@ export async function resident() {
81
117
  killDescendants();
82
118
  process.exit(1);
83
119
  });
84
- await placeSkillSet();
120
+ await placeSkillSet((await readEnrolment()).house);
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
- let stream = null;
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 = () => conversations.idle() && signIns.idle() && agents.idle();
138
+ const idle = () => calls.idle() && conversations.idle() && agents.idle();
96
139
  const changed = () => {
97
140
  if (!idle()) {
98
141
  reported = false;
@@ -100,20 +143,26 @@ export async function resident() {
100
143
  else if (restartAsked() && conversations.quiet()) {
101
144
  restart('restarting for an updated CLI');
102
145
  }
146
+ else if (update.held() && conversations.quiet()) {
147
+ update.apply(connection.pause());
148
+ }
103
149
  else if (!reported) {
104
150
  reported = true;
105
151
  logged(house.deliver('/kit/idle', {}, idle));
106
152
  }
107
153
  };
154
+ const calls = new Calls(house, changed);
108
155
  const conversations = new Conversations({
109
156
  house,
110
157
  agents,
111
158
  copies,
112
- send: (frame) => stream?.send(frame) ?? false,
159
+ calls,
160
+ send: (notification) => connection?.send(notification) ?? false,
113
161
  changed,
114
162
  });
115
- const signIns = new SignIns(house, agents, changed);
116
- const updates = new Updates(house);
163
+ const signIns = new SignIns(house, agents, calls);
164
+ const update = new KitUpdate(calls, changed);
165
+ const wizards = new WebWizards(calls, (notification) => connection?.send(notification) ?? false);
117
166
  for (const signal of ['SIGTERM', 'SIGINT']) {
118
167
  process.once(signal, () => {
119
168
  killDescendants();
@@ -121,32 +170,37 @@ export async function resident() {
121
170
  });
122
171
  }
123
172
  await house.deliver('/kit/restarted', {}).catch(report);
124
- await updates.resumed().catch(report);
173
+ await update.resumed().catch(report);
125
174
  logged(agents.refresh());
126
- stream = holdStream({
175
+ connection = holdConnection({
127
176
  opened: () => {
128
177
  conversations.opened();
129
178
  changed();
130
179
  logged(copies.connected());
131
180
  },
132
- frame: (arrived) => {
133
- if (arrived.type === 'watching')
134
- conversations.watching(arrived.watching === true);
135
- if (arrived.type === 'work_available' && arrived.subject === 'agents')
136
- logged(agents.refresh());
137
- if (arrived.type === 'input') {
138
- reported = false;
139
- if (arrived.kind === 'sign_in')
140
- signIns.start(arrived);
141
- else if (arrived.kind === 'update')
142
- updates.start(arrived);
143
- else if (CONVERSATION_INPUTS.has(arrived.kind))
144
- conversations.input(arrived);
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
- }
181
+ closed: () => wizards.disconnected(),
182
+ call: (call) => {
183
+ if (call.kind === 'sign_in')
184
+ signIns.start(call);
185
+ else if (call.kind === 'update')
186
+ update.hold(call);
187
+ else if (call.kind === 'run_wizard')
188
+ wizards.start(call);
189
+ else if (CONVERSATION_METHODS.has(call.kind))
190
+ conversations.call(call);
191
+ else {
192
+ const refused = { refused: `This Kit does not know ${call.kind}; update the Kit` };
193
+ void calls.carry(call.input_id, async () => ({ result: refused }));
149
194
  }
150
195
  },
196
+ notification: (notification) => {
197
+ if (notification.type === 'watching')
198
+ conversations.watching(notification.watching === true);
199
+ if (notification.type === 'authority_changed')
200
+ logged(copies.changed(notification.rooms));
201
+ if (notification.type === 'work_available' && notification.subject === 'agents')
202
+ logged(agents.refresh());
203
+ wizards.notified(notification);
204
+ },
151
205
  });
152
206
  }
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 House Kit service could not be started: ${cause}`);
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, 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;
@@ -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
- changed;
22
- running = new Set();
23
- constructor(house, agents, changed) {
19
+ calls;
20
+ constructor(house, agents, calls) {
24
21
  this.house = house;
25
22
  this.agents = agents;
26
- this.changed = changed;
27
- }
28
- idle() {
29
- return this.running.size === 0;
23
+ this.calls = calls;
30
24
  }
31
- start(input) {
32
- if (this.running.has(input.input_id))
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(input) {
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(), input.ends_in * 1000);
47
- 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)]);
48
33
  clearTimeout(timer);
49
34
  if (ended)
50
- process.stderr.write(`kit: the ${input.cli} sign-in reached the end House set for it\n`);
51
- await this.house.deliver(`/kit/inputs/${input.input_id}/ack`, {});
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(input, signal) {
56
- await this.login(input, signal);
38
+ async signedIn(call, signal) {
39
+ await this.login(call, signal);
57
40
  if (!signal.aborted)
58
- await this.agents.recheck(input.cli, signal);
41
+ await this.agents.recheck(call.cli, signal);
59
42
  return false;
60
43
  }
61
- async login(input, signal) {
44
+ async login(call, signal) {
62
45
  let cli;
63
46
  try {
64
- cli = await this.agents.located(input.cli, signal);
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 ${input.cli} sign-in failed: ${causeOf(error)}\n`);
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[input.cli].login;
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: withoutProxy(),
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, input.input_id, steps, running).then((content) => {
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 ${input.cli} sign-in failed: ${failed}\n`);
105
+ process.stderr.write(`kit: the ${call.cli} sign-in failed: ${failed}\n`);
123
106
  }
124
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
+ }
package/dist/skills.js CHANGED
@@ -1,31 +1,14 @@
1
- import { createHash } from 'node:crypto';
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 { fileURLToPath } from 'node:url';
4
+ import { GUIDE, listSkills } from '@agentshouse/skills';
7
5
  import { parse } from 'yaml';
8
- import { KIT_VERSION } from './clis.js';
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 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: (.+)$/;
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
- async function install() {
47
- const names = [];
48
- for (const name of (await readdir(SKILL_SET)).sort()) {
49
- if (!(await present(join(skillsDirectory(), name))) && !(await present(join(claudeDirectory(), name)))) {
50
- names.push(name);
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 names) {
59
- await cp(join(SKILL_SET, name), join(skillsDirectory(), name), { recursive: true });
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_VERSION, 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_VERSION)
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
- }