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