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