@agentshouse/kit 0.2.1-c8179f06883b6 → 0.2.1-c845e172df1af
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 +30 -29
- package/dist/acp.js +36 -16
- package/dist/agents.js +106 -23
- package/dist/api.js +5 -5
- 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 +14 -4
- package/dist/connection.js +125 -0
- package/dist/conversations.js +323 -127
- package/dist/copies.js +89 -52
- package/dist/end.js +9 -4
- package/dist/events.js +6 -2
- package/dist/files.js +117 -22
- package/dist/git.js +11 -9
- package/dist/home.js +56 -5
- package/dist/house-cli.js +327 -0
- package/dist/house-main.js +7 -228
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -67
- package/dist/login.js +40 -16
- package/dist/logout.js +0 -2
- package/dist/mcp.js +4 -2
- package/dist/placement.js +9 -18
- package/dist/resident.js +82 -37
- 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 +43 -45
- package/dist/wire.js +19 -3
- 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/codex.js +0 -61
- package/dist/stream.js +0 -77
- 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,107 @@
|
|
|
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, HouseRefusal } 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 { placeFiles,
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
+
import { placeFiles, storeRefusal, uploaded } 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';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
14
17
|
import { killMarked, Scope } from './scope.js';
|
|
15
18
|
import { holdSecretInput } from './secret-input.js';
|
|
16
19
|
import { createHowWeWork } from './skills.js';
|
|
17
20
|
import { transcribe } from './transcription.js';
|
|
18
21
|
import { workFor } from './work.js';
|
|
22
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
19
23
|
const KILLED = 'the conversation was killed';
|
|
20
|
-
|
|
24
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
25
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
26
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
27
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
28
|
+
const ENDED = {
|
|
29
|
+
conversation_not_found: GONE,
|
|
30
|
+
conversation_historical: RETIRED,
|
|
31
|
+
};
|
|
32
|
+
const NO_START = "the Agent's CLI did not start";
|
|
33
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
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";
|
|
36
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
37
|
+
class NotStarted extends Error {
|
|
38
|
+
}
|
|
39
|
+
async function step(reason, work) {
|
|
40
|
+
try {
|
|
41
|
+
return await work;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function signedOut(error) {
|
|
48
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
49
|
+
}
|
|
50
|
+
function unopened(reason) {
|
|
51
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
52
|
+
}
|
|
53
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
54
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
55
|
+
function unbridged(error) {
|
|
56
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
57
|
+
}
|
|
58
|
+
const EXIT_PLAN_MODE = {
|
|
59
|
+
parse(value) {
|
|
60
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
61
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
21
67
|
function causeOf(error) {
|
|
22
68
|
return error instanceof Error ? error.message : String(error);
|
|
23
69
|
}
|
|
70
|
+
function answeredWith(error) {
|
|
71
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
72
|
+
}
|
|
73
|
+
function exitedWith(running, cause) {
|
|
74
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
75
|
+
}
|
|
24
76
|
function answerMessage(request, response) {
|
|
25
77
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
26
78
|
}
|
|
79
|
+
async function sentPart(url, bytes) {
|
|
80
|
+
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
81
|
+
await answer.arrayBuffer();
|
|
82
|
+
if (!answer.ok)
|
|
83
|
+
throw storeRefusal('upload', answer.status);
|
|
84
|
+
}
|
|
27
85
|
function logged(error) {
|
|
28
86
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
29
87
|
}
|
|
88
|
+
function refusal(conversation, error) {
|
|
89
|
+
if (conversation.kills > 0)
|
|
90
|
+
return KILLED;
|
|
91
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
92
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
93
|
+
return causeOf(error);
|
|
94
|
+
}
|
|
30
95
|
function marker(bridge) {
|
|
31
96
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
32
97
|
}
|
|
33
98
|
class Turn {
|
|
34
99
|
id = randomUUID();
|
|
35
100
|
parts;
|
|
36
|
-
admitted = false;
|
|
37
101
|
questions = 0;
|
|
38
102
|
secrets = 0;
|
|
39
103
|
prompted = false;
|
|
104
|
+
taken = null;
|
|
40
105
|
ended = false;
|
|
41
106
|
images = 0;
|
|
42
107
|
uploads = [];
|
|
@@ -50,6 +115,8 @@ class Conversation {
|
|
|
50
115
|
session = null;
|
|
51
116
|
turn = null;
|
|
52
117
|
commands = null;
|
|
118
|
+
options = null;
|
|
119
|
+
exitUntold = false;
|
|
53
120
|
context = null;
|
|
54
121
|
leftovers = new Set();
|
|
55
122
|
waiting = [];
|
|
@@ -57,6 +124,8 @@ class Conversation {
|
|
|
57
124
|
subagents = new Map();
|
|
58
125
|
held = [];
|
|
59
126
|
opening = null;
|
|
127
|
+
launch = null;
|
|
128
|
+
prompted = null;
|
|
60
129
|
kills = 0;
|
|
61
130
|
killed = new AbortController();
|
|
62
131
|
interrupts = 0;
|
|
@@ -64,6 +133,11 @@ class Conversation {
|
|
|
64
133
|
open = 0;
|
|
65
134
|
queue = Promise.resolve();
|
|
66
135
|
reports = Promise.resolve();
|
|
136
|
+
starting = null;
|
|
137
|
+
preparing = null;
|
|
138
|
+
told = null;
|
|
139
|
+
untold = false;
|
|
140
|
+
shown = null;
|
|
67
141
|
constructor(id) {
|
|
68
142
|
this.id = id;
|
|
69
143
|
}
|
|
@@ -79,9 +153,7 @@ function asksSecret(request) {
|
|
|
79
153
|
export class Conversations {
|
|
80
154
|
kit;
|
|
81
155
|
conversations = new Map();
|
|
82
|
-
acks = new Map();
|
|
83
156
|
questions = new Map();
|
|
84
|
-
carrying = 0;
|
|
85
157
|
reporting = 0;
|
|
86
158
|
watched = false;
|
|
87
159
|
constructor(kit) {
|
|
@@ -89,38 +161,27 @@ export class Conversations {
|
|
|
89
161
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
90
162
|
setInterval(() => this.look(), 2_000);
|
|
91
163
|
}
|
|
92
|
-
|
|
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();
|
|
164
|
+
call(call) {
|
|
165
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
166
|
+
const conversation = this.conversation(call.conversation_id);
|
|
167
|
+
if (call.kind === 'kill') {
|
|
168
|
+
conversation.kills++;
|
|
169
|
+
conversation.killed.abort(new Error(KILLED));
|
|
170
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
171
|
+
waiting.settle({ refused: KILLED });
|
|
172
|
+
this.halt(conversation);
|
|
173
|
+
}
|
|
174
|
+
if (call.kind === 'interrupt') {
|
|
175
|
+
conversation.interrupts++;
|
|
176
|
+
conversation.interrupted.abort();
|
|
177
|
+
}
|
|
178
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
179
|
+
conversation.queue = handled.catch(() => undefined);
|
|
180
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
120
181
|
});
|
|
121
182
|
}
|
|
122
183
|
idle() {
|
|
123
|
-
if (this.
|
|
184
|
+
if (this.reporting > 0)
|
|
124
185
|
return false;
|
|
125
186
|
for (const conversation of this.conversations.values()) {
|
|
126
187
|
const turn = conversation.turn;
|
|
@@ -149,6 +210,14 @@ export class Conversations {
|
|
|
149
210
|
}
|
|
150
211
|
opened() {
|
|
151
212
|
this.watched = false;
|
|
213
|
+
for (const conversation of this.conversations.values()) {
|
|
214
|
+
if (conversation.running !== null)
|
|
215
|
+
this.facts(conversation);
|
|
216
|
+
else if (conversation.exitUntold)
|
|
217
|
+
this.process(conversation, false);
|
|
218
|
+
if (conversation.untold || conversation.told !== null)
|
|
219
|
+
this.retell(conversation);
|
|
220
|
+
}
|
|
152
221
|
}
|
|
153
222
|
watching(watched) {
|
|
154
223
|
const resend = watched && !this.watched;
|
|
@@ -168,50 +237,87 @@ export class Conversations {
|
|
|
168
237
|
}
|
|
169
238
|
return conversation;
|
|
170
239
|
}
|
|
171
|
-
async carry(conversation,
|
|
172
|
-
if (
|
|
173
|
-
return {
|
|
174
|
-
if (
|
|
175
|
-
return this.message(conversation,
|
|
176
|
-
|
|
240
|
+
async carry(conversation, call) {
|
|
241
|
+
if (call.kind === 'open')
|
|
242
|
+
return { result: this.open(conversation, call) };
|
|
243
|
+
if (call.kind === 'message')
|
|
244
|
+
return this.message(conversation, call);
|
|
245
|
+
await this.launched(conversation);
|
|
246
|
+
if (call.kind === 'interrupt') {
|
|
177
247
|
if (--conversation.interrupts === 0)
|
|
178
248
|
conversation.interrupted = new AbortController();
|
|
179
|
-
await this.interrupt(conversation, String(
|
|
249
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
180
250
|
}
|
|
181
|
-
if (
|
|
251
|
+
if (call.kind === 'kill') {
|
|
182
252
|
await this.kill(conversation);
|
|
183
253
|
await conversation.sending;
|
|
184
254
|
if (--conversation.kills === 0)
|
|
185
255
|
conversation.killed = new AbortController();
|
|
186
256
|
}
|
|
187
|
-
if (
|
|
188
|
-
await this.option(conversation, String(
|
|
189
|
-
if (
|
|
190
|
-
const question = this.questions.get(String(
|
|
257
|
+
if (call.kind === 'option')
|
|
258
|
+
await this.option(conversation, String(call.option), call.value);
|
|
259
|
+
if (call.kind === 'answer') {
|
|
260
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
191
261
|
if (question === undefined) {
|
|
192
|
-
const text = answerMessage(
|
|
193
|
-
return this.message(conversation, { ...
|
|
262
|
+
const text = answerMessage(call.request, call.response);
|
|
263
|
+
return this.message(conversation, { ...call, text, files: [], first: false });
|
|
194
264
|
}
|
|
195
|
-
question.answer(
|
|
265
|
+
question.answer(call.response);
|
|
196
266
|
}
|
|
197
|
-
return {
|
|
267
|
+
return { result: Promise.resolve({}) };
|
|
198
268
|
}
|
|
199
|
-
|
|
269
|
+
open(conversation, call) {
|
|
200
270
|
if (conversation.running !== null)
|
|
201
|
-
return {};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
271
|
+
return Promise.resolve({});
|
|
272
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
273
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
274
|
+
}
|
|
275
|
+
launch(conversation, route, sessionId, empty) {
|
|
276
|
+
let opened;
|
|
277
|
+
let failed;
|
|
278
|
+
const bridge = new Promise((resolve, reject) => {
|
|
279
|
+
opened = resolve;
|
|
280
|
+
failed = reject;
|
|
281
|
+
});
|
|
282
|
+
bridge.catch(() => undefined);
|
|
283
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
284
|
+
session.catch(failed);
|
|
285
|
+
const launch = { bridge, session };
|
|
286
|
+
conversation.launch = launch;
|
|
287
|
+
void session
|
|
288
|
+
.catch(() => undefined)
|
|
289
|
+
.then(() => {
|
|
290
|
+
if (conversation.launch !== launch)
|
|
291
|
+
return;
|
|
292
|
+
conversation.launch = null;
|
|
293
|
+
this.next(conversation);
|
|
294
|
+
});
|
|
295
|
+
this.next(conversation);
|
|
296
|
+
return launch;
|
|
297
|
+
}
|
|
298
|
+
async launched(conversation) {
|
|
299
|
+
while (conversation.launch !== null)
|
|
300
|
+
await conversation.launch.session.catch(() => undefined);
|
|
301
|
+
}
|
|
302
|
+
instructed(conversation, route) {
|
|
303
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
304
|
+
if (bridge === undefined)
|
|
305
|
+
return null;
|
|
306
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
307
|
+
read.catch(() => undefined);
|
|
308
|
+
return read;
|
|
309
|
+
}
|
|
310
|
+
async message(conversation, call) {
|
|
311
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
211
312
|
let route;
|
|
313
|
+
let instructing = null;
|
|
212
314
|
try {
|
|
213
|
-
route = await this.route(conversation,
|
|
214
|
-
|
|
315
|
+
route = await this.route(conversation, call.agent_id);
|
|
316
|
+
if (call.first === true)
|
|
317
|
+
instructing = this.instructed(conversation, route);
|
|
318
|
+
const files = call.files;
|
|
319
|
+
if (files.length > 0)
|
|
320
|
+
this.prepares(conversation, 'files');
|
|
215
321
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
216
322
|
const lines = await this.transcribed(conversation, files, paths);
|
|
217
323
|
if (lines.length > 0)
|
|
@@ -220,11 +326,13 @@ export class Conversations {
|
|
|
220
326
|
throw new Error(KILLED);
|
|
221
327
|
}
|
|
222
328
|
catch (error) {
|
|
223
|
-
|
|
329
|
+
this.prepares(conversation, null);
|
|
330
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
224
331
|
}
|
|
332
|
+
conversation.preparing = null;
|
|
225
333
|
return {
|
|
226
|
-
|
|
227
|
-
conversation.waiting.push({
|
|
334
|
+
result: new Promise((settle) => {
|
|
335
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
228
336
|
this.next(conversation);
|
|
229
337
|
}),
|
|
230
338
|
};
|
|
@@ -233,43 +341,86 @@ export class Conversations {
|
|
|
233
341
|
if (conversation.sending !== null || conversation.open > 0)
|
|
234
342
|
return;
|
|
235
343
|
const waiting = conversation.waiting.shift();
|
|
236
|
-
if (waiting === undefined)
|
|
344
|
+
if (waiting === undefined) {
|
|
345
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
346
|
+
if (conversation.starting !== step)
|
|
347
|
+
this.starts(conversation, step);
|
|
237
348
|
return;
|
|
349
|
+
}
|
|
238
350
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
239
351
|
conversation.sending = null;
|
|
240
352
|
this.next(conversation);
|
|
241
353
|
});
|
|
242
354
|
}
|
|
243
355
|
async send(conversation, waiting) {
|
|
244
|
-
const {
|
|
245
|
-
let
|
|
356
|
+
const { call } = waiting;
|
|
357
|
+
let result = {};
|
|
358
|
+
let started = false;
|
|
359
|
+
let instructing = waiting.instructing;
|
|
246
360
|
const prompt = [];
|
|
247
361
|
try {
|
|
248
362
|
if (conversation.running === null) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
363
|
+
this.starts(conversation, 'session');
|
|
364
|
+
await this.launched(conversation);
|
|
365
|
+
}
|
|
366
|
+
if (conversation.running === null) {
|
|
367
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
368
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
369
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
370
|
+
if (call.first === true)
|
|
371
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
372
|
+
started = (await launch.session) !== stored;
|
|
373
|
+
}
|
|
374
|
+
const session = conversation.running.sessionId;
|
|
375
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
376
|
+
result = { provider_session_id: session };
|
|
253
377
|
}
|
|
254
|
-
if (
|
|
255
|
-
prompt.push({
|
|
256
|
-
type: 'text',
|
|
257
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
258
|
-
});
|
|
378
|
+
if (call.first === true) {
|
|
379
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
259
380
|
}
|
|
260
381
|
if (conversation.kills > 0)
|
|
261
382
|
throw new Error(KILLED);
|
|
262
383
|
}
|
|
263
384
|
catch (error) {
|
|
264
|
-
|
|
385
|
+
this.starts(conversation, null);
|
|
386
|
+
if (started) {
|
|
265
387
|
conversation.session = null;
|
|
266
388
|
await this.kill(conversation);
|
|
267
389
|
}
|
|
268
|
-
waiting.settle({ refused: conversation
|
|
390
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
269
391
|
return;
|
|
270
392
|
}
|
|
271
|
-
this.
|
|
272
|
-
waiting.
|
|
393
|
+
this.starts(conversation, null);
|
|
394
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
395
|
+
waiting.settle(result);
|
|
396
|
+
}
|
|
397
|
+
starts(conversation, step) {
|
|
398
|
+
conversation.starting = step;
|
|
399
|
+
if (conversation.shown !== null)
|
|
400
|
+
clearTimeout(conversation.shown);
|
|
401
|
+
conversation.shown = null;
|
|
402
|
+
if (step === null || conversation.told !== null) {
|
|
403
|
+
this.tell(conversation, step);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
conversation.shown = setTimeout(() => {
|
|
407
|
+
conversation.shown = null;
|
|
408
|
+
if (conversation.starting === step)
|
|
409
|
+
this.tell(conversation, step);
|
|
410
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
411
|
+
}
|
|
412
|
+
prepares(conversation, step) {
|
|
413
|
+
conversation.preparing = step;
|
|
414
|
+
this.next(conversation);
|
|
415
|
+
}
|
|
416
|
+
tell(conversation, step) {
|
|
417
|
+
if (conversation.told === step && !conversation.untold)
|
|
418
|
+
return;
|
|
419
|
+
conversation.told = step;
|
|
420
|
+
this.retell(conversation);
|
|
421
|
+
}
|
|
422
|
+
retell(conversation) {
|
|
423
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
273
424
|
}
|
|
274
425
|
async transcribed(conversation, files, paths) {
|
|
275
426
|
const lines = [];
|
|
@@ -277,6 +428,7 @@ export class Conversations {
|
|
|
277
428
|
lines.push(path);
|
|
278
429
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
279
430
|
continue;
|
|
431
|
+
this.prepares(conversation, 'transcription');
|
|
280
432
|
try {
|
|
281
433
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
282
434
|
if (text !== null && text !== '')
|
|
@@ -343,14 +495,16 @@ export class Conversations {
|
|
|
343
495
|
}
|
|
344
496
|
return route;
|
|
345
497
|
}
|
|
346
|
-
async start(conversation,
|
|
498
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
499
|
+
const route = await routed;
|
|
347
500
|
const reader = readerFor(route.kind);
|
|
348
501
|
reader.session = sessionId;
|
|
349
502
|
const app = client({ name: '@agentshouse/kit' })
|
|
350
503
|
.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))
|
|
504
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
505
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
352
506
|
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);
|
|
507
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
354
508
|
if (conversation.kills > 0) {
|
|
355
509
|
bridge.close();
|
|
356
510
|
throw new Error(KILLED);
|
|
@@ -358,39 +512,39 @@ export class Conversations {
|
|
|
358
512
|
conversation.opening = bridge;
|
|
359
513
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
360
514
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
515
|
+
let adapter = null;
|
|
361
516
|
try {
|
|
362
517
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
518
|
+
bridged(bridge);
|
|
363
519
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
364
|
-
const
|
|
520
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
521
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
365
522
|
work.read(message);
|
|
366
523
|
this.read(conversation, reader, message);
|
|
367
|
-
}, { ...bridge.env, ...
|
|
524
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
368
525
|
void adapter.exited.then(() => bridge.close());
|
|
369
526
|
const agent = adapter.connection.agent;
|
|
370
527
|
const cwd = route.working_directory;
|
|
371
528
|
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 };
|
|
529
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
530
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
375
531
|
reader.session = opened.sessionId;
|
|
376
|
-
if (sessionId
|
|
532
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
377
533
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
378
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
534
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
379
535
|
}
|
|
380
536
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
381
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
537
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
382
538
|
void work.settled().then(() => scope.record());
|
|
383
539
|
conversation.running = running;
|
|
384
540
|
conversation.session = running.sessionId;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this.options(conversation, options);
|
|
389
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
541
|
+
conversation.options = options;
|
|
542
|
+
this.facts(conversation);
|
|
543
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
390
544
|
return running.sessionId;
|
|
391
545
|
}
|
|
392
546
|
catch (error) {
|
|
393
|
-
|
|
547
|
+
adapter?.retire();
|
|
394
548
|
work.close();
|
|
395
549
|
conversation.leftovers.add(scope);
|
|
396
550
|
bridge.close();
|
|
@@ -400,6 +554,18 @@ export class Conversations {
|
|
|
400
554
|
conversation.opening = null;
|
|
401
555
|
}
|
|
402
556
|
}
|
|
557
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
558
|
+
try {
|
|
559
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
563
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
564
|
+
}
|
|
565
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
403
569
|
async launchSettings(running, route, offered) {
|
|
404
570
|
let options = offered;
|
|
405
571
|
const settings = [
|
|
@@ -421,13 +587,10 @@ export class Conversations {
|
|
|
421
587
|
}
|
|
422
588
|
return options;
|
|
423
589
|
}
|
|
424
|
-
exited(conversation,
|
|
425
|
-
const running = conversation.running;
|
|
426
|
-
if (running?.adapter !== adapter)
|
|
427
|
-
return;
|
|
590
|
+
exited(conversation, running, cause) {
|
|
428
591
|
this.detach(conversation, running);
|
|
429
592
|
if (conversation.turn !== null) {
|
|
430
|
-
this.end(conversation, conversation.turn,
|
|
593
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
431
594
|
}
|
|
432
595
|
this.next(conversation);
|
|
433
596
|
this.kit.changed();
|
|
@@ -437,11 +600,22 @@ export class Conversations {
|
|
|
437
600
|
conversation.leftovers.add(running.scope);
|
|
438
601
|
conversation.running = null;
|
|
439
602
|
conversation.commands = null;
|
|
603
|
+
conversation.options = null;
|
|
440
604
|
for (const [id, question] of this.questions) {
|
|
441
605
|
if (question.conversation === conversation)
|
|
442
606
|
this.questions.delete(id);
|
|
443
607
|
}
|
|
444
|
-
this.
|
|
608
|
+
this.process(conversation, false);
|
|
609
|
+
}
|
|
610
|
+
process(conversation, running) {
|
|
611
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
612
|
+
}
|
|
613
|
+
facts(conversation) {
|
|
614
|
+
this.process(conversation, true);
|
|
615
|
+
if (conversation.commands !== null)
|
|
616
|
+
this.commands(conversation, conversation.commands);
|
|
617
|
+
if (conversation.options !== null)
|
|
618
|
+
this.options(conversation, conversation.options);
|
|
445
619
|
}
|
|
446
620
|
commands(conversation, commands) {
|
|
447
621
|
conversation.commands = commands;
|
|
@@ -452,6 +626,7 @@ export class Conversations {
|
|
|
452
626
|
options(conversation, options) {
|
|
453
627
|
if (conversation.running === null)
|
|
454
628
|
return;
|
|
629
|
+
conversation.options = options;
|
|
455
630
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
456
631
|
}
|
|
457
632
|
deliver(conversation, turn, event, body) {
|
|
@@ -466,7 +641,7 @@ export class Conversations {
|
|
|
466
641
|
return delivered;
|
|
467
642
|
}
|
|
468
643
|
write(conversation, turn, operation) {
|
|
469
|
-
if (
|
|
644
|
+
if (turn.ended || !this.watched)
|
|
470
645
|
return;
|
|
471
646
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
472
647
|
}
|
|
@@ -477,12 +652,7 @@ export class Conversations {
|
|
|
477
652
|
begin(conversation) {
|
|
478
653
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
479
654
|
conversation.open++;
|
|
480
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
481
|
-
.then(() => {
|
|
482
|
-
turn.admitted = true;
|
|
483
|
-
this.replay(conversation, turn);
|
|
484
|
-
})
|
|
485
|
-
.catch(logged);
|
|
655
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
486
656
|
conversation.turn = turn;
|
|
487
657
|
if (conversation.context !== null)
|
|
488
658
|
turn.parts.measure(conversation.context);
|
|
@@ -491,13 +661,17 @@ export class Conversations {
|
|
|
491
661
|
this.kit.changed();
|
|
492
662
|
return turn;
|
|
493
663
|
}
|
|
494
|
-
end(conversation, turn, failed) {
|
|
664
|
+
end(conversation, turn, failed, notice = null) {
|
|
495
665
|
if (turn.ended)
|
|
496
666
|
return;
|
|
667
|
+
if (failed !== null)
|
|
668
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
497
669
|
if (conversation.turn === turn) {
|
|
498
670
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
499
671
|
this.apply(conversation, turn, event);
|
|
500
672
|
}
|
|
673
|
+
if (notice !== null)
|
|
674
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
501
675
|
turn.ended = true;
|
|
502
676
|
conversation.open--;
|
|
503
677
|
if (conversation.turn === turn)
|
|
@@ -505,31 +679,45 @@ export class Conversations {
|
|
|
505
679
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
506
680
|
parts: turn.parts.saved(),
|
|
507
681
|
context: turn.parts.context,
|
|
508
|
-
...failed,
|
|
682
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
509
683
|
}));
|
|
510
684
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
511
685
|
this.next(conversation);
|
|
512
686
|
this.kit.changed();
|
|
513
687
|
}
|
|
514
|
-
prompt(conversation, prompt) {
|
|
688
|
+
prompt(conversation, prompt, taken) {
|
|
515
689
|
const running = conversation.running;
|
|
516
690
|
const turn = this.begin(conversation);
|
|
517
691
|
turn.prompted = true;
|
|
518
|
-
|
|
692
|
+
turn.taken = taken;
|
|
693
|
+
conversation.prompted = running.sessionId;
|
|
694
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
695
|
+
this.took(turn);
|
|
696
|
+
const notice = answeredFailure(answer);
|
|
697
|
+
if (notice !== null) {
|
|
698
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
702
|
+
}
|
|
703
|
+
}, async (error) => {
|
|
519
704
|
if (running.adapter.connection.signal.aborted) {
|
|
520
|
-
this.end(conversation, turn,
|
|
705
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
521
706
|
}
|
|
522
707
|
else {
|
|
708
|
+
this.took(turn);
|
|
523
709
|
if (conversation.open === 1 && conversation.running === running) {
|
|
524
|
-
running.
|
|
710
|
+
running.adapter.retire();
|
|
525
711
|
this.detach(conversation, running);
|
|
526
712
|
}
|
|
527
|
-
this.end(conversation, turn,
|
|
713
|
+
this.end(conversation, turn, answeredWith(error));
|
|
528
714
|
}
|
|
529
715
|
});
|
|
530
716
|
}
|
|
531
717
|
started(conversation) {
|
|
532
|
-
|
|
718
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
719
|
+
this.took(turn);
|
|
720
|
+
return turn;
|
|
533
721
|
}
|
|
534
722
|
ended(conversation) {
|
|
535
723
|
const turn = conversation.turn;
|
|
@@ -624,6 +812,11 @@ export class Conversations {
|
|
|
624
812
|
}
|
|
625
813
|
this.apply(conversation, this.started(conversation), event);
|
|
626
814
|
}
|
|
815
|
+
took(turn) {
|
|
816
|
+
const taken = turn.taken;
|
|
817
|
+
turn.taken = null;
|
|
818
|
+
taken?.();
|
|
819
|
+
}
|
|
627
820
|
apply(conversation, turn, event) {
|
|
628
821
|
switch (event.kind) {
|
|
629
822
|
case 'text':
|
|
@@ -678,13 +871,16 @@ export class Conversations {
|
|
|
678
871
|
bytes: bytes.length,
|
|
679
872
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
680
873
|
}));
|
|
681
|
-
const saved =
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
874
|
+
const saved = await uploaded(declared.upload, {
|
|
875
|
+
bytes: bytes.length,
|
|
876
|
+
send: (url, start, end) => sentPart(url, bytes.subarray(start, end)),
|
|
877
|
+
finish: (upload) => this.kit.house.deliver('/kit/uploads/finish', { upload }),
|
|
878
|
+
});
|
|
879
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
685
880
|
}
|
|
686
881
|
catch (error) {
|
|
687
|
-
|
|
882
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
883
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
688
884
|
}
|
|
689
885
|
}
|
|
690
886
|
}
|