@agentshouse/kit 0.2.1-alpha.2 → 0.2.1-alpha.21

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 (114) hide show
  1. package/README.md +39 -3
  2. package/dist/abort.js +9 -0
  3. package/dist/acp.js +27 -7
  4. package/dist/agents.js +204 -52
  5. package/dist/api.js +6 -1
  6. package/dist/authority-main.js +22 -0
  7. package/dist/bootstrap.json +31 -0
  8. package/dist/bridge.js +178 -54
  9. package/dist/clis-main.js +23 -0
  10. package/dist/clis.js +106 -32
  11. package/dist/configure-main.js +6 -0
  12. package/dist/conversations.js +128 -36
  13. package/dist/copies.js +809 -0
  14. package/dist/enrol-main.js +4 -0
  15. package/dist/git-main.js +153 -0
  16. package/dist/git.js +85 -0
  17. package/dist/home.js +36 -7
  18. package/dist/house-main.js +112 -41
  19. package/dist/instructions.js +16 -10
  20. package/dist/kit-main.js +2 -0
  21. package/dist/login.js +29 -5
  22. package/dist/mcp.js +38 -0
  23. package/dist/operation.js +12 -0
  24. package/dist/refusals.js +41 -0
  25. package/dist/resident.js +63 -7
  26. package/dist/sign-in.js +21 -5
  27. package/dist/skills.js +78 -0
  28. package/dist/transcription.js +122 -0
  29. package/dist/translate.js +51 -0
  30. package/dist/update.js +117 -0
  31. package/how-we-work.md +23 -0
  32. package/package.json +13 -9
  33. package/skills/ask-matt/LICENSE +21 -0
  34. package/skills/ask-matt/PHASE-BOUNDARIES.md +55 -0
  35. package/skills/ask-matt/SKILL.md +91 -0
  36. package/skills/ask-matt/agents/openai.yaml +4 -0
  37. package/skills/code-review/LICENSE +21 -0
  38. package/skills/code-review/SKILL.md +89 -0
  39. package/skills/code-review/agents/openai.yaml +3 -0
  40. package/skills/codebase-design/DEEPENING.md +37 -0
  41. package/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  42. package/skills/codebase-design/LICENSE +21 -0
  43. package/skills/codebase-design/SKILL.md +116 -0
  44. package/skills/codebase-design/agents/openai.yaml +3 -0
  45. package/skills/diagnosing-bugs/LICENSE +21 -0
  46. package/skills/diagnosing-bugs/SKILL.md +140 -0
  47. package/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  48. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  49. package/skills/domain-modeling/ADR-FORMAT.md +47 -0
  50. package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  51. package/skills/domain-modeling/LICENSE +21 -0
  52. package/skills/domain-modeling/SKILL.md +76 -0
  53. package/skills/domain-modeling/agents/openai.yaml +3 -0
  54. package/skills/grill-me/LICENSE +21 -0
  55. package/skills/grill-me/SKILL.md +8 -0
  56. package/skills/grill-me/agents/openai.yaml +4 -0
  57. package/skills/grill-with-docs/LICENSE +21 -0
  58. package/skills/grill-with-docs/SKILL.md +8 -0
  59. package/skills/grill-with-docs/agents/openai.yaml +4 -0
  60. package/skills/grilling/LICENSE +21 -0
  61. package/skills/grilling/SKILL.md +30 -0
  62. package/skills/grilling/agents/openai.yaml +3 -0
  63. package/skills/handoff/LICENSE +21 -0
  64. package/skills/handoff/SKILL.md +17 -0
  65. package/skills/handoff/agents/openai.yaml +4 -0
  66. package/skills/implement/LICENSE +21 -0
  67. package/skills/implement/SKILL.md +16 -0
  68. package/skills/implement/agents/openai.yaml +4 -0
  69. package/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  70. package/skills/improve-codebase-architecture/LICENSE +21 -0
  71. package/skills/improve-codebase-architecture/SKILL.md +72 -0
  72. package/skills/improve-codebase-architecture/agents/openai.yaml +4 -0
  73. package/skills/prototype/LICENSE +21 -0
  74. package/skills/prototype/LOGIC.md +67 -0
  75. package/skills/prototype/SKILL.md +28 -0
  76. package/skills/prototype/UI.md +112 -0
  77. package/skills/prototype/agents/openai.yaml +3 -0
  78. package/skills/research/LICENSE +21 -0
  79. package/skills/research/SKILL.md +14 -0
  80. package/skills/research/agents/openai.yaml +3 -0
  81. package/skills/resolving-merge-conflicts/LICENSE +21 -0
  82. package/skills/resolving-merge-conflicts/SKILL.md +16 -0
  83. package/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
  84. package/skills/tdd/LICENSE +21 -0
  85. package/skills/tdd/SKILL.md +40 -0
  86. package/skills/tdd/agents/openai.yaml +3 -0
  87. package/skills/tdd/mocking.md +59 -0
  88. package/skills/tdd/tests.md +77 -0
  89. package/skills/teach/GLOSSARY-FORMAT.md +35 -0
  90. package/skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
  91. package/skills/teach/LICENSE +21 -0
  92. package/skills/teach/MISSION-FORMAT.md +31 -0
  93. package/skills/teach/RESOURCES-FORMAT.md +32 -0
  94. package/skills/teach/SKILL.md +141 -0
  95. package/skills/teach/agents/openai.yaml +4 -0
  96. package/skills/to-questionnaire/LICENSE +21 -0
  97. package/skills/to-questionnaire/SKILL.md +55 -0
  98. package/skills/to-questionnaire/agents/openai.yaml +4 -0
  99. package/skills/to-spec/LICENSE +21 -0
  100. package/skills/to-spec/SKILL.md +76 -0
  101. package/skills/to-spec/agents/openai.yaml +4 -0
  102. package/skills/to-tickets/LICENSE +21 -0
  103. package/skills/to-tickets/SKILL.md +106 -0
  104. package/skills/to-tickets/agents/openai.yaml +4 -0
  105. package/skills/wait-what/LICENSE +21 -0
  106. package/skills/wait-what/SKILL.md +8 -0
  107. package/skills/wait-what/agents/openai.yaml +4 -0
  108. package/skills/wayfinder/LICENSE +21 -0
  109. package/skills/wayfinder/SKILL.md +129 -0
  110. package/skills/wayfinder/agents/openai.yaml +4 -0
  111. package/skills/writing-for-agents/LICENSE +21 -0
  112. package/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
  113. package/skills/writing-for-agents/SKILL.md +83 -0
  114. package/skills/writing-for-agents/agents/openai.yaml +3 -0
@@ -2,14 +2,17 @@ import { client, } from '@agentclientprotocol/sdk';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { mkdir } from 'node:fs/promises';
4
4
  import { join } from 'node:path';
5
+ import { unlessAborted } from './abort.js';
5
6
  import { killMarked, startAdapter, TURN_ENDED, TURN_STARTED } from './acp.js';
6
7
  import { blocksOf, firstChange } from './blocks.js';
7
8
  import { openBridge } from './bridge.js';
8
9
  import { CLIS } from './clis.js';
9
10
  import { placeFiles } from './files.js';
11
+ import { agentBase } from './home.js';
10
12
  import { instructions } from './instructions.js';
11
13
  import { holdSecretInput } from './secret-input.js';
12
- const LAUNCH_BASE = '/agents/house';
14
+ import { createHowWeWork } from './skills.js';
15
+ import { transcribe } from './transcription.js';
13
16
  const KILLED = 'the conversation was killed';
14
17
  function causeOf(error) {
15
18
  return error instanceof Error ? error.message : String(error);
@@ -20,30 +23,24 @@ function answerMessage(request, response) {
20
23
  function logged(error) {
21
24
  process.stderr.write(`kit: ${causeOf(error)}\n`);
22
25
  }
23
- function unlessKilled(work, signal) {
24
- return new Promise((resolve, reject) => {
25
- const killed = () => reject(signal.reason);
26
- signal.addEventListener('abort', killed, { once: true });
27
- if (signal.aborted)
28
- killed();
29
- work.then(resolve, reject).finally(() => signal.removeEventListener('abort', killed));
30
- });
31
- }
32
26
  function stop(bridge) {
33
27
  killMarked(`HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`);
34
28
  }
35
29
  class Turn {
36
30
  id = randomUUID();
37
31
  text = '';
32
+ segment = null;
33
+ admitted = false;
34
+ notes = [];
38
35
  sent = [];
39
36
  unsentFrom = null;
40
37
  draftSequence = 0;
41
38
  planSequence = 0;
39
+ entries = null;
42
40
  questions = 0;
43
41
  secrets = 0;
44
42
  prompt = null;
45
43
  ended = false;
46
- ready = Promise.resolve();
47
44
  }
48
45
  class Conversation {
49
46
  id;
@@ -56,6 +53,8 @@ class Conversation {
56
53
  opening = null;
57
54
  kills = 0;
58
55
  killed = new AbortController();
56
+ interrupts = 0;
57
+ interrupted = new AbortController();
59
58
  open = 0;
60
59
  queue = Promise.resolve();
61
60
  reports = Promise.resolve();
@@ -91,6 +90,10 @@ export class Conversations {
91
90
  conversation.killed.abort(new Error(KILLED));
92
91
  this.halt(conversation);
93
92
  }
93
+ if (input.kind === 'interrupt') {
94
+ conversation.interrupts++;
95
+ conversation.interrupted.abort();
96
+ }
94
97
  const work = conversation.queue.then(() => this.carry(conversation, input));
95
98
  conversation.queue = work.catch(() => undefined);
96
99
  this.acks.set(input.input_id, work);
@@ -134,8 +137,11 @@ export class Conversations {
134
137
  return this.open(conversation, input);
135
138
  if (input.kind === 'message')
136
139
  return this.message(conversation, input);
137
- if (input.kind === 'interrupt')
140
+ if (input.kind === 'interrupt') {
141
+ if (--conversation.interrupts === 0)
142
+ conversation.interrupted = new AbortController();
138
143
  await this.interrupt(conversation, String(input.turn_id));
144
+ }
139
145
  if (input.kind === 'kill') {
140
146
  await this.kill(conversation);
141
147
  if (--conversation.kills === 0)
@@ -167,18 +173,23 @@ export class Conversations {
167
173
  const prompt = [];
168
174
  try {
169
175
  const route = await this.route(conversation, input.agent_id);
170
- const paths = await placeFiles(this.kit.house, route.working_directory, input.files, conversation.killed.signal);
176
+ const files = input.files;
177
+ const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
178
+ const lines = await this.transcribed(conversation, files, paths);
171
179
  if (conversation.running === null) {
172
180
  const opened = await this.start(conversation, route, input.provider_session_id);
173
181
  if (input.provider_session_id === null)
174
182
  ack = { provider_session_id: opened };
175
183
  }
176
184
  if (input.first === true) {
177
- prompt.push({ type: 'text', text: await instructions(conversation.running.bridge, route.base_instructions) });
185
+ prompt.push({
186
+ type: 'text',
187
+ text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
188
+ });
178
189
  }
179
190
  prompt.push({ type: 'text', text: String(input.text) });
180
- if (paths.length > 0)
181
- prompt.push({ type: 'text', text: paths.join('\n') });
191
+ if (lines.length > 0)
192
+ prompt.push({ type: 'text', text: lines.join('\n') });
182
193
  if (conversation.kills > 0)
183
194
  throw new Error(KILLED);
184
195
  }
@@ -190,6 +201,25 @@ export class Conversations {
190
201
  this.prompt(conversation, prompt);
191
202
  return ack;
192
203
  }
204
+ async transcribed(conversation, files, paths) {
205
+ const lines = [];
206
+ for (const [index, path] of paths.entries()) {
207
+ lines.push(path);
208
+ if (files[index].media_type?.startsWith('audio/') !== true)
209
+ continue;
210
+ try {
211
+ const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
212
+ if (text !== '')
213
+ lines.push(`Transcript: ${text}`);
214
+ }
215
+ catch (error) {
216
+ if (conversation.kills > 0)
217
+ throw error;
218
+ logged(new Error(`${files[index].name} was not transcribed: ${causeOf(error)}`));
219
+ }
220
+ }
221
+ return lines;
222
+ }
193
223
  async interrupt(conversation, turnId) {
194
224
  if (conversation.running === null || conversation.turn?.id !== turnId)
195
225
  return;
@@ -228,13 +258,13 @@ export class Conversations {
228
258
  }
229
259
  async route(conversation, agentId) {
230
260
  const signal = conversation.killed.signal;
231
- await unlessKilled(this.kit.agents.read(), signal);
261
+ await unlessAborted(this.kit.agents.read(), signal);
232
262
  if (this.kit.agents.route(agentId) === undefined)
233
- await unlessKilled(this.kit.agents.refresh(), signal);
263
+ await unlessAborted(this.kit.agents.refresh(), signal);
234
264
  const route = this.kit.agents.route(agentId);
235
265
  if (route === undefined)
236
266
  throw new Error(`this Environment hosts no Agent ${agentId}`);
237
- if (route.working_directory === join(LAUNCH_BASE, route.agent_id)) {
267
+ if (route.working_directory === join(agentBase(), route.agent_id)) {
238
268
  await mkdir(route.working_directory, { recursive: true });
239
269
  }
240
270
  return route;
@@ -246,15 +276,16 @@ export class Conversations {
246
276
  .onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
247
277
  .onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
248
278
  .onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal));
249
- await unlessKilled(this.kit.agents.installed(), conversation.killed.signal);
250
- const bridge = await openBridge(this.kit.house, conversation.id, conversation.killed.signal);
279
+ const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
280
+ const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
251
281
  if (conversation.kills > 0) {
252
282
  bridge.close();
253
283
  throw new Error(KILLED);
254
284
  }
255
285
  conversation.opening = bridge;
256
286
  try {
257
- const adapter = await startAdapter(route.kind, route.working_directory, app, (job) => this.job(conversation, job), { ...process.env, ...bridge.env });
287
+ await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
288
+ const adapter = await startAdapter(route.kind, cli, route.working_directory, app, (job) => this.job(conversation, job), bridge.env);
258
289
  void adapter.exited.then(() => bridge.close());
259
290
  const agent = adapter.connection.agent;
260
291
  const cwd = route.working_directory;
@@ -267,6 +298,7 @@ export class Conversations {
267
298
  sessionId: opened.sessionId,
268
299
  killed: false,
269
300
  queues: CLIS[route.kind].queues,
301
+ phase: CLIS[route.kind].phase,
270
302
  };
271
303
  const options = await this.launchSettings(running, route, opened.configOptions ?? []);
272
304
  conversation.running = running;
@@ -348,15 +380,39 @@ export class Conversations {
348
380
  report(conversation) {
349
381
  const turn = new Turn();
350
382
  conversation.open++;
351
- turn.ready = this.deliver(conversation, turn, 'started', {}).then(() => undefined, logged);
383
+ void this.deliver(conversation, turn, 'started', {})
384
+ .then(() => this.admitted(conversation, turn))
385
+ .catch(logged);
352
386
  return turn;
353
387
  }
388
+ admitted(conversation, turn) {
389
+ turn.admitted = true;
390
+ for (const note of turn.notes.splice(0))
391
+ this.kit.send(note);
392
+ if (turn.text !== '') {
393
+ turn.unsentFrom = 0;
394
+ this.draft(conversation, turn);
395
+ }
396
+ if (turn.entries !== null)
397
+ this.plan(conversation, turn);
398
+ }
354
399
  begin(conversation) {
355
400
  const turn = this.report(conversation);
356
401
  conversation.turn = turn;
357
402
  this.kit.changed();
358
403
  return turn;
359
404
  }
405
+ finish(conversation, turn) {
406
+ if (turn.ended)
407
+ return;
408
+ const segment = turn.segment;
409
+ turn.segment = null;
410
+ if (segment?.phase === 'note')
411
+ this.note(conversation, turn, segment.text);
412
+ else if (segment !== null)
413
+ this.answer(conversation, turn, segment.text);
414
+ this.end(conversation, turn, { text: turn.text });
415
+ }
360
416
  end(conversation, turn, outcome) {
361
417
  if (turn.ended)
362
418
  return;
@@ -414,7 +470,7 @@ export class Conversations {
414
470
  this.end(conversation, turn, { failed });
415
471
  }
416
472
  else if (turn.prompt === sent) {
417
- this.end(conversation, turn, { text: turn.text });
473
+ this.finish(conversation, turn);
418
474
  }
419
475
  }
420
476
  started(conversation) {
@@ -431,7 +487,7 @@ export class Conversations {
431
487
  if (turn === null)
432
488
  return;
433
489
  if (turn.prompt === null) {
434
- this.end(conversation, turn, { text: turn.text });
490
+ this.finish(conversation, turn);
435
491
  }
436
492
  else {
437
493
  conversation.turn = null;
@@ -485,17 +541,21 @@ export class Conversations {
485
541
  update(conversation, notification) {
486
542
  const update = notification.update;
487
543
  if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {
488
- const turn = this.started(conversation);
489
- turn.text += update.content.text;
490
- const text = turn.text;
491
- void turn.ready.then(() => this.draft(conversation, turn, text));
544
+ this.chunk(conversation, this.started(conversation), update, update.content.text);
492
545
  }
493
546
  else if (update.sessionUpdate === 'plan') {
494
547
  const turn = this.started(conversation);
495
- void turn.ready.then(() => this.plan(conversation, turn, update.entries));
548
+ this.close(conversation, turn);
549
+ turn.entries = update.entries;
550
+ this.plan(conversation, turn);
551
+ }
552
+ else if (update.sessionUpdate === 'tool_call') {
553
+ this.close(conversation, this.started(conversation));
496
554
  }
497
- else if (update.sessionUpdate === 'agent_thought_chunk' || update.sessionUpdate === 'tool_call') {
498
- this.started(conversation);
555
+ else if (update.sessionUpdate === 'agent_thought_chunk') {
556
+ const turn = this.started(conversation);
557
+ if (turn.segment?.messageId !== (update.messageId ?? null))
558
+ this.close(conversation, turn);
499
559
  }
500
560
  else if (update.sessionUpdate === 'available_commands_update') {
501
561
  this.commands(conversation, update.availableCommands);
@@ -511,8 +571,40 @@ export class Conversations {
511
571
  conversation.jobs.delete(job.id);
512
572
  this.kit.changed();
513
573
  }
514
- draft(conversation, turn, text) {
515
- const blocks = blocksOf(text);
574
+ chunk(conversation, turn, chunk, text) {
575
+ const phase = conversation.running?.phase(chunk) ?? null;
576
+ const messageId = chunk.messageId ?? null;
577
+ if (turn.segment?.messageId !== messageId || turn.segment.phase !== phase)
578
+ this.close(conversation, turn);
579
+ if (phase === 'answer') {
580
+ this.answer(conversation, turn, text);
581
+ return;
582
+ }
583
+ turn.segment ??= { messageId, phase, text: '' };
584
+ turn.segment.text += text;
585
+ }
586
+ close(conversation, turn) {
587
+ const segment = turn.segment;
588
+ if (segment === null)
589
+ return;
590
+ turn.segment = null;
591
+ this.note(conversation, turn, segment.text);
592
+ }
593
+ note(conversation, turn, text) {
594
+ if (text.trim() === '')
595
+ return;
596
+ const note = { type: 'working-note', conversation_id: conversation.id, note_id: randomUUID(), text };
597
+ if (turn.admitted)
598
+ this.kit.send(note);
599
+ else
600
+ turn.notes.push(note);
601
+ }
602
+ answer(conversation, turn, text) {
603
+ turn.text += text;
604
+ this.draft(conversation, turn);
605
+ }
606
+ draft(conversation, turn) {
607
+ const blocks = blocksOf(turn.text);
516
608
  const from = Math.min(firstChange(turn.sent, blocks), turn.unsentFrom ?? Infinity);
517
609
  const sent = this.kit.send({
518
610
  type: 'draft',
@@ -531,14 +623,14 @@ export class Conversations {
531
623
  turn.unsentFrom = from;
532
624
  }
533
625
  }
534
- plan(conversation, turn, entries) {
626
+ plan(conversation, turn) {
535
627
  const sent = this.kit.send({
536
628
  type: 'plan',
537
629
  conversation_id: conversation.id,
538
630
  turn_id: turn.id,
539
631
  sequence: turn.planSequence + 1,
540
632
  from: 0,
541
- steps: entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
633
+ steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
542
634
  });
543
635
  if (sent)
544
636
  turn.planSequence++;