@agentshouse/kit 0.2.1-c7252abc8a583 → 0.2.1-c7a397cb0cce7
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 +20 -19
- package/dist/acp.js +34 -14
- package/dist/agents.js +104 -21
- package/dist/api.js +1 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +19 -3
- package/dist/conversations.js +280 -94
- package/dist/copies.js +98 -47
- package/dist/end.js +3 -2
- package/dist/events.js +4 -0
- package/dist/files.js +8 -2
- package/dist/git.js +10 -8
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -228
- package/dist/inputs.js +63 -0
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -64
- package/dist/login.js +44 -27
- package/dist/logout.js +0 -2
- package/dist/placement.js +2 -20
- package/dist/resident.js +60 -15
- package/dist/scope.js +3 -0
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/skills.js +81 -12
- package/dist/stream.js +62 -16
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +21 -23
- package/dist/wire.js +1 -1
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/conversations.js
CHANGED
|
@@ -1,42 +1,100 @@
|
|
|
1
|
-
import { client, } from '@agentclientprotocol/sdk';
|
|
1
|
+
import { client, RequestError, } 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
6
|
import { startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal } from './api.js';
|
|
7
8
|
import { openBridge } from './bridge.js';
|
|
8
9
|
import { CLIS } from './clis.js';
|
|
9
|
-
import { readerFor } from './events.js';
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
10
11
|
import { placeFiles, uploadBytes } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
14
16
|
import { killMarked, Scope } from './scope.js';
|
|
15
17
|
import { holdSecretInput } from './secret-input.js';
|
|
16
18
|
import { createHowWeWork } from './skills.js';
|
|
17
19
|
import { transcribe } from './transcription.js';
|
|
18
20
|
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
19
22
|
const KILLED = 'the conversation was killed';
|
|
23
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
24
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
26
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
27
|
+
const ENDED = {
|
|
28
|
+
conversation_not_found: GONE,
|
|
29
|
+
conversation_historical: RETIRED,
|
|
30
|
+
};
|
|
31
|
+
const NO_START = "the Agent's CLI did not start";
|
|
32
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
35
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
class NotStarted extends Error {
|
|
37
|
+
}
|
|
38
|
+
async function step(reason, work) {
|
|
39
|
+
try {
|
|
40
|
+
return await work;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function signedOut(error) {
|
|
47
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
54
|
+
function unbridged(error) {
|
|
55
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
56
|
+
}
|
|
57
|
+
const EXIT_PLAN_MODE = {
|
|
58
|
+
parse(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
60
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
20
65
|
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
21
66
|
function causeOf(error) {
|
|
22
67
|
return error instanceof Error ? error.message : String(error);
|
|
23
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
74
|
+
}
|
|
24
75
|
function answerMessage(request, response) {
|
|
25
76
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
26
77
|
}
|
|
27
78
|
function logged(error) {
|
|
28
79
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
29
80
|
}
|
|
81
|
+
function refusal(conversation, error) {
|
|
82
|
+
if (conversation.kills > 0)
|
|
83
|
+
return KILLED;
|
|
84
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
85
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
86
|
+
return causeOf(error);
|
|
87
|
+
}
|
|
30
88
|
function marker(bridge) {
|
|
31
89
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
32
90
|
}
|
|
33
91
|
class Turn {
|
|
34
92
|
id = randomUUID();
|
|
35
93
|
parts;
|
|
36
|
-
admitted = false;
|
|
37
94
|
questions = 0;
|
|
38
95
|
secrets = 0;
|
|
39
96
|
prompted = false;
|
|
97
|
+
taken = null;
|
|
40
98
|
ended = false;
|
|
41
99
|
images = 0;
|
|
42
100
|
uploads = [];
|
|
@@ -50,6 +108,8 @@ class Conversation {
|
|
|
50
108
|
session = null;
|
|
51
109
|
turn = null;
|
|
52
110
|
commands = null;
|
|
111
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
53
113
|
context = null;
|
|
54
114
|
leftovers = new Set();
|
|
55
115
|
waiting = [];
|
|
@@ -57,6 +117,8 @@ class Conversation {
|
|
|
57
117
|
subagents = new Map();
|
|
58
118
|
held = [];
|
|
59
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
121
|
+
prompted = null;
|
|
60
122
|
kills = 0;
|
|
61
123
|
killed = new AbortController();
|
|
62
124
|
interrupts = 0;
|
|
@@ -64,6 +126,11 @@ class Conversation {
|
|
|
64
126
|
open = 0;
|
|
65
127
|
queue = Promise.resolve();
|
|
66
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
67
134
|
constructor(id) {
|
|
68
135
|
this.id = id;
|
|
69
136
|
}
|
|
@@ -79,9 +146,7 @@ function asksSecret(request) {
|
|
|
79
146
|
export class Conversations {
|
|
80
147
|
kit;
|
|
81
148
|
conversations = new Map();
|
|
82
|
-
acks = new Map();
|
|
83
149
|
questions = new Map();
|
|
84
|
-
carrying = 0;
|
|
85
150
|
reporting = 0;
|
|
86
151
|
watched = false;
|
|
87
152
|
constructor(kit) {
|
|
@@ -90,37 +155,26 @@ export class Conversations {
|
|
|
90
155
|
setInterval(() => this.look(), 2_000);
|
|
91
156
|
}
|
|
92
157
|
input(input) {
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
conversation.queue = handled.catch(() => undefined);
|
|
110
|
-
const work = handled.then(({ ack }) => ack);
|
|
111
|
-
this.acks.set(input.input_id, work);
|
|
112
|
-
return work;
|
|
113
|
-
})();
|
|
114
|
-
carried
|
|
115
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
116
|
-
.catch(logged)
|
|
117
|
-
.finally(() => {
|
|
118
|
-
this.carrying--;
|
|
119
|
-
this.kit.changed();
|
|
158
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
159
|
+
const conversation = this.conversation(input.conversation_id);
|
|
160
|
+
if (input.kind === 'kill') {
|
|
161
|
+
conversation.kills++;
|
|
162
|
+
conversation.killed.abort(new Error(KILLED));
|
|
163
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
164
|
+
waiting.settle({ refused: KILLED });
|
|
165
|
+
this.halt(conversation);
|
|
166
|
+
}
|
|
167
|
+
if (input.kind === 'interrupt') {
|
|
168
|
+
conversation.interrupts++;
|
|
169
|
+
conversation.interrupted.abort();
|
|
170
|
+
}
|
|
171
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
172
|
+
conversation.queue = handled.catch(() => undefined);
|
|
173
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
120
174
|
});
|
|
121
175
|
}
|
|
122
176
|
idle() {
|
|
123
|
-
if (this.
|
|
177
|
+
if (this.reporting > 0)
|
|
124
178
|
return false;
|
|
125
179
|
for (const conversation of this.conversations.values()) {
|
|
126
180
|
const turn = conversation.turn;
|
|
@@ -149,6 +203,14 @@ export class Conversations {
|
|
|
149
203
|
}
|
|
150
204
|
opened() {
|
|
151
205
|
this.watched = false;
|
|
206
|
+
for (const conversation of this.conversations.values()) {
|
|
207
|
+
if (conversation.running !== null)
|
|
208
|
+
this.facts(conversation);
|
|
209
|
+
else if (conversation.exitUntold)
|
|
210
|
+
this.process(conversation, false);
|
|
211
|
+
if (conversation.untold || conversation.told !== null)
|
|
212
|
+
this.retell(conversation);
|
|
213
|
+
}
|
|
152
214
|
}
|
|
153
215
|
watching(watched) {
|
|
154
216
|
const resend = watched && !this.watched;
|
|
@@ -170,9 +232,10 @@ export class Conversations {
|
|
|
170
232
|
}
|
|
171
233
|
async carry(conversation, input) {
|
|
172
234
|
if (input.kind === 'open')
|
|
173
|
-
return { ack:
|
|
235
|
+
return { ack: this.open(conversation, input) };
|
|
174
236
|
if (input.kind === 'message')
|
|
175
237
|
return this.message(conversation, input);
|
|
238
|
+
await this.launched(conversation);
|
|
176
239
|
if (input.kind === 'interrupt') {
|
|
177
240
|
if (--conversation.interrupts === 0)
|
|
178
241
|
conversation.interrupted = new AbortController();
|
|
@@ -196,22 +259,58 @@ export class Conversations {
|
|
|
196
259
|
}
|
|
197
260
|
return { ack: Promise.resolve({}) };
|
|
198
261
|
}
|
|
199
|
-
|
|
262
|
+
open(conversation, input) {
|
|
200
263
|
if (conversation.running !== null)
|
|
201
|
-
return {};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, input.agent_id), null, true);
|
|
266
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
267
|
+
}
|
|
268
|
+
launch(conversation, route, sessionId, empty) {
|
|
269
|
+
let opened;
|
|
270
|
+
let failed;
|
|
271
|
+
const bridge = new Promise((resolve, reject) => {
|
|
272
|
+
opened = resolve;
|
|
273
|
+
failed = reject;
|
|
274
|
+
});
|
|
275
|
+
bridge.catch(() => undefined);
|
|
276
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
277
|
+
session.catch(failed);
|
|
278
|
+
const launch = { bridge, session };
|
|
279
|
+
conversation.launch = launch;
|
|
280
|
+
void session
|
|
281
|
+
.catch(() => undefined)
|
|
282
|
+
.then(() => {
|
|
283
|
+
if (conversation.launch !== launch)
|
|
284
|
+
return;
|
|
285
|
+
conversation.launch = null;
|
|
286
|
+
this.next(conversation);
|
|
287
|
+
});
|
|
288
|
+
this.next(conversation);
|
|
289
|
+
return launch;
|
|
290
|
+
}
|
|
291
|
+
async launched(conversation) {
|
|
292
|
+
while (conversation.launch !== null)
|
|
293
|
+
await conversation.launch.session.catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
instructed(conversation, route) {
|
|
296
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
297
|
+
if (bridge === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
300
|
+
read.catch(() => undefined);
|
|
301
|
+
return read;
|
|
208
302
|
}
|
|
209
303
|
async message(conversation, input) {
|
|
210
304
|
const content = [{ type: 'text', text: String(input.text) }];
|
|
211
305
|
let route;
|
|
306
|
+
let instructing = null;
|
|
212
307
|
try {
|
|
213
308
|
route = await this.route(conversation, input.agent_id);
|
|
309
|
+
if (input.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
214
311
|
const files = input.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
215
314
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
216
315
|
const lines = await this.transcribed(conversation, files, paths);
|
|
217
316
|
if (lines.length > 0)
|
|
@@ -220,11 +319,13 @@ export class Conversations {
|
|
|
220
319
|
throw new Error(KILLED);
|
|
221
320
|
}
|
|
222
321
|
catch (error) {
|
|
223
|
-
|
|
322
|
+
this.prepares(conversation, null);
|
|
323
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
224
324
|
}
|
|
325
|
+
conversation.preparing = null;
|
|
225
326
|
return {
|
|
226
327
|
ack: new Promise((settle) => {
|
|
227
|
-
conversation.waiting.push({ input, route, content, settle });
|
|
328
|
+
conversation.waiting.push({ input, route, content, instructing, settle });
|
|
228
329
|
this.next(conversation);
|
|
229
330
|
}),
|
|
230
331
|
};
|
|
@@ -233,8 +334,12 @@ export class Conversations {
|
|
|
233
334
|
if (conversation.sending !== null || conversation.open > 0)
|
|
234
335
|
return;
|
|
235
336
|
const waiting = conversation.waiting.shift();
|
|
236
|
-
if (waiting === undefined)
|
|
337
|
+
if (waiting === undefined) {
|
|
338
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
|
+
if (conversation.starting !== step)
|
|
340
|
+
this.starts(conversation, step);
|
|
237
341
|
return;
|
|
342
|
+
}
|
|
238
343
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
239
344
|
conversation.sending = null;
|
|
240
345
|
this.next(conversation);
|
|
@@ -243,40 +348,80 @@ export class Conversations {
|
|
|
243
348
|
async send(conversation, waiting) {
|
|
244
349
|
const { input } = waiting;
|
|
245
350
|
let ack = {};
|
|
351
|
+
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
246
353
|
const prompt = [];
|
|
247
354
|
try {
|
|
248
355
|
if (conversation.running === null) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
359
|
+
if (conversation.running === null) {
|
|
360
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
361
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (input.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
366
|
+
}
|
|
367
|
+
const session = conversation.running.sessionId;
|
|
368
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
369
|
+
ack = { provider_session_id: session };
|
|
253
370
|
}
|
|
254
371
|
if (input.first === true) {
|
|
255
|
-
prompt.push({
|
|
256
|
-
type: 'text',
|
|
257
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
258
|
-
});
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
259
373
|
}
|
|
260
374
|
if (conversation.kills > 0)
|
|
261
375
|
throw new Error(KILLED);
|
|
262
376
|
}
|
|
263
377
|
catch (error) {
|
|
264
|
-
|
|
378
|
+
this.starts(conversation, null);
|
|
379
|
+
if (started) {
|
|
265
380
|
conversation.session = null;
|
|
266
381
|
await this.kill(conversation);
|
|
267
382
|
}
|
|
268
|
-
waiting.settle({ refused: conversation
|
|
383
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
269
384
|
return;
|
|
270
385
|
}
|
|
271
|
-
this.
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
272
388
|
waiting.settle(ack);
|
|
273
389
|
}
|
|
390
|
+
starts(conversation, step) {
|
|
391
|
+
conversation.starting = step;
|
|
392
|
+
if (conversation.shown !== null)
|
|
393
|
+
clearTimeout(conversation.shown);
|
|
394
|
+
conversation.shown = null;
|
|
395
|
+
if (step === null || conversation.told !== null) {
|
|
396
|
+
this.tell(conversation, step);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
conversation.shown = setTimeout(() => {
|
|
400
|
+
conversation.shown = null;
|
|
401
|
+
if (conversation.starting === step)
|
|
402
|
+
this.tell(conversation, step);
|
|
403
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
404
|
+
}
|
|
405
|
+
prepares(conversation, step) {
|
|
406
|
+
conversation.preparing = step;
|
|
407
|
+
this.next(conversation);
|
|
408
|
+
}
|
|
409
|
+
tell(conversation, step) {
|
|
410
|
+
if (conversation.told === step && !conversation.untold)
|
|
411
|
+
return;
|
|
412
|
+
conversation.told = step;
|
|
413
|
+
this.retell(conversation);
|
|
414
|
+
}
|
|
415
|
+
retell(conversation) {
|
|
416
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
417
|
+
}
|
|
274
418
|
async transcribed(conversation, files, paths) {
|
|
275
419
|
const lines = [];
|
|
276
420
|
for (const [index, path] of paths.entries()) {
|
|
277
421
|
lines.push(path);
|
|
278
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
279
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
280
425
|
try {
|
|
281
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
282
427
|
if (text !== null && text !== '')
|
|
@@ -343,14 +488,16 @@ export class Conversations {
|
|
|
343
488
|
}
|
|
344
489
|
return route;
|
|
345
490
|
}
|
|
346
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
347
493
|
const reader = readerFor(route.kind);
|
|
348
494
|
reader.session = sessionId;
|
|
349
495
|
const app = client({ name: '@agentshouse/kit' })
|
|
350
496
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
351
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
497
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
498
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
352
499
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
353
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
500
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
354
501
|
if (conversation.kills > 0) {
|
|
355
502
|
bridge.close();
|
|
356
503
|
throw new Error(KILLED);
|
|
@@ -358,39 +505,39 @@ export class Conversations {
|
|
|
358
505
|
conversation.opening = bridge;
|
|
359
506
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
360
507
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
|
+
let adapter = null;
|
|
361
509
|
try {
|
|
362
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
511
|
+
bridged(bridge);
|
|
363
512
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
364
|
-
const
|
|
513
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
514
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
365
515
|
work.read(message);
|
|
366
516
|
this.read(conversation, reader, message);
|
|
367
|
-
}, { ...bridge.env, ...
|
|
517
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
368
518
|
void adapter.exited.then(() => bridge.close());
|
|
369
519
|
const agent = adapter.connection.agent;
|
|
370
520
|
const cwd = route.working_directory;
|
|
371
521
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
522
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
523
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
375
524
|
reader.session = opened.sessionId;
|
|
376
|
-
if (sessionId
|
|
525
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
377
526
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
378
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
379
528
|
}
|
|
380
529
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
381
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
530
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
382
531
|
void work.settled().then(() => scope.record());
|
|
383
532
|
conversation.running = running;
|
|
384
533
|
conversation.session = running.sessionId;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this.options(conversation, options);
|
|
389
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
534
|
+
conversation.options = options;
|
|
535
|
+
this.facts(conversation);
|
|
536
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
390
537
|
return running.sessionId;
|
|
391
538
|
}
|
|
392
539
|
catch (error) {
|
|
393
|
-
|
|
540
|
+
adapter?.retire();
|
|
394
541
|
work.close();
|
|
395
542
|
conversation.leftovers.add(scope);
|
|
396
543
|
bridge.close();
|
|
@@ -400,6 +547,18 @@ export class Conversations {
|
|
|
400
547
|
conversation.opening = null;
|
|
401
548
|
}
|
|
402
549
|
}
|
|
550
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
551
|
+
try {
|
|
552
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
556
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
557
|
+
}
|
|
558
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
403
562
|
async launchSettings(running, route, offered) {
|
|
404
563
|
let options = offered;
|
|
405
564
|
const settings = [
|
|
@@ -421,13 +580,10 @@ export class Conversations {
|
|
|
421
580
|
}
|
|
422
581
|
return options;
|
|
423
582
|
}
|
|
424
|
-
exited(conversation,
|
|
425
|
-
const running = conversation.running;
|
|
426
|
-
if (running?.adapter !== adapter)
|
|
427
|
-
return;
|
|
583
|
+
exited(conversation, running, cause) {
|
|
428
584
|
this.detach(conversation, running);
|
|
429
585
|
if (conversation.turn !== null) {
|
|
430
|
-
this.end(conversation, conversation.turn,
|
|
586
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
431
587
|
}
|
|
432
588
|
this.next(conversation);
|
|
433
589
|
this.kit.changed();
|
|
@@ -437,11 +593,22 @@ export class Conversations {
|
|
|
437
593
|
conversation.leftovers.add(running.scope);
|
|
438
594
|
conversation.running = null;
|
|
439
595
|
conversation.commands = null;
|
|
596
|
+
conversation.options = null;
|
|
440
597
|
for (const [id, question] of this.questions) {
|
|
441
598
|
if (question.conversation === conversation)
|
|
442
599
|
this.questions.delete(id);
|
|
443
600
|
}
|
|
444
|
-
this.
|
|
601
|
+
this.process(conversation, false);
|
|
602
|
+
}
|
|
603
|
+
process(conversation, running) {
|
|
604
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
605
|
+
}
|
|
606
|
+
facts(conversation) {
|
|
607
|
+
this.process(conversation, true);
|
|
608
|
+
if (conversation.commands !== null)
|
|
609
|
+
this.commands(conversation, conversation.commands);
|
|
610
|
+
if (conversation.options !== null)
|
|
611
|
+
this.options(conversation, conversation.options);
|
|
445
612
|
}
|
|
446
613
|
commands(conversation, commands) {
|
|
447
614
|
conversation.commands = commands;
|
|
@@ -452,6 +619,7 @@ export class Conversations {
|
|
|
452
619
|
options(conversation, options) {
|
|
453
620
|
if (conversation.running === null)
|
|
454
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
455
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
456
624
|
}
|
|
457
625
|
deliver(conversation, turn, event, body) {
|
|
@@ -466,7 +634,7 @@ export class Conversations {
|
|
|
466
634
|
return delivered;
|
|
467
635
|
}
|
|
468
636
|
write(conversation, turn, operation) {
|
|
469
|
-
if (
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
470
638
|
return;
|
|
471
639
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
472
640
|
}
|
|
@@ -477,12 +645,7 @@ export class Conversations {
|
|
|
477
645
|
begin(conversation) {
|
|
478
646
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
479
647
|
conversation.open++;
|
|
480
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
481
|
-
.then(() => {
|
|
482
|
-
turn.admitted = true;
|
|
483
|
-
this.replay(conversation, turn);
|
|
484
|
-
})
|
|
485
|
-
.catch(logged);
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
486
649
|
conversation.turn = turn;
|
|
487
650
|
if (conversation.context !== null)
|
|
488
651
|
turn.parts.measure(conversation.context);
|
|
@@ -491,13 +654,17 @@ export class Conversations {
|
|
|
491
654
|
this.kit.changed();
|
|
492
655
|
return turn;
|
|
493
656
|
}
|
|
494
|
-
end(conversation, turn, failed) {
|
|
657
|
+
end(conversation, turn, failed, notice = null) {
|
|
495
658
|
if (turn.ended)
|
|
496
659
|
return;
|
|
660
|
+
if (failed !== null)
|
|
661
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
497
662
|
if (conversation.turn === turn) {
|
|
498
663
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
499
664
|
this.apply(conversation, turn, event);
|
|
500
665
|
}
|
|
666
|
+
if (notice !== null)
|
|
667
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
501
668
|
turn.ended = true;
|
|
502
669
|
conversation.open--;
|
|
503
670
|
if (conversation.turn === turn)
|
|
@@ -505,31 +672,45 @@ export class Conversations {
|
|
|
505
672
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
506
673
|
parts: turn.parts.saved(),
|
|
507
674
|
context: turn.parts.context,
|
|
508
|
-
...failed,
|
|
675
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
509
676
|
}));
|
|
510
677
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
511
678
|
this.next(conversation);
|
|
512
679
|
this.kit.changed();
|
|
513
680
|
}
|
|
514
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
515
682
|
const running = conversation.running;
|
|
516
683
|
const turn = this.begin(conversation);
|
|
517
684
|
turn.prompted = true;
|
|
518
|
-
|
|
685
|
+
turn.taken = taken;
|
|
686
|
+
conversation.prompted = running.sessionId;
|
|
687
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
688
|
+
this.took(turn);
|
|
689
|
+
const notice = answeredFailure(answer);
|
|
690
|
+
if (notice !== null) {
|
|
691
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
695
|
+
}
|
|
696
|
+
}, async (error) => {
|
|
519
697
|
if (running.adapter.connection.signal.aborted) {
|
|
520
|
-
this.end(conversation, turn,
|
|
698
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
521
699
|
}
|
|
522
700
|
else {
|
|
701
|
+
this.took(turn);
|
|
523
702
|
if (conversation.open === 1 && conversation.running === running) {
|
|
524
|
-
running.
|
|
703
|
+
running.adapter.retire();
|
|
525
704
|
this.detach(conversation, running);
|
|
526
705
|
}
|
|
527
|
-
this.end(conversation, turn,
|
|
706
|
+
this.end(conversation, turn, answeredWith(error));
|
|
528
707
|
}
|
|
529
708
|
});
|
|
530
709
|
}
|
|
531
710
|
started(conversation) {
|
|
532
|
-
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
713
|
+
return turn;
|
|
533
714
|
}
|
|
534
715
|
ended(conversation) {
|
|
535
716
|
const turn = conversation.turn;
|
|
@@ -624,6 +805,11 @@ export class Conversations {
|
|
|
624
805
|
}
|
|
625
806
|
this.apply(conversation, this.started(conversation), event);
|
|
626
807
|
}
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
812
|
+
}
|
|
627
813
|
apply(conversation, turn, event) {
|
|
628
814
|
switch (event.kind) {
|
|
629
815
|
case 'text':
|