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