@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf3a50c90b895
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 +31 -30
- package/dist/acp.js +36 -16
- package/dist/agents.js +110 -23
- package/dist/api.js +5 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +20 -4
- package/dist/connection.js +125 -0
- package/dist/conversations.js +307 -120
- package/dist/copies.js +89 -52
- package/dist/end.js +11 -2
- package/dist/events.js +6 -2
- package/dist/files.js +8 -2
- package/dist/git.js +11 -9
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -206
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +46 -28
- package/dist/logout.js +0 -2
- package/dist/mcp.js +2 -2
- package/dist/placement.js +39 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +82 -33
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +19 -37
- package/dist/skills.js +84 -15
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +69 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- 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/dist/stream.js +0 -74
- 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,41 +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
|
+
};
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
20
66
|
function causeOf(error) {
|
|
21
67
|
return error instanceof Error ? error.message : String(error);
|
|
22
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
|
+
}
|
|
23
75
|
function answerMessage(request, response) {
|
|
24
76
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
25
77
|
}
|
|
26
78
|
function logged(error) {
|
|
27
79
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
28
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
|
+
}
|
|
29
88
|
function marker(bridge) {
|
|
30
89
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
90
|
}
|
|
32
91
|
class Turn {
|
|
33
92
|
id = randomUUID();
|
|
34
93
|
parts;
|
|
35
|
-
admitted = false;
|
|
36
94
|
questions = 0;
|
|
37
95
|
secrets = 0;
|
|
38
96
|
prompted = false;
|
|
97
|
+
taken = null;
|
|
39
98
|
ended = false;
|
|
40
99
|
images = 0;
|
|
41
100
|
uploads = [];
|
|
@@ -49,6 +108,8 @@ class Conversation {
|
|
|
49
108
|
session = null;
|
|
50
109
|
turn = null;
|
|
51
110
|
commands = null;
|
|
111
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
52
113
|
context = null;
|
|
53
114
|
leftovers = new Set();
|
|
54
115
|
waiting = [];
|
|
@@ -56,6 +117,8 @@ class Conversation {
|
|
|
56
117
|
subagents = new Map();
|
|
57
118
|
held = [];
|
|
58
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
121
|
+
prompted = null;
|
|
59
122
|
kills = 0;
|
|
60
123
|
killed = new AbortController();
|
|
61
124
|
interrupts = 0;
|
|
@@ -63,6 +126,11 @@ class Conversation {
|
|
|
63
126
|
open = 0;
|
|
64
127
|
queue = Promise.resolve();
|
|
65
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
66
134
|
constructor(id) {
|
|
67
135
|
this.id = id;
|
|
68
136
|
}
|
|
@@ -78,9 +146,7 @@ function asksSecret(request) {
|
|
|
78
146
|
export class Conversations {
|
|
79
147
|
kit;
|
|
80
148
|
conversations = new Map();
|
|
81
|
-
acks = new Map();
|
|
82
149
|
questions = new Map();
|
|
83
|
-
carrying = 0;
|
|
84
150
|
reporting = 0;
|
|
85
151
|
watched = false;
|
|
86
152
|
constructor(kit) {
|
|
@@ -88,38 +154,27 @@ export class Conversations {
|
|
|
88
154
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
89
155
|
setInterval(() => this.look(), 2_000);
|
|
90
156
|
}
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
conversation.queue = handled.catch(() => undefined);
|
|
109
|
-
const work = handled.then(({ ack }) => ack);
|
|
110
|
-
this.acks.set(input.input_id, work);
|
|
111
|
-
return work;
|
|
112
|
-
})();
|
|
113
|
-
carried
|
|
114
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
115
|
-
.catch(logged)
|
|
116
|
-
.finally(() => {
|
|
117
|
-
this.carrying--;
|
|
118
|
-
this.kit.changed();
|
|
157
|
+
call(call) {
|
|
158
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
159
|
+
const conversation = this.conversation(call.conversation_id);
|
|
160
|
+
if (call.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 (call.kind === 'interrupt') {
|
|
168
|
+
conversation.interrupts++;
|
|
169
|
+
conversation.interrupted.abort();
|
|
170
|
+
}
|
|
171
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
172
|
+
conversation.queue = handled.catch(() => undefined);
|
|
173
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
119
174
|
});
|
|
120
175
|
}
|
|
121
176
|
idle() {
|
|
122
|
-
if (this.
|
|
177
|
+
if (this.reporting > 0)
|
|
123
178
|
return false;
|
|
124
179
|
for (const conversation of this.conversations.values()) {
|
|
125
180
|
const turn = conversation.turn;
|
|
@@ -148,6 +203,14 @@ export class Conversations {
|
|
|
148
203
|
}
|
|
149
204
|
opened() {
|
|
150
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
|
+
}
|
|
151
214
|
}
|
|
152
215
|
watching(watched) {
|
|
153
216
|
const resend = watched && !this.watched;
|
|
@@ -167,50 +230,87 @@ export class Conversations {
|
|
|
167
230
|
}
|
|
168
231
|
return conversation;
|
|
169
232
|
}
|
|
170
|
-
async carry(conversation,
|
|
171
|
-
if (
|
|
172
|
-
return {
|
|
173
|
-
if (
|
|
174
|
-
return this.message(conversation,
|
|
175
|
-
|
|
233
|
+
async carry(conversation, call) {
|
|
234
|
+
if (call.kind === 'open')
|
|
235
|
+
return { result: this.open(conversation, call) };
|
|
236
|
+
if (call.kind === 'message')
|
|
237
|
+
return this.message(conversation, call);
|
|
238
|
+
await this.launched(conversation);
|
|
239
|
+
if (call.kind === 'interrupt') {
|
|
176
240
|
if (--conversation.interrupts === 0)
|
|
177
241
|
conversation.interrupted = new AbortController();
|
|
178
|
-
await this.interrupt(conversation, String(
|
|
242
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
179
243
|
}
|
|
180
|
-
if (
|
|
244
|
+
if (call.kind === 'kill') {
|
|
181
245
|
await this.kill(conversation);
|
|
182
246
|
await conversation.sending;
|
|
183
247
|
if (--conversation.kills === 0)
|
|
184
248
|
conversation.killed = new AbortController();
|
|
185
249
|
}
|
|
186
|
-
if (
|
|
187
|
-
await this.option(conversation, String(
|
|
188
|
-
if (
|
|
189
|
-
const question = this.questions.get(String(
|
|
250
|
+
if (call.kind === 'option')
|
|
251
|
+
await this.option(conversation, String(call.option), call.value);
|
|
252
|
+
if (call.kind === 'answer') {
|
|
253
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
190
254
|
if (question === undefined) {
|
|
191
|
-
const text = answerMessage(
|
|
192
|
-
return this.message(conversation, { ...
|
|
255
|
+
const text = answerMessage(call.request, call.response);
|
|
256
|
+
return this.message(conversation, { ...call, text, files: [], first: false });
|
|
193
257
|
}
|
|
194
|
-
question.answer(
|
|
258
|
+
question.answer(call.response);
|
|
195
259
|
}
|
|
196
|
-
return {
|
|
260
|
+
return { result: Promise.resolve({}) };
|
|
197
261
|
}
|
|
198
|
-
|
|
262
|
+
open(conversation, call) {
|
|
199
263
|
if (conversation.running !== null)
|
|
200
|
-
return {};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.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;
|
|
302
|
+
}
|
|
303
|
+
async message(conversation, call) {
|
|
304
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
210
305
|
let route;
|
|
306
|
+
let instructing = null;
|
|
211
307
|
try {
|
|
212
|
-
route = await this.route(conversation,
|
|
213
|
-
|
|
308
|
+
route = await this.route(conversation, call.agent_id);
|
|
309
|
+
if (call.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
311
|
+
const files = call.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
214
314
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
215
315
|
const lines = await this.transcribed(conversation, files, paths);
|
|
216
316
|
if (lines.length > 0)
|
|
@@ -219,11 +319,13 @@ export class Conversations {
|
|
|
219
319
|
throw new Error(KILLED);
|
|
220
320
|
}
|
|
221
321
|
catch (error) {
|
|
222
|
-
|
|
322
|
+
this.prepares(conversation, null);
|
|
323
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
223
324
|
}
|
|
325
|
+
conversation.preparing = null;
|
|
224
326
|
return {
|
|
225
|
-
|
|
226
|
-
conversation.waiting.push({
|
|
327
|
+
result: new Promise((settle) => {
|
|
328
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
227
329
|
this.next(conversation);
|
|
228
330
|
}),
|
|
229
331
|
};
|
|
@@ -232,43 +334,86 @@ export class Conversations {
|
|
|
232
334
|
if (conversation.sending !== null || conversation.open > 0)
|
|
233
335
|
return;
|
|
234
336
|
const waiting = conversation.waiting.shift();
|
|
235
|
-
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);
|
|
236
341
|
return;
|
|
342
|
+
}
|
|
237
343
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
238
344
|
conversation.sending = null;
|
|
239
345
|
this.next(conversation);
|
|
240
346
|
});
|
|
241
347
|
}
|
|
242
348
|
async send(conversation, waiting) {
|
|
243
|
-
const {
|
|
244
|
-
let
|
|
349
|
+
const { call } = waiting;
|
|
350
|
+
let result = {};
|
|
351
|
+
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
245
353
|
const prompt = [];
|
|
246
354
|
try {
|
|
247
355
|
if (conversation.running === null) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
359
|
+
if (conversation.running === null) {
|
|
360
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
361
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (call.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
252
366
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
367
|
+
const session = conversation.running.sessionId;
|
|
368
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
369
|
+
result = { provider_session_id: session };
|
|
370
|
+
}
|
|
371
|
+
if (call.first === true) {
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
258
373
|
}
|
|
259
374
|
if (conversation.kills > 0)
|
|
260
375
|
throw new Error(KILLED);
|
|
261
376
|
}
|
|
262
377
|
catch (error) {
|
|
263
|
-
|
|
378
|
+
this.starts(conversation, null);
|
|
379
|
+
if (started) {
|
|
264
380
|
conversation.session = null;
|
|
265
381
|
await this.kill(conversation);
|
|
266
382
|
}
|
|
267
|
-
waiting.settle({ refused: conversation
|
|
383
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
388
|
+
waiting.settle(result);
|
|
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);
|
|
268
397
|
return;
|
|
269
398
|
}
|
|
270
|
-
|
|
271
|
-
|
|
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 });
|
|
272
417
|
}
|
|
273
418
|
async transcribed(conversation, files, paths) {
|
|
274
419
|
const lines = [];
|
|
@@ -276,6 +421,7 @@ export class Conversations {
|
|
|
276
421
|
lines.push(path);
|
|
277
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
278
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
279
425
|
try {
|
|
280
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
281
427
|
if (text !== null && text !== '')
|
|
@@ -342,14 +488,16 @@ export class Conversations {
|
|
|
342
488
|
}
|
|
343
489
|
return route;
|
|
344
490
|
}
|
|
345
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
346
493
|
const reader = readerFor(route.kind);
|
|
347
494
|
reader.session = sessionId;
|
|
348
495
|
const app = client({ name: '@agentshouse/kit' })
|
|
349
496
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
350
|
-
.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));
|
|
351
499
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
352
|
-
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));
|
|
353
501
|
if (conversation.kills > 0) {
|
|
354
502
|
bridge.close();
|
|
355
503
|
throw new Error(KILLED);
|
|
@@ -357,39 +505,39 @@ export class Conversations {
|
|
|
357
505
|
conversation.opening = bridge;
|
|
358
506
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
359
507
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
|
+
let adapter = null;
|
|
360
509
|
try {
|
|
361
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
511
|
+
bridged(bridge);
|
|
362
512
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
363
|
-
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) => {
|
|
364
515
|
work.read(message);
|
|
365
516
|
this.read(conversation, reader, message);
|
|
366
|
-
}, { ...bridge.env, ...
|
|
517
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
367
518
|
void adapter.exited.then(() => bridge.close());
|
|
368
519
|
const agent = adapter.connection.agent;
|
|
369
520
|
const cwd = route.working_directory;
|
|
370
521
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
: { ...(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 })));
|
|
374
524
|
reader.session = opened.sessionId;
|
|
375
|
-
if (sessionId
|
|
525
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
376
526
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
377
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
378
528
|
}
|
|
379
529
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
380
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
530
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
381
531
|
void work.settled().then(() => scope.record());
|
|
382
532
|
conversation.running = running;
|
|
383
533
|
conversation.session = running.sessionId;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.options(conversation, options);
|
|
388
|
-
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));
|
|
389
537
|
return running.sessionId;
|
|
390
538
|
}
|
|
391
539
|
catch (error) {
|
|
392
|
-
|
|
540
|
+
adapter?.retire();
|
|
393
541
|
work.close();
|
|
394
542
|
conversation.leftovers.add(scope);
|
|
395
543
|
bridge.close();
|
|
@@ -399,6 +547,18 @@ export class Conversations {
|
|
|
399
547
|
conversation.opening = null;
|
|
400
548
|
}
|
|
401
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
|
+
}
|
|
402
562
|
async launchSettings(running, route, offered) {
|
|
403
563
|
let options = offered;
|
|
404
564
|
const settings = [
|
|
@@ -420,13 +580,10 @@ export class Conversations {
|
|
|
420
580
|
}
|
|
421
581
|
return options;
|
|
422
582
|
}
|
|
423
|
-
exited(conversation,
|
|
424
|
-
const running = conversation.running;
|
|
425
|
-
if (running?.adapter !== adapter)
|
|
426
|
-
return;
|
|
583
|
+
exited(conversation, running, cause) {
|
|
427
584
|
this.detach(conversation, running);
|
|
428
585
|
if (conversation.turn !== null) {
|
|
429
|
-
this.end(conversation, conversation.turn,
|
|
586
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
430
587
|
}
|
|
431
588
|
this.next(conversation);
|
|
432
589
|
this.kit.changed();
|
|
@@ -436,11 +593,22 @@ export class Conversations {
|
|
|
436
593
|
conversation.leftovers.add(running.scope);
|
|
437
594
|
conversation.running = null;
|
|
438
595
|
conversation.commands = null;
|
|
596
|
+
conversation.options = null;
|
|
439
597
|
for (const [id, question] of this.questions) {
|
|
440
598
|
if (question.conversation === conversation)
|
|
441
599
|
this.questions.delete(id);
|
|
442
600
|
}
|
|
443
|
-
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);
|
|
444
612
|
}
|
|
445
613
|
commands(conversation, commands) {
|
|
446
614
|
conversation.commands = commands;
|
|
@@ -451,6 +619,7 @@ export class Conversations {
|
|
|
451
619
|
options(conversation, options) {
|
|
452
620
|
if (conversation.running === null)
|
|
453
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
454
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
455
624
|
}
|
|
456
625
|
deliver(conversation, turn, event, body) {
|
|
@@ -465,7 +634,7 @@ export class Conversations {
|
|
|
465
634
|
return delivered;
|
|
466
635
|
}
|
|
467
636
|
write(conversation, turn, operation) {
|
|
468
|
-
if (
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
469
638
|
return;
|
|
470
639
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
471
640
|
}
|
|
@@ -476,12 +645,7 @@ export class Conversations {
|
|
|
476
645
|
begin(conversation) {
|
|
477
646
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
478
647
|
conversation.open++;
|
|
479
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
480
|
-
.then(() => {
|
|
481
|
-
turn.admitted = true;
|
|
482
|
-
this.replay(conversation, turn);
|
|
483
|
-
})
|
|
484
|
-
.catch(logged);
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
485
649
|
conversation.turn = turn;
|
|
486
650
|
if (conversation.context !== null)
|
|
487
651
|
turn.parts.measure(conversation.context);
|
|
@@ -490,13 +654,17 @@ export class Conversations {
|
|
|
490
654
|
this.kit.changed();
|
|
491
655
|
return turn;
|
|
492
656
|
}
|
|
493
|
-
end(conversation, turn, failed) {
|
|
657
|
+
end(conversation, turn, failed, notice = null) {
|
|
494
658
|
if (turn.ended)
|
|
495
659
|
return;
|
|
660
|
+
if (failed !== null)
|
|
661
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
496
662
|
if (conversation.turn === turn) {
|
|
497
663
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
498
664
|
this.apply(conversation, turn, event);
|
|
499
665
|
}
|
|
666
|
+
if (notice !== null)
|
|
667
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
500
668
|
turn.ended = true;
|
|
501
669
|
conversation.open--;
|
|
502
670
|
if (conversation.turn === turn)
|
|
@@ -504,31 +672,45 @@ export class Conversations {
|
|
|
504
672
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
505
673
|
parts: turn.parts.saved(),
|
|
506
674
|
context: turn.parts.context,
|
|
507
|
-
...failed,
|
|
675
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
508
676
|
}));
|
|
509
677
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
510
678
|
this.next(conversation);
|
|
511
679
|
this.kit.changed();
|
|
512
680
|
}
|
|
513
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
514
682
|
const running = conversation.running;
|
|
515
683
|
const turn = this.begin(conversation);
|
|
516
684
|
turn.prompted = true;
|
|
517
|
-
|
|
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) => {
|
|
518
697
|
if (running.adapter.connection.signal.aborted) {
|
|
519
|
-
this.end(conversation, turn,
|
|
698
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
520
699
|
}
|
|
521
700
|
else {
|
|
701
|
+
this.took(turn);
|
|
522
702
|
if (conversation.open === 1 && conversation.running === running) {
|
|
523
|
-
running.
|
|
703
|
+
running.adapter.retire();
|
|
524
704
|
this.detach(conversation, running);
|
|
525
705
|
}
|
|
526
|
-
this.end(conversation, turn,
|
|
706
|
+
this.end(conversation, turn, answeredWith(error));
|
|
527
707
|
}
|
|
528
708
|
});
|
|
529
709
|
}
|
|
530
710
|
started(conversation) {
|
|
531
|
-
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
713
|
+
return turn;
|
|
532
714
|
}
|
|
533
715
|
ended(conversation) {
|
|
534
716
|
const turn = conversation.turn;
|
|
@@ -623,6 +805,11 @@ export class Conversations {
|
|
|
623
805
|
}
|
|
624
806
|
this.apply(conversation, this.started(conversation), event);
|
|
625
807
|
}
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
812
|
+
}
|
|
626
813
|
apply(conversation, turn, event) {
|
|
627
814
|
switch (event.kind) {
|
|
628
815
|
case 'text':
|