@agentshouse/kit 0.2.1-c0eea939ba749 → 0.2.1-c1236ac1fbfbb
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 +21 -21
- package/dist/acp.js +2 -2
- package/dist/agents.js +2 -2
- package/dist/api.js +5 -5
- package/dist/bootstrap.json +4 -4
- package/dist/calls.js +63 -0
- package/dist/clis.js +1 -1
- package/dist/{stream.js → connection.js} +29 -27
- package/dist/conversations.js +289 -103
- package/dist/copies.js +15 -29
- package/dist/end.js +6 -2
- package/dist/events.js +2 -2
- package/dist/files.js +123 -37
- package/dist/git.js +1 -1
- package/dist/house-cli.js +95 -59
- package/dist/instructions.js +28 -8
- package/dist/kit-cli.js +2 -2
- package/dist/login.js +1 -3
- package/dist/mcp.js +4 -2
- package/dist/placement.js +11 -2
- package/dist/resident.js +32 -32
- package/dist/scope.js +3 -0
- package/dist/service.js +1 -1
- package/dist/sign-in.js +19 -19
- package/dist/skills.js +12 -93
- package/dist/transcription.js +51 -20
- package/dist/translate.js +2 -8
- package/dist/update.js +32 -32
- package/dist/wire.js +19 -3
- package/package.json +6 -7
- package/dist/inputs.js +0 -37
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -4,22 +4,23 @@ import { mkdir } from 'node:fs/promises';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
6
|
import { startAdapter } from './acp.js';
|
|
7
|
-
import { finalRefusal } from './api.js';
|
|
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';
|
|
15
15
|
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
16
17
|
import { killMarked, Scope } from './scope.js';
|
|
17
18
|
import { holdSecretInput } from './secret-input.js';
|
|
18
|
-
import { createHowWeWork } from './skills.js';
|
|
19
19
|
import { transcribe } from './transcription.js';
|
|
20
20
|
import { workFor } from './work.js';
|
|
21
21
|
const SIGNED_OUT = RequestError.authRequired().code;
|
|
22
22
|
const KILLED = 'the conversation was killed';
|
|
23
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
23
24
|
const REFUSED = 'the CLI refused the prompt';
|
|
24
25
|
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
26
|
const GONE = 'House no longer holds this conversation; start a new one';
|
|
@@ -31,9 +32,12 @@ const ENDED = {
|
|
|
31
32
|
const NO_START = "the Agent's CLI did not start";
|
|
32
33
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
34
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
35
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
34
36
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
35
37
|
class NotStarted extends Error {
|
|
36
38
|
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
37
41
|
async function step(reason, work) {
|
|
38
42
|
try {
|
|
39
43
|
return await work;
|
|
@@ -42,6 +46,14 @@ async function step(reason, work) {
|
|
|
42
46
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
function signedOut(error) {
|
|
50
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
51
|
+
}
|
|
52
|
+
function unopened(reason) {
|
|
53
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
54
|
+
}
|
|
55
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
56
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
45
57
|
function unbridged(error) {
|
|
46
58
|
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
59
|
}
|
|
@@ -53,13 +65,12 @@ const EXIT_PLAN_MODE = {
|
|
|
53
65
|
return value;
|
|
54
66
|
},
|
|
55
67
|
};
|
|
56
|
-
export const
|
|
68
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
57
69
|
function causeOf(error) {
|
|
58
70
|
return error instanceof Error ? error.message : String(error);
|
|
59
71
|
}
|
|
60
72
|
function answeredWith(error) {
|
|
61
|
-
|
|
62
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
73
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
74
|
}
|
|
64
75
|
function exitedWith(running, cause) {
|
|
65
76
|
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
@@ -67,16 +78,28 @@ function exitedWith(running, cause) {
|
|
|
67
78
|
function answerMessage(request, response) {
|
|
68
79
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
69
80
|
}
|
|
81
|
+
async function sentPart(url, bytes) {
|
|
82
|
+
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
83
|
+
await answer.arrayBuffer();
|
|
84
|
+
if (!answer.ok)
|
|
85
|
+
throw storeRefusal('upload', answer.status);
|
|
86
|
+
}
|
|
70
87
|
function logged(error) {
|
|
71
88
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
72
89
|
}
|
|
73
90
|
function refusal(conversation, error) {
|
|
74
91
|
if (conversation.kills > 0)
|
|
75
92
|
return KILLED;
|
|
76
|
-
const cause = error instanceof
|
|
93
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
77
94
|
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
78
95
|
return causeOf(error);
|
|
79
96
|
}
|
|
97
|
+
function audioOf(result) {
|
|
98
|
+
if (result.isError === true)
|
|
99
|
+
return null;
|
|
100
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
101
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
102
|
+
}
|
|
80
103
|
function marker(bridge) {
|
|
81
104
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
82
105
|
}
|
|
@@ -86,6 +109,7 @@ class Turn {
|
|
|
86
109
|
questions = 0;
|
|
87
110
|
secrets = 0;
|
|
88
111
|
prompted = false;
|
|
112
|
+
taken = null;
|
|
89
113
|
ended = false;
|
|
90
114
|
images = 0;
|
|
91
115
|
uploads = [];
|
|
@@ -99,6 +123,8 @@ class Conversation {
|
|
|
99
123
|
session = null;
|
|
100
124
|
turn = null;
|
|
101
125
|
commands = null;
|
|
126
|
+
options = null;
|
|
127
|
+
exitUntold = false;
|
|
102
128
|
context = null;
|
|
103
129
|
leftovers = new Set();
|
|
104
130
|
waiting = [];
|
|
@@ -106,6 +132,8 @@ class Conversation {
|
|
|
106
132
|
subagents = new Map();
|
|
107
133
|
held = [];
|
|
108
134
|
opening = null;
|
|
135
|
+
bridge = null;
|
|
136
|
+
launch = null;
|
|
109
137
|
prompted = null;
|
|
110
138
|
kills = 0;
|
|
111
139
|
killed = new AbortController();
|
|
@@ -114,6 +142,11 @@ class Conversation {
|
|
|
114
142
|
open = 0;
|
|
115
143
|
queue = Promise.resolve();
|
|
116
144
|
reports = Promise.resolve();
|
|
145
|
+
starting = null;
|
|
146
|
+
preparing = null;
|
|
147
|
+
told = null;
|
|
148
|
+
untold = false;
|
|
149
|
+
shown = null;
|
|
117
150
|
constructor(id) {
|
|
118
151
|
this.id = id;
|
|
119
152
|
}
|
|
@@ -137,23 +170,23 @@ export class Conversations {
|
|
|
137
170
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
138
171
|
setInterval(() => this.look(), 2_000);
|
|
139
172
|
}
|
|
140
|
-
|
|
141
|
-
void this.kit.
|
|
142
|
-
const conversation = this.conversation(
|
|
143
|
-
if (
|
|
173
|
+
call(call) {
|
|
174
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
175
|
+
const conversation = this.conversation(call.conversation_id);
|
|
176
|
+
if (call.kind === 'kill') {
|
|
144
177
|
conversation.kills++;
|
|
145
178
|
conversation.killed.abort(new Error(KILLED));
|
|
146
179
|
for (const waiting of conversation.waiting.splice(0))
|
|
147
180
|
waiting.settle({ refused: KILLED });
|
|
148
181
|
this.halt(conversation);
|
|
149
182
|
}
|
|
150
|
-
if (
|
|
183
|
+
if (call.kind === 'interrupt') {
|
|
151
184
|
conversation.interrupts++;
|
|
152
185
|
conversation.interrupted.abort();
|
|
153
186
|
}
|
|
154
|
-
const handled = conversation.queue.then(() => this.carry(conversation,
|
|
187
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
155
188
|
conversation.queue = handled.catch(() => undefined);
|
|
156
|
-
return handled.then(async ({
|
|
189
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
157
190
|
});
|
|
158
191
|
}
|
|
159
192
|
idle() {
|
|
@@ -186,6 +219,14 @@ export class Conversations {
|
|
|
186
219
|
}
|
|
187
220
|
opened() {
|
|
188
221
|
this.watched = false;
|
|
222
|
+
for (const conversation of this.conversations.values()) {
|
|
223
|
+
if (conversation.running !== null)
|
|
224
|
+
this.facts(conversation);
|
|
225
|
+
else if (conversation.exitUntold)
|
|
226
|
+
this.process(conversation, false);
|
|
227
|
+
if (conversation.untold || conversation.told !== null)
|
|
228
|
+
this.retell(conversation);
|
|
229
|
+
}
|
|
189
230
|
}
|
|
190
231
|
watching(watched) {
|
|
191
232
|
const resend = watched && !this.watched;
|
|
@@ -205,63 +246,99 @@ export class Conversations {
|
|
|
205
246
|
}
|
|
206
247
|
return conversation;
|
|
207
248
|
}
|
|
208
|
-
async carry(conversation,
|
|
209
|
-
if (
|
|
210
|
-
return {
|
|
211
|
-
if (
|
|
212
|
-
return this.message(conversation,
|
|
213
|
-
|
|
249
|
+
async carry(conversation, call) {
|
|
250
|
+
if (call.kind === 'open')
|
|
251
|
+
return { result: this.open(conversation, call) };
|
|
252
|
+
if (call.kind === 'message')
|
|
253
|
+
return this.message(conversation, call);
|
|
254
|
+
await this.launched(conversation);
|
|
255
|
+
if (call.kind === 'interrupt') {
|
|
214
256
|
if (--conversation.interrupts === 0)
|
|
215
257
|
conversation.interrupted = new AbortController();
|
|
216
|
-
await this.interrupt(conversation, String(
|
|
258
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
217
259
|
}
|
|
218
|
-
if (
|
|
260
|
+
if (call.kind === 'kill') {
|
|
219
261
|
await this.kill(conversation);
|
|
220
262
|
await conversation.sending;
|
|
221
263
|
if (--conversation.kills === 0)
|
|
222
264
|
conversation.killed = new AbortController();
|
|
223
265
|
}
|
|
224
|
-
if (
|
|
225
|
-
await this.option(conversation, String(
|
|
226
|
-
if (
|
|
227
|
-
const question = this.questions.get(String(
|
|
266
|
+
if (call.kind === 'option')
|
|
267
|
+
await this.option(conversation, String(call.option), call.value);
|
|
268
|
+
if (call.kind === 'answer') {
|
|
269
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
228
270
|
if (question === undefined) {
|
|
229
|
-
const text = answerMessage(
|
|
230
|
-
return this.message(conversation, { ...
|
|
271
|
+
const text = answerMessage(call.request, call.response);
|
|
272
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
231
273
|
}
|
|
232
|
-
question.answer(
|
|
274
|
+
question.answer(call.response);
|
|
233
275
|
}
|
|
234
|
-
return {
|
|
276
|
+
return { result: Promise.resolve({}) };
|
|
235
277
|
}
|
|
236
|
-
|
|
278
|
+
open(conversation, call) {
|
|
237
279
|
if (conversation.running !== null)
|
|
238
|
-
return {};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
return Promise.resolve({});
|
|
281
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
282
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
283
|
+
}
|
|
284
|
+
launch(conversation, route, sessionId, empty) {
|
|
285
|
+
let opened;
|
|
286
|
+
let failed;
|
|
287
|
+
const bridge = new Promise((resolve, reject) => {
|
|
288
|
+
opened = resolve;
|
|
289
|
+
failed = reject;
|
|
290
|
+
});
|
|
291
|
+
bridge.catch(() => undefined);
|
|
292
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
293
|
+
session.catch(failed);
|
|
294
|
+
const launch = { bridge, session };
|
|
295
|
+
conversation.launch = launch;
|
|
296
|
+
void session
|
|
297
|
+
.catch(() => undefined)
|
|
298
|
+
.then(() => {
|
|
299
|
+
if (conversation.launch !== launch)
|
|
300
|
+
return;
|
|
301
|
+
conversation.launch = null;
|
|
302
|
+
this.closeIdleBridge(conversation);
|
|
303
|
+
this.next(conversation);
|
|
304
|
+
});
|
|
305
|
+
this.next(conversation);
|
|
306
|
+
return launch;
|
|
245
307
|
}
|
|
246
|
-
async
|
|
247
|
-
|
|
308
|
+
async launched(conversation) {
|
|
309
|
+
while (conversation.launch !== null)
|
|
310
|
+
await conversation.launch.session.catch(() => undefined);
|
|
311
|
+
}
|
|
312
|
+
instructed(conversation, route) {
|
|
313
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
314
|
+
if (bridge === undefined)
|
|
315
|
+
return null;
|
|
316
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
317
|
+
read.catch(() => undefined);
|
|
318
|
+
return read;
|
|
319
|
+
}
|
|
320
|
+
async message(conversation, call) {
|
|
321
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
248
322
|
let route;
|
|
323
|
+
let instructing = null;
|
|
249
324
|
try {
|
|
250
|
-
route = await this.route(conversation,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
325
|
+
route = await this.route(conversation, call.agent_id);
|
|
326
|
+
if (call.first === true)
|
|
327
|
+
instructing = this.instructed(conversation, route);
|
|
328
|
+
if (call.message_id !== null)
|
|
329
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
256
330
|
if (conversation.kills > 0)
|
|
257
331
|
throw new Error(KILLED);
|
|
258
332
|
}
|
|
259
333
|
catch (error) {
|
|
260
|
-
|
|
334
|
+
this.closeIdleBridge(conversation);
|
|
335
|
+
this.prepares(conversation, null);
|
|
336
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
261
337
|
}
|
|
338
|
+
conversation.preparing = null;
|
|
262
339
|
return {
|
|
263
|
-
|
|
264
|
-
conversation.waiting.push({
|
|
340
|
+
result: new Promise((settle) => {
|
|
341
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
265
342
|
this.next(conversation);
|
|
266
343
|
}),
|
|
267
344
|
};
|
|
@@ -270,66 +347,148 @@ export class Conversations {
|
|
|
270
347
|
if (conversation.sending !== null || conversation.open > 0)
|
|
271
348
|
return;
|
|
272
349
|
const waiting = conversation.waiting.shift();
|
|
273
|
-
if (waiting === undefined)
|
|
350
|
+
if (waiting === undefined) {
|
|
351
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
352
|
+
if (conversation.starting !== step)
|
|
353
|
+
this.starts(conversation, step);
|
|
274
354
|
return;
|
|
355
|
+
}
|
|
275
356
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
276
357
|
conversation.sending = null;
|
|
277
358
|
this.next(conversation);
|
|
278
359
|
});
|
|
279
360
|
}
|
|
280
361
|
async send(conversation, waiting) {
|
|
281
|
-
const {
|
|
282
|
-
let
|
|
362
|
+
const { call } = waiting;
|
|
363
|
+
let result = {};
|
|
283
364
|
let started = false;
|
|
365
|
+
let instructing = waiting.instructing;
|
|
284
366
|
const prompt = [];
|
|
285
367
|
try {
|
|
286
368
|
if (conversation.running === null) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
369
|
+
this.starts(conversation, 'session');
|
|
370
|
+
await this.launched(conversation);
|
|
371
|
+
}
|
|
372
|
+
if (conversation.running === null) {
|
|
373
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
374
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
375
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
376
|
+
if (call.first === true)
|
|
377
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
378
|
+
started = (await launch.session) !== stored;
|
|
290
379
|
}
|
|
291
380
|
const session = conversation.running.sessionId;
|
|
292
|
-
if (started || (
|
|
293
|
-
|
|
381
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
382
|
+
result = { provider_session_id: session };
|
|
294
383
|
}
|
|
295
|
-
if (
|
|
296
|
-
prompt.push({
|
|
297
|
-
type: 'text',
|
|
298
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
299
|
-
});
|
|
384
|
+
if (call.first === true) {
|
|
385
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
300
386
|
}
|
|
301
387
|
if (conversation.kills > 0)
|
|
302
388
|
throw new Error(KILLED);
|
|
303
389
|
}
|
|
304
390
|
catch (error) {
|
|
391
|
+
this.starts(conversation, null);
|
|
305
392
|
if (started) {
|
|
306
393
|
conversation.session = null;
|
|
307
394
|
await this.kill(conversation);
|
|
308
395
|
}
|
|
309
|
-
|
|
396
|
+
const refused = refusal(conversation, error);
|
|
397
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
398
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this.starts(conversation, null);
|
|
402
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
403
|
+
waiting.settle(result);
|
|
404
|
+
}
|
|
405
|
+
starts(conversation, step) {
|
|
406
|
+
conversation.starting = step;
|
|
407
|
+
if (conversation.shown !== null)
|
|
408
|
+
clearTimeout(conversation.shown);
|
|
409
|
+
conversation.shown = null;
|
|
410
|
+
if (step === null || conversation.told !== null) {
|
|
411
|
+
this.tell(conversation, step);
|
|
310
412
|
return;
|
|
311
413
|
}
|
|
312
|
-
|
|
313
|
-
|
|
414
|
+
conversation.shown = setTimeout(() => {
|
|
415
|
+
conversation.shown = null;
|
|
416
|
+
if (conversation.starting === step)
|
|
417
|
+
this.tell(conversation, step);
|
|
418
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
314
419
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
420
|
+
prepares(conversation, step) {
|
|
421
|
+
conversation.preparing = step;
|
|
422
|
+
this.next(conversation);
|
|
423
|
+
}
|
|
424
|
+
tell(conversation, step) {
|
|
425
|
+
if (conversation.told === step && !conversation.untold)
|
|
426
|
+
return;
|
|
427
|
+
conversation.told = step;
|
|
428
|
+
this.retell(conversation);
|
|
429
|
+
}
|
|
430
|
+
retell(conversation) {
|
|
431
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
432
|
+
}
|
|
433
|
+
async transcribed(conversation, messageId, text) {
|
|
434
|
+
const paths = linkedPaths(text);
|
|
435
|
+
if (paths.length === 0)
|
|
436
|
+
return [];
|
|
437
|
+
const killed = conversation.killed.signal;
|
|
438
|
+
const interrupted = conversation.interrupted.signal;
|
|
439
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
440
|
+
let signal = killed;
|
|
441
|
+
const blocks = [];
|
|
442
|
+
try {
|
|
443
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
444
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
445
|
+
for (const path of paths) {
|
|
446
|
+
let answered = await read(path);
|
|
447
|
+
if (answered === null)
|
|
448
|
+
continue;
|
|
449
|
+
signal = transcribing;
|
|
450
|
+
this.prepares(conversation, 'transcription');
|
|
451
|
+
const said = await transcribe(async () => {
|
|
452
|
+
const address = answered ?? (await read(path));
|
|
453
|
+
answered = null;
|
|
454
|
+
if (address === null)
|
|
455
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
456
|
+
return address;
|
|
457
|
+
}, signal);
|
|
458
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
459
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
460
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
330
461
|
}
|
|
331
462
|
}
|
|
332
|
-
|
|
463
|
+
catch (error) {
|
|
464
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
465
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
return blocks;
|
|
469
|
+
}
|
|
470
|
+
bridged(conversation) {
|
|
471
|
+
if (conversation.bridge === null) {
|
|
472
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
473
|
+
conversation.bridge = bridging;
|
|
474
|
+
bridging.catch(() => {
|
|
475
|
+
if (conversation.bridge === bridging)
|
|
476
|
+
conversation.bridge = null;
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return conversation.bridge;
|
|
480
|
+
}
|
|
481
|
+
closeBridge(conversation, bridging, bridge) {
|
|
482
|
+
bridge.close();
|
|
483
|
+
if (conversation.bridge === bridging)
|
|
484
|
+
conversation.bridge = null;
|
|
485
|
+
}
|
|
486
|
+
closeIdleBridge(conversation) {
|
|
487
|
+
const bridging = conversation.bridge;
|
|
488
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
489
|
+
return;
|
|
490
|
+
conversation.bridge = null;
|
|
491
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
333
492
|
}
|
|
334
493
|
async interrupt(conversation, turnId) {
|
|
335
494
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -384,7 +543,8 @@ export class Conversations {
|
|
|
384
543
|
}
|
|
385
544
|
return route;
|
|
386
545
|
}
|
|
387
|
-
async start(conversation,
|
|
546
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
547
|
+
const route = await routed;
|
|
388
548
|
const reader = readerFor(route.kind);
|
|
389
549
|
reader.session = sessionId;
|
|
390
550
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -392,9 +552,10 @@ export class Conversations {
|
|
|
392
552
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
393
553
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
394
554
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
395
|
-
const
|
|
555
|
+
const bridging = this.bridged(conversation);
|
|
556
|
+
const bridge = await step(unbridged, bridging);
|
|
396
557
|
if (conversation.kills > 0) {
|
|
397
|
-
|
|
558
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
398
559
|
throw new Error(KILLED);
|
|
399
560
|
}
|
|
400
561
|
conversation.opening = bridge;
|
|
@@ -402,33 +563,31 @@ export class Conversations {
|
|
|
402
563
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
403
564
|
let adapter = null;
|
|
404
565
|
try {
|
|
405
|
-
|
|
566
|
+
bridged(bridge);
|
|
406
567
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
407
568
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
408
569
|
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
409
570
|
work.read(message);
|
|
410
571
|
this.read(conversation, reader, message);
|
|
411
572
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
412
|
-
void adapter.exited.then(() =>
|
|
573
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
413
574
|
const agent = adapter.connection.agent;
|
|
414
575
|
const cwd = route.working_directory;
|
|
415
576
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
416
577
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
417
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
578
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
418
579
|
reader.session = opened.sessionId;
|
|
419
580
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
420
581
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
421
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
582
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
422
583
|
}
|
|
423
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
584
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
424
585
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
425
586
|
void work.settled().then(() => scope.record());
|
|
426
587
|
conversation.running = running;
|
|
427
588
|
conversation.session = running.sessionId;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
this.commands(conversation, conversation.commands);
|
|
431
|
-
this.options(conversation, options);
|
|
589
|
+
conversation.options = options;
|
|
590
|
+
this.facts(conversation);
|
|
432
591
|
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
433
592
|
return running.sessionId;
|
|
434
593
|
}
|
|
@@ -436,7 +595,7 @@ export class Conversations {
|
|
|
436
595
|
adapter?.retire();
|
|
437
596
|
work.close();
|
|
438
597
|
conversation.leftovers.add(scope);
|
|
439
|
-
|
|
598
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
440
599
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
441
600
|
}
|
|
442
601
|
finally {
|
|
@@ -449,7 +608,7 @@ export class Conversations {
|
|
|
449
608
|
}
|
|
450
609
|
catch (error) {
|
|
451
610
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
452
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
611
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
453
612
|
}
|
|
454
613
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
455
614
|
return null;
|
|
@@ -485,15 +644,28 @@ export class Conversations {
|
|
|
485
644
|
this.kit.changed();
|
|
486
645
|
}
|
|
487
646
|
detach(conversation, running) {
|
|
647
|
+
if (conversation.bridge === running.bridging)
|
|
648
|
+
conversation.bridge = null;
|
|
488
649
|
running.work.close();
|
|
489
650
|
conversation.leftovers.add(running.scope);
|
|
490
651
|
conversation.running = null;
|
|
491
652
|
conversation.commands = null;
|
|
653
|
+
conversation.options = null;
|
|
492
654
|
for (const [id, question] of this.questions) {
|
|
493
655
|
if (question.conversation === conversation)
|
|
494
656
|
this.questions.delete(id);
|
|
495
657
|
}
|
|
496
|
-
this.
|
|
658
|
+
this.process(conversation, false);
|
|
659
|
+
}
|
|
660
|
+
process(conversation, running) {
|
|
661
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
662
|
+
}
|
|
663
|
+
facts(conversation) {
|
|
664
|
+
this.process(conversation, true);
|
|
665
|
+
if (conversation.commands !== null)
|
|
666
|
+
this.commands(conversation, conversation.commands);
|
|
667
|
+
if (conversation.options !== null)
|
|
668
|
+
this.options(conversation, conversation.options);
|
|
497
669
|
}
|
|
498
670
|
commands(conversation, commands) {
|
|
499
671
|
conversation.commands = commands;
|
|
@@ -504,6 +676,7 @@ export class Conversations {
|
|
|
504
676
|
options(conversation, options) {
|
|
505
677
|
if (conversation.running === null)
|
|
506
678
|
return;
|
|
679
|
+
conversation.options = options;
|
|
507
680
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
508
681
|
}
|
|
509
682
|
deliver(conversation, turn, event, body) {
|
|
@@ -562,12 +735,14 @@ export class Conversations {
|
|
|
562
735
|
this.next(conversation);
|
|
563
736
|
this.kit.changed();
|
|
564
737
|
}
|
|
565
|
-
prompt(conversation, prompt) {
|
|
738
|
+
prompt(conversation, prompt, taken) {
|
|
566
739
|
const running = conversation.running;
|
|
567
740
|
const turn = this.begin(conversation);
|
|
568
741
|
turn.prompted = true;
|
|
742
|
+
turn.taken = taken;
|
|
569
743
|
conversation.prompted = running.sessionId;
|
|
570
744
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
745
|
+
this.took(turn);
|
|
571
746
|
const notice = answeredFailure(answer);
|
|
572
747
|
if (notice !== null) {
|
|
573
748
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -580,6 +755,7 @@ export class Conversations {
|
|
|
580
755
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
581
756
|
}
|
|
582
757
|
else {
|
|
758
|
+
this.took(turn);
|
|
583
759
|
if (conversation.open === 1 && conversation.running === running) {
|
|
584
760
|
running.adapter.retire();
|
|
585
761
|
this.detach(conversation, running);
|
|
@@ -589,7 +765,9 @@ export class Conversations {
|
|
|
589
765
|
});
|
|
590
766
|
}
|
|
591
767
|
started(conversation) {
|
|
592
|
-
|
|
768
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
769
|
+
this.took(turn);
|
|
770
|
+
return turn;
|
|
593
771
|
}
|
|
594
772
|
ended(conversation) {
|
|
595
773
|
const turn = conversation.turn;
|
|
@@ -684,6 +862,11 @@ export class Conversations {
|
|
|
684
862
|
}
|
|
685
863
|
this.apply(conversation, this.started(conversation), event);
|
|
686
864
|
}
|
|
865
|
+
took(turn) {
|
|
866
|
+
const taken = turn.taken;
|
|
867
|
+
turn.taken = null;
|
|
868
|
+
taken?.();
|
|
869
|
+
}
|
|
687
870
|
apply(conversation, turn, event) {
|
|
688
871
|
switch (event.kind) {
|
|
689
872
|
case 'text':
|
|
@@ -738,13 +921,16 @@ export class Conversations {
|
|
|
738
921
|
bytes: bytes.length,
|
|
739
922
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
740
923
|
}));
|
|
741
|
-
const saved =
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
924
|
+
const saved = await uploaded(declared.upload, {
|
|
925
|
+
bytes: bytes.length,
|
|
926
|
+
send: (url, start, end) => sentPart(url, bytes.subarray(start, end)),
|
|
927
|
+
finish: (upload) => this.kit.house.deliver('/kit/uploads/finish', { upload }),
|
|
928
|
+
});
|
|
929
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
745
930
|
}
|
|
746
931
|
catch (error) {
|
|
747
|
-
|
|
932
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
933
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
748
934
|
}
|
|
749
935
|
}
|
|
750
936
|
}
|