@agentshouse/kit 0.2.1-c845e172df1af → 0.2.1-c849d8d4ab62f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/acp.js +57 -6
- package/dist/agents.js +6 -9
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +39 -15
- package/dist/connection.js +20 -29
- package/dist/conversations.js +235 -81
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +53 -40
- package/dist/git.js +2 -2
- package/dist/house-cli.js +31 -59
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -6
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +41 -96
- package/dist/transcription.js +83 -43
- package/dist/update.js +51 -8
- package/package.json +19 -20
- package/dist/bootstrap.json +0 -24
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { client,
|
|
1
|
+
import { client, } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
|
-
import { startAdapter } from './acp.js';
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
7
|
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
10
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
-
import {
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
12
12
|
import { agentBase } from './home.js';
|
|
13
13
|
import { instructions } from './instructions.js';
|
|
14
14
|
import { Parts } from './parts.js';
|
|
@@ -16,11 +16,10 @@ import { refusalOf } from './refusals.js';
|
|
|
16
16
|
import { relayed } from './relay.js';
|
|
17
17
|
import { killMarked, Scope } from './scope.js';
|
|
18
18
|
import { holdSecretInput } from './secret-input.js';
|
|
19
|
-
import { createHowWeWork } from './skills.js';
|
|
20
19
|
import { transcribe } from './transcription.js';
|
|
21
20
|
import { workFor } from './work.js';
|
|
22
|
-
const SIGNED_OUT = RequestError.authRequired().code;
|
|
23
21
|
const KILLED = 'the conversation was killed';
|
|
22
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
24
23
|
const REFUSED = 'the CLI refused the prompt';
|
|
25
24
|
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
26
25
|
const GONE = 'House no longer holds this conversation; start a new one';
|
|
@@ -34,8 +33,11 @@ const NO_SESSION = "the Agent's CLI did not open a session";
|
|
|
34
33
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
35
34
|
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
36
35
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
37
37
|
class NotStarted extends Error {
|
|
38
38
|
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
39
41
|
async function step(reason, work) {
|
|
40
42
|
try {
|
|
41
43
|
return await work;
|
|
@@ -44,9 +46,6 @@ async function step(reason, work) {
|
|
|
44
46
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
function signedOut(error) {
|
|
48
|
-
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
49
|
-
}
|
|
50
49
|
function unopened(reason) {
|
|
51
50
|
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
52
51
|
}
|
|
@@ -63,7 +62,7 @@ const EXIT_PLAN_MODE = {
|
|
|
63
62
|
return value;
|
|
64
63
|
},
|
|
65
64
|
};
|
|
66
|
-
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'done', 'option', 'answer']);
|
|
67
66
|
function causeOf(error) {
|
|
68
67
|
return error instanceof Error ? error.message : String(error);
|
|
69
68
|
}
|
|
@@ -71,13 +70,15 @@ function answeredWith(error) {
|
|
|
71
70
|
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
72
71
|
}
|
|
73
72
|
function exitedWith(running, cause) {
|
|
74
|
-
|
|
73
|
+
if (running.killed)
|
|
74
|
+
return { failure: 'killed', cause: KILLED };
|
|
75
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
75
76
|
}
|
|
76
77
|
function answerMessage(request, response) {
|
|
77
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
78
79
|
}
|
|
79
|
-
async function sentPart(url, bytes) {
|
|
80
|
-
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
80
|
+
async function sentPart(url, bytes, headers) {
|
|
81
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
81
82
|
await answer.arrayBuffer();
|
|
82
83
|
if (!answer.ok)
|
|
83
84
|
throw storeRefusal('upload', answer.status);
|
|
@@ -88,19 +89,29 @@ function logged(error) {
|
|
|
88
89
|
function refusal(conversation, error) {
|
|
89
90
|
if (conversation.kills > 0)
|
|
90
91
|
return KILLED;
|
|
91
|
-
const cause = error instanceof
|
|
92
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
92
93
|
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
93
94
|
return causeOf(error);
|
|
94
95
|
}
|
|
96
|
+
function audioOf(result) {
|
|
97
|
+
if (result.isError === true)
|
|
98
|
+
return null;
|
|
99
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
100
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
101
|
+
}
|
|
95
102
|
function marker(bridge) {
|
|
96
103
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
97
104
|
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
107
|
+
}
|
|
98
108
|
class Turn {
|
|
99
109
|
id = randomUUID();
|
|
100
110
|
parts;
|
|
101
111
|
questions = 0;
|
|
102
112
|
secrets = 0;
|
|
103
113
|
prompted = false;
|
|
114
|
+
steered = false;
|
|
104
115
|
taken = null;
|
|
105
116
|
ended = false;
|
|
106
117
|
images = 0;
|
|
@@ -120,17 +131,19 @@ class Conversation {
|
|
|
120
131
|
context = null;
|
|
121
132
|
leftovers = new Set();
|
|
122
133
|
waiting = [];
|
|
134
|
+
done = false;
|
|
123
135
|
sending = null;
|
|
124
136
|
subagents = new Map();
|
|
125
137
|
held = [];
|
|
126
138
|
opening = null;
|
|
139
|
+
bridge = null;
|
|
127
140
|
launch = null;
|
|
128
141
|
prompted = null;
|
|
129
142
|
kills = 0;
|
|
130
143
|
killed = new AbortController();
|
|
131
144
|
interrupts = 0;
|
|
132
145
|
interrupted = new AbortController();
|
|
133
|
-
|
|
146
|
+
turns = new Set();
|
|
134
147
|
queue = Promise.resolve();
|
|
135
148
|
reports = Promise.resolve();
|
|
136
149
|
starting = null;
|
|
@@ -186,7 +199,7 @@ export class Conversations {
|
|
|
186
199
|
for (const conversation of this.conversations.values()) {
|
|
187
200
|
const turn = conversation.turn;
|
|
188
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
189
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
190
203
|
return false;
|
|
191
204
|
}
|
|
192
205
|
const conversations = [...this.conversations.values()];
|
|
@@ -195,7 +208,7 @@ export class Conversations {
|
|
|
195
208
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
196
209
|
}
|
|
197
210
|
quiet() {
|
|
198
|
-
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
199
212
|
}
|
|
200
213
|
look() {
|
|
201
214
|
for (const conversation of this.conversations.values()) {
|
|
@@ -211,8 +224,10 @@ export class Conversations {
|
|
|
211
224
|
opened() {
|
|
212
225
|
this.watched = false;
|
|
213
226
|
for (const conversation of this.conversations.values()) {
|
|
214
|
-
if (conversation.running !== null)
|
|
227
|
+
if (conversation.running !== null) {
|
|
215
228
|
this.facts(conversation);
|
|
229
|
+
this.subagentsChanged(conversation);
|
|
230
|
+
}
|
|
216
231
|
else if (conversation.exitUntold)
|
|
217
232
|
this.process(conversation, false);
|
|
218
233
|
if (conversation.untold || conversation.told !== null)
|
|
@@ -239,9 +254,14 @@ export class Conversations {
|
|
|
239
254
|
}
|
|
240
255
|
async carry(conversation, call) {
|
|
241
256
|
if (call.kind === 'open')
|
|
242
|
-
return
|
|
257
|
+
return this.open(conversation, call);
|
|
243
258
|
if (call.kind === 'message')
|
|
244
259
|
return this.message(conversation, call);
|
|
260
|
+
if (call.kind === 'done') {
|
|
261
|
+
conversation.done = true;
|
|
262
|
+
this.next(conversation);
|
|
263
|
+
return { result: Promise.resolve({}) };
|
|
264
|
+
}
|
|
245
265
|
await this.launched(conversation);
|
|
246
266
|
if (call.kind === 'interrupt') {
|
|
247
267
|
if (--conversation.interrupts === 0)
|
|
@@ -260,17 +280,23 @@ export class Conversations {
|
|
|
260
280
|
const question = this.questions.get(String(call.interaction_id));
|
|
261
281
|
if (question === undefined) {
|
|
262
282
|
const text = answerMessage(call.request, call.response);
|
|
263
|
-
return this.message(conversation, { ...call, text,
|
|
283
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
264
284
|
}
|
|
265
285
|
question.answer(call.response);
|
|
266
286
|
}
|
|
267
287
|
return { result: Promise.resolve({}) };
|
|
268
288
|
}
|
|
269
|
-
open(conversation, call) {
|
|
270
|
-
|
|
271
|
-
return Promise.resolve({});
|
|
289
|
+
async open(conversation, call) {
|
|
290
|
+
await this.stop(conversation);
|
|
272
291
|
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
273
|
-
return
|
|
292
|
+
return {
|
|
293
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async stop(conversation) {
|
|
297
|
+
const running = conversation.running;
|
|
298
|
+
running?.scope.kill();
|
|
299
|
+
await running?.adapter.exited;
|
|
274
300
|
}
|
|
275
301
|
launch(conversation, route, sessionId, empty) {
|
|
276
302
|
let opened;
|
|
@@ -290,6 +316,7 @@ export class Conversations {
|
|
|
290
316
|
if (conversation.launch !== launch)
|
|
291
317
|
return;
|
|
292
318
|
conversation.launch = null;
|
|
319
|
+
this.closeIdleBridge(conversation);
|
|
293
320
|
this.next(conversation);
|
|
294
321
|
});
|
|
295
322
|
this.next(conversation);
|
|
@@ -308,43 +335,92 @@ export class Conversations {
|
|
|
308
335
|
return read;
|
|
309
336
|
}
|
|
310
337
|
async message(conversation, call) {
|
|
311
|
-
const content = [
|
|
338
|
+
const content = [
|
|
339
|
+
...(typeof call.recent === 'string' ? [{ type: 'text', text: call.recent }] : []),
|
|
340
|
+
{ type: 'text', text: String(call.text) },
|
|
341
|
+
];
|
|
312
342
|
let route;
|
|
313
343
|
let instructing = null;
|
|
314
344
|
try {
|
|
315
345
|
route = await this.route(conversation, call.agent_id);
|
|
316
346
|
if (call.first === true)
|
|
317
347
|
instructing = this.instructed(conversation, route);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.prepares(conversation, 'files');
|
|
321
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
322
|
-
const lines = await this.transcribed(conversation, files, paths);
|
|
323
|
-
if (lines.length > 0)
|
|
324
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
348
|
+
if (call.message_id !== null)
|
|
349
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
325
350
|
if (conversation.kills > 0)
|
|
326
351
|
throw new Error(KILLED);
|
|
327
352
|
}
|
|
328
353
|
catch (error) {
|
|
354
|
+
this.closeIdleBridge(conversation);
|
|
329
355
|
this.prepares(conversation, null);
|
|
330
356
|
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
331
357
|
}
|
|
332
358
|
conversation.preparing = null;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
359
|
+
const { promise, resolve: settle } = Promise.withResolvers();
|
|
360
|
+
const waiting = { call, route, content, instructing, settle };
|
|
361
|
+
if (call.at_once === true && conversation.turns.size > 0)
|
|
362
|
+
await this.atOnce(conversation, waiting);
|
|
363
|
+
else
|
|
364
|
+
conversation.waiting.push(waiting);
|
|
365
|
+
this.next(conversation);
|
|
366
|
+
return { result: promise };
|
|
367
|
+
}
|
|
368
|
+
async atOnce(conversation, waiting) {
|
|
369
|
+
while (conversation.sending !== null)
|
|
370
|
+
await conversation.sending;
|
|
371
|
+
const running = conversation.running;
|
|
372
|
+
if (running !== null && conversation.turns.size > 0 && steers(running.adapter)) {
|
|
373
|
+
conversation.sending = this.steer(conversation, running, waiting);
|
|
374
|
+
await conversation.sending;
|
|
375
|
+
conversation.sending = null;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.ahead(conversation, waiting);
|
|
379
|
+
if (conversation.turns.size > 0)
|
|
380
|
+
await this.cancel(conversation);
|
|
381
|
+
}
|
|
382
|
+
ahead(conversation, waiting) {
|
|
383
|
+
const behind = conversation.waiting.findIndex((held) => held.call.at_once !== true);
|
|
384
|
+
conversation.waiting.splice(behind === -1 ? conversation.waiting.length : behind, 0, waiting);
|
|
385
|
+
}
|
|
386
|
+
async steer(conversation, running, waiting) {
|
|
387
|
+
let outcome;
|
|
388
|
+
try {
|
|
389
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
390
|
+
sessionId: running.sessionId,
|
|
391
|
+
prompt: waiting.content,
|
|
392
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
outcome = error;
|
|
397
|
+
}
|
|
398
|
+
if (outcome === 'promptRequired') {
|
|
399
|
+
this.ahead(conversation, waiting);
|
|
400
|
+
}
|
|
401
|
+
else if (outcome === 'injected' || outcome === 'startedNewTurn') {
|
|
402
|
+
if (outcome === 'injected') {
|
|
403
|
+
for (const turn of conversation.turns)
|
|
404
|
+
turn.steered ||= turn.prompted;
|
|
405
|
+
}
|
|
406
|
+
this.kit.calls.keepResult(String(waiting.call.input_id), {});
|
|
407
|
+
waiting.settle({});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const cause = outcome instanceof Error ? outcome : new Error(`the CLI answered ${JSON.stringify(outcome)}`);
|
|
411
|
+
waiting.settle({ refused: refusal(conversation, new NotStarted(UNSTEERED, { cause })) });
|
|
412
|
+
}
|
|
339
413
|
}
|
|
340
414
|
next(conversation) {
|
|
341
|
-
if (conversation.sending !== null || conversation.
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
342
416
|
return;
|
|
343
417
|
const waiting = conversation.waiting.shift();
|
|
344
418
|
if (waiting === undefined) {
|
|
345
419
|
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
346
420
|
if (conversation.starting !== step)
|
|
347
421
|
this.starts(conversation, step);
|
|
422
|
+
if (conversation.done && conversation.launch === null)
|
|
423
|
+
void this.stop(conversation).catch(logged);
|
|
348
424
|
return;
|
|
349
425
|
}
|
|
350
426
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
@@ -387,7 +463,9 @@ export class Conversations {
|
|
|
387
463
|
conversation.session = null;
|
|
388
464
|
await this.kill(conversation);
|
|
389
465
|
}
|
|
390
|
-
|
|
466
|
+
const refused = refusal(conversation, error);
|
|
467
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
468
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
391
469
|
return;
|
|
392
470
|
}
|
|
393
471
|
this.starts(conversation, null);
|
|
@@ -422,28 +500,72 @@ export class Conversations {
|
|
|
422
500
|
retell(conversation) {
|
|
423
501
|
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
424
502
|
}
|
|
425
|
-
async transcribed(conversation,
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
503
|
+
async transcribed(conversation, messageId, text) {
|
|
504
|
+
const paths = linkedPaths(text);
|
|
505
|
+
if (paths.length === 0)
|
|
506
|
+
return [];
|
|
507
|
+
const killed = conversation.killed.signal;
|
|
508
|
+
const interrupted = conversation.interrupted.signal;
|
|
509
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
510
|
+
let signal = killed;
|
|
511
|
+
const blocks = [];
|
|
512
|
+
try {
|
|
513
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
514
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
515
|
+
for (const path of paths) {
|
|
516
|
+
let answered = await read(path);
|
|
517
|
+
if (answered === null)
|
|
518
|
+
continue;
|
|
519
|
+
signal = transcribing;
|
|
520
|
+
this.prepares(conversation, 'transcription');
|
|
521
|
+
const said = await transcribe(async () => {
|
|
522
|
+
const address = answered ?? (await read(path));
|
|
523
|
+
answered = null;
|
|
524
|
+
if (address === null)
|
|
525
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
526
|
+
return address;
|
|
527
|
+
}, signal);
|
|
528
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
529
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
530
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
441
531
|
}
|
|
442
532
|
}
|
|
443
|
-
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
535
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
return blocks;
|
|
539
|
+
}
|
|
540
|
+
bridged(conversation) {
|
|
541
|
+
if (conversation.bridge === null) {
|
|
542
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
543
|
+
conversation.bridge = bridging;
|
|
544
|
+
bridging.catch(() => {
|
|
545
|
+
if (conversation.bridge === bridging)
|
|
546
|
+
conversation.bridge = null;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return conversation.bridge;
|
|
550
|
+
}
|
|
551
|
+
closeBridge(conversation, bridging, bridge) {
|
|
552
|
+
bridge.close();
|
|
553
|
+
if (conversation.bridge === bridging)
|
|
554
|
+
conversation.bridge = null;
|
|
555
|
+
}
|
|
556
|
+
closeIdleBridge(conversation) {
|
|
557
|
+
const bridging = conversation.bridge;
|
|
558
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
559
|
+
return;
|
|
560
|
+
conversation.bridge = null;
|
|
561
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
444
562
|
}
|
|
445
563
|
async interrupt(conversation, turnId) {
|
|
446
|
-
if (conversation.
|
|
564
|
+
if ([...conversation.turns].some((turn) => turn.id === turnId))
|
|
565
|
+
await this.cancel(conversation);
|
|
566
|
+
}
|
|
567
|
+
async cancel(conversation) {
|
|
568
|
+
if (conversation.running === null)
|
|
447
569
|
return;
|
|
448
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
449
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -504,9 +626,10 @@ export class Conversations {
|
|
|
504
626
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
505
627
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
506
628
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
507
|
-
const
|
|
629
|
+
const bridging = this.bridged(conversation);
|
|
630
|
+
const bridge = await step(unbridged, bridging);
|
|
508
631
|
if (conversation.kills > 0) {
|
|
509
|
-
|
|
632
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
510
633
|
throw new Error(KILLED);
|
|
511
634
|
}
|
|
512
635
|
conversation.opening = bridge;
|
|
@@ -514,7 +637,6 @@ export class Conversations {
|
|
|
514
637
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
515
638
|
let adapter = null;
|
|
516
639
|
try {
|
|
517
|
-
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
518
640
|
bridged(bridge);
|
|
519
641
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
520
642
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
@@ -522,7 +644,7 @@ export class Conversations {
|
|
|
522
644
|
work.read(message);
|
|
523
645
|
this.read(conversation, reader, message);
|
|
524
646
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
525
|
-
void adapter.exited.then(() =>
|
|
647
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
526
648
|
const agent = adapter.connection.agent;
|
|
527
649
|
const cwd = route.working_directory;
|
|
528
650
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
@@ -533,10 +655,11 @@ export class Conversations {
|
|
|
533
655
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
534
656
|
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
535
657
|
}
|
|
536
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
658
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
537
659
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
538
660
|
void work.settled().then(() => scope.record());
|
|
539
661
|
conversation.running = running;
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
540
663
|
conversation.session = running.sessionId;
|
|
541
664
|
conversation.options = options;
|
|
542
665
|
this.facts(conversation);
|
|
@@ -547,7 +670,7 @@ export class Conversations {
|
|
|
547
670
|
adapter?.retire();
|
|
548
671
|
work.close();
|
|
549
672
|
conversation.leftovers.add(scope);
|
|
550
|
-
|
|
673
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
551
674
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
552
675
|
}
|
|
553
676
|
finally {
|
|
@@ -560,7 +683,7 @@ export class Conversations {
|
|
|
560
683
|
}
|
|
561
684
|
catch (error) {
|
|
562
685
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
563
|
-
throw new NotStarted(
|
|
686
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
564
687
|
}
|
|
565
688
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
566
689
|
return null;
|
|
@@ -596,11 +719,17 @@ export class Conversations {
|
|
|
596
719
|
this.kit.changed();
|
|
597
720
|
}
|
|
598
721
|
detach(conversation, running) {
|
|
722
|
+
if (conversation.bridge === running.bridging)
|
|
723
|
+
conversation.bridge = null;
|
|
599
724
|
running.work.close();
|
|
600
725
|
conversation.leftovers.add(running.scope);
|
|
601
726
|
conversation.running = null;
|
|
602
727
|
conversation.commands = null;
|
|
603
728
|
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
604
733
|
for (const [id, question] of this.questions) {
|
|
605
734
|
if (question.conversation === conversation)
|
|
606
735
|
this.questions.delete(id);
|
|
@@ -617,6 +746,10 @@ export class Conversations {
|
|
|
617
746
|
if (conversation.options !== null)
|
|
618
747
|
this.options(conversation, conversation.options);
|
|
619
748
|
}
|
|
749
|
+
subagentsChanged(conversation) {
|
|
750
|
+
const subagents = [...conversation.subagents.values()].map(({ description, activity }) => ({ description, activity }));
|
|
751
|
+
this.kit.send({ type: 'subagents', conversation_id: conversation.id, subagents });
|
|
752
|
+
}
|
|
620
753
|
commands(conversation, commands) {
|
|
621
754
|
conversation.commands = commands;
|
|
622
755
|
if (conversation.running === null)
|
|
@@ -651,7 +784,8 @@ export class Conversations {
|
|
|
651
784
|
}
|
|
652
785
|
begin(conversation) {
|
|
653
786
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
654
|
-
conversation.
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
655
789
|
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
656
790
|
conversation.turn = turn;
|
|
657
791
|
if (conversation.context !== null)
|
|
@@ -673,7 +807,8 @@ export class Conversations {
|
|
|
673
807
|
if (notice !== null)
|
|
674
808
|
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
675
809
|
turn.ended = true;
|
|
676
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
677
812
|
if (conversation.turn === turn)
|
|
678
813
|
conversation.turn = null;
|
|
679
814
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
@@ -706,7 +841,7 @@ export class Conversations {
|
|
|
706
841
|
}
|
|
707
842
|
else {
|
|
708
843
|
this.took(turn);
|
|
709
|
-
if (conversation.
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
710
845
|
running.adapter.retire();
|
|
711
846
|
this.detach(conversation, running);
|
|
712
847
|
}
|
|
@@ -719,19 +854,22 @@ export class Conversations {
|
|
|
719
854
|
this.took(turn);
|
|
720
855
|
return turn;
|
|
721
856
|
}
|
|
722
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
723
858
|
const turn = conversation.turn;
|
|
724
859
|
if (turn === null)
|
|
725
860
|
return;
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
this.apply(conversation, turn, event);
|
|
732
|
-
conversation.turn = null;
|
|
733
|
-
this.kit.changed();
|
|
861
|
+
const held = stop !== null && !turn.steered && [...conversation.subagents.values()].some((subagent) => subagent.turn === turn);
|
|
862
|
+
if (!turn.prompted || held) {
|
|
863
|
+
this.took(turn);
|
|
864
|
+
this.end(conversation, turn, stop === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
865
|
+
return;
|
|
734
866
|
}
|
|
867
|
+
if (stop !== null)
|
|
868
|
+
return;
|
|
869
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
870
|
+
this.apply(conversation, turn, event);
|
|
871
|
+
conversation.turn = null;
|
|
872
|
+
this.kit.changed();
|
|
735
873
|
}
|
|
736
874
|
async ask(conversation, method, params, secret, signal) {
|
|
737
875
|
const turn = this.started(conversation);
|
|
@@ -787,7 +925,7 @@ export class Conversations {
|
|
|
787
925
|
this.started(conversation);
|
|
788
926
|
return;
|
|
789
927
|
case 'ended':
|
|
790
|
-
this.ended(conversation);
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
791
929
|
return;
|
|
792
930
|
case 'commands':
|
|
793
931
|
this.commands(conversation, event.commands);
|
|
@@ -802,6 +940,9 @@ export class Conversations {
|
|
|
802
940
|
case 'subagent-done':
|
|
803
941
|
this.subagentDone(conversation, event.id, event.result);
|
|
804
942
|
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
805
946
|
case 'marker':
|
|
806
947
|
case 'image':
|
|
807
948
|
if (conversation.turn !== null)
|
|
@@ -828,7 +969,8 @@ export class Conversations {
|
|
|
828
969
|
return;
|
|
829
970
|
case 'subagent': {
|
|
830
971
|
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
831
|
-
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
972
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
973
|
+
this.subagentsChanged(conversation);
|
|
832
974
|
return;
|
|
833
975
|
}
|
|
834
976
|
case 'message':
|
|
@@ -851,6 +993,7 @@ export class Conversations {
|
|
|
851
993
|
if (subagent === undefined)
|
|
852
994
|
return;
|
|
853
995
|
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
854
997
|
if (subagent.turn === conversation.turn) {
|
|
855
998
|
subagent.turn.parts.done(subagent.index, result);
|
|
856
999
|
return;
|
|
@@ -861,20 +1004,31 @@ export class Conversations {
|
|
|
861
1004
|
else
|
|
862
1005
|
conversation.turn.parts.start(part);
|
|
863
1006
|
}
|
|
1007
|
+
subagentActivity(conversation, id, activity) {
|
|
1008
|
+
const subagent = conversation.subagents.get(id);
|
|
1009
|
+
if (subagent === undefined || subagent.activity === activity)
|
|
1010
|
+
return;
|
|
1011
|
+
subagent.activity = activity;
|
|
1012
|
+
this.subagentsChanged(conversation);
|
|
1013
|
+
}
|
|
864
1014
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
865
1015
|
const bytes = Buffer.from(data, 'base64');
|
|
866
1016
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
867
1018
|
try {
|
|
868
1019
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
869
1020
|
name,
|
|
870
1021
|
media_type: mediaType,
|
|
871
1022
|
bytes: bytes.length,
|
|
872
|
-
sha256
|
|
1023
|
+
sha256,
|
|
873
1024
|
}));
|
|
874
1025
|
const saved = await uploaded(declared.upload, {
|
|
1026
|
+
name,
|
|
875
1027
|
bytes: bytes.length,
|
|
876
|
-
|
|
877
|
-
|
|
1028
|
+
sha256,
|
|
1029
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
1030
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
1031
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
878
1032
|
});
|
|
879
1033
|
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
880
1034
|
}
|
package/dist/end.js
CHANGED
|
@@ -14,9 +14,15 @@ export function end(reason) {
|
|
|
14
14
|
export function restart(reason) {
|
|
15
15
|
exit(reason, 1);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
function updateLine(lead) {
|
|
18
18
|
const command = installCommand();
|
|
19
|
-
|
|
19
|
+
return command === null ? `${lead}; install it again.` : `${lead}. Update it: ${command}`;
|
|
20
|
+
}
|
|
21
|
+
export function updateRequired() {
|
|
22
|
+
end(updateLine('This Kit is too old for House'));
|
|
23
|
+
}
|
|
24
|
+
export function unknownMethod(kind) {
|
|
25
|
+
return updateLine(`This Kit does not know ${kind}`);
|
|
20
26
|
}
|
|
21
27
|
export function newerThanHouse(release, command) {
|
|
22
28
|
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|