@agentshouse/kit 0.2.1-cbabe8ad337ff → 0.2.1-cc6246211e00c
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 +42 -41
- package/dist/acp.js +38 -17
- package/dist/agents.js +111 -24
- package/dist/api.js +7 -3
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +11 -25
- package/dist/building.js +46 -0
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +87 -22
- package/dist/connection.js +126 -0
- package/dist/conversations.js +410 -156
- package/dist/copies.js +185 -123
- package/dist/end.js +23 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +6 -2
- package/dist/files.js +139 -31
- package/dist/git.js +13 -11
- package/dist/home.js +96 -7
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -177
- package/dist/instructions.js +8 -10
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +111 -23
- package/dist/logout.js +0 -2
- package/dist/mcp.js +4 -2
- package/dist/parts.js +34 -16
- package/dist/placement.js +39 -0
- package/dist/relay.js +33 -2
- package/dist/resident.js +106 -44
- package/dist/scope.js +3 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +22 -39
- package/dist/skills.js +49 -35
- package/dist/transcription.js +85 -45
- package/dist/translate.js +2 -8
- package/dist/update.js +98 -53
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +22 -0
- package/dist/work.js +3 -5
- package/package.json +20 -20
- package/dist/bootstrap.json +0 -24
- package/dist/stream.js +0 -72
- 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, 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
|
+
}
|
|
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;
|
|
@@ -132,6 +203,9 @@ export class Conversations {
|
|
|
132
203
|
return false;
|
|
133
204
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
134
205
|
}
|
|
206
|
+
quiet() {
|
|
207
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
208
|
+
}
|
|
135
209
|
look() {
|
|
136
210
|
for (const conversation of this.conversations.values()) {
|
|
137
211
|
for (const scope of conversation.leftovers) {
|
|
@@ -145,6 +219,14 @@ export class Conversations {
|
|
|
145
219
|
}
|
|
146
220
|
opened() {
|
|
147
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
|
+
}
|
|
148
230
|
}
|
|
149
231
|
watching(watched) {
|
|
150
232
|
const resend = watched && !this.watched;
|
|
@@ -164,63 +246,99 @@ export class Conversations {
|
|
|
164
246
|
}
|
|
165
247
|
return conversation;
|
|
166
248
|
}
|
|
167
|
-
async carry(conversation,
|
|
168
|
-
if (
|
|
169
|
-
return {
|
|
170
|
-
if (
|
|
171
|
-
return this.message(conversation,
|
|
172
|
-
|
|
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') {
|
|
173
256
|
if (--conversation.interrupts === 0)
|
|
174
257
|
conversation.interrupted = new AbortController();
|
|
175
|
-
await this.interrupt(conversation, String(
|
|
258
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
176
259
|
}
|
|
177
|
-
if (
|
|
260
|
+
if (call.kind === 'kill') {
|
|
178
261
|
await this.kill(conversation);
|
|
179
262
|
await conversation.sending;
|
|
180
263
|
if (--conversation.kills === 0)
|
|
181
264
|
conversation.killed = new AbortController();
|
|
182
265
|
}
|
|
183
|
-
if (
|
|
184
|
-
await this.option(conversation, String(
|
|
185
|
-
if (
|
|
186
|
-
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));
|
|
187
270
|
if (question === undefined) {
|
|
188
|
-
const text = answerMessage(
|
|
189
|
-
return this.message(conversation, { ...
|
|
271
|
+
const text = answerMessage(call.request, call.response);
|
|
272
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
190
273
|
}
|
|
191
|
-
question.answer(
|
|
274
|
+
question.answer(call.response);
|
|
192
275
|
}
|
|
193
|
-
return {
|
|
276
|
+
return { result: Promise.resolve({}) };
|
|
194
277
|
}
|
|
195
|
-
|
|
278
|
+
open(conversation, call) {
|
|
196
279
|
if (conversation.running !== null)
|
|
197
|
-
return {};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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) }];
|
|
207
322
|
let route;
|
|
323
|
+
let instructing = null;
|
|
208
324
|
try {
|
|
209
|
-
route = await this.route(conversation,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
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))));
|
|
215
330
|
if (conversation.kills > 0)
|
|
216
331
|
throw new Error(KILLED);
|
|
217
332
|
}
|
|
218
333
|
catch (error) {
|
|
219
|
-
|
|
334
|
+
this.closeIdleBridge(conversation);
|
|
335
|
+
this.prepares(conversation, null);
|
|
336
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
220
337
|
}
|
|
338
|
+
conversation.preparing = null;
|
|
221
339
|
return {
|
|
222
|
-
|
|
223
|
-
conversation.waiting.push({
|
|
340
|
+
result: new Promise((settle) => {
|
|
341
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
224
342
|
this.next(conversation);
|
|
225
343
|
}),
|
|
226
344
|
};
|
|
@@ -229,62 +347,148 @@ export class Conversations {
|
|
|
229
347
|
if (conversation.sending !== null || conversation.open > 0)
|
|
230
348
|
return;
|
|
231
349
|
const waiting = conversation.waiting.shift();
|
|
232
|
-
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);
|
|
233
354
|
return;
|
|
355
|
+
}
|
|
234
356
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
235
357
|
conversation.sending = null;
|
|
236
358
|
this.next(conversation);
|
|
237
359
|
});
|
|
238
360
|
}
|
|
239
361
|
async send(conversation, waiting) {
|
|
240
|
-
const {
|
|
241
|
-
let
|
|
362
|
+
const { call } = waiting;
|
|
363
|
+
let result = {};
|
|
364
|
+
let started = false;
|
|
365
|
+
let instructing = waiting.instructing;
|
|
242
366
|
const prompt = [];
|
|
243
367
|
try {
|
|
244
368
|
if (conversation.running === null) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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;
|
|
249
379
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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)) });
|
|
255
386
|
}
|
|
256
387
|
if (conversation.kills > 0)
|
|
257
388
|
throw new Error(KILLED);
|
|
258
389
|
}
|
|
259
390
|
catch (error) {
|
|
260
|
-
|
|
391
|
+
this.starts(conversation, null);
|
|
392
|
+
if (started) {
|
|
261
393
|
conversation.session = null;
|
|
262
394
|
await this.kill(conversation);
|
|
263
395
|
}
|
|
264
|
-
|
|
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);
|
|
265
412
|
return;
|
|
266
413
|
}
|
|
267
|
-
|
|
268
|
-
|
|
414
|
+
conversation.shown = setTimeout(() => {
|
|
415
|
+
conversation.shown = null;
|
|
416
|
+
if (conversation.starting === step)
|
|
417
|
+
this.tell(conversation, step);
|
|
418
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
269
419
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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}` });
|
|
285
461
|
}
|
|
286
462
|
}
|
|
287
|
-
|
|
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);
|
|
288
492
|
}
|
|
289
493
|
async interrupt(conversation, turnId) {
|
|
290
494
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -339,63 +543,77 @@ export class Conversations {
|
|
|
339
543
|
}
|
|
340
544
|
return route;
|
|
341
545
|
}
|
|
342
|
-
async start(conversation,
|
|
546
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
547
|
+
const route = await routed;
|
|
343
548
|
const reader = readerFor(route.kind);
|
|
344
549
|
reader.session = sessionId;
|
|
345
550
|
const app = client({ name: '@agentshouse/kit' })
|
|
346
551
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
347
|
-
.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));
|
|
348
554
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
349
|
-
const
|
|
555
|
+
const bridging = this.bridged(conversation);
|
|
556
|
+
const bridge = await step(unbridged, bridging);
|
|
350
557
|
if (conversation.kills > 0) {
|
|
351
|
-
|
|
558
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
352
559
|
throw new Error(KILLED);
|
|
353
560
|
}
|
|
354
561
|
conversation.opening = bridge;
|
|
355
562
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
356
563
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
564
|
+
let adapter = null;
|
|
357
565
|
try {
|
|
358
|
-
|
|
566
|
+
bridged(bridge);
|
|
359
567
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
360
|
-
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) => {
|
|
361
570
|
work.read(message);
|
|
362
571
|
this.read(conversation, reader, message);
|
|
363
|
-
}, { ...bridge.env, ...
|
|
364
|
-
void adapter.exited.then(() =>
|
|
572
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
573
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
365
574
|
const agent = adapter.connection.agent;
|
|
366
575
|
const cwd = route.working_directory;
|
|
367
576
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
: { ...(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 })));
|
|
371
579
|
reader.session = opened.sessionId;
|
|
372
|
-
if (sessionId
|
|
580
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
373
581
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
374
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
582
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
375
583
|
}
|
|
376
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
377
|
-
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 ?? []));
|
|
378
586
|
void work.settled().then(() => scope.record());
|
|
379
587
|
conversation.running = running;
|
|
380
588
|
conversation.session = running.sessionId;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this.options(conversation, options);
|
|
385
|
-
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));
|
|
386
592
|
return running.sessionId;
|
|
387
593
|
}
|
|
388
594
|
catch (error) {
|
|
389
|
-
|
|
595
|
+
adapter?.retire();
|
|
390
596
|
work.close();
|
|
391
597
|
conversation.leftovers.add(scope);
|
|
392
|
-
|
|
598
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
393
599
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
394
600
|
}
|
|
395
601
|
finally {
|
|
396
602
|
conversation.opening = null;
|
|
397
603
|
}
|
|
398
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
|
+
}
|
|
399
617
|
async launchSettings(running, route, offered) {
|
|
400
618
|
let options = offered;
|
|
401
619
|
const settings = [
|
|
@@ -417,27 +635,37 @@ export class Conversations {
|
|
|
417
635
|
}
|
|
418
636
|
return options;
|
|
419
637
|
}
|
|
420
|
-
exited(conversation,
|
|
421
|
-
const running = conversation.running;
|
|
422
|
-
if (running?.adapter !== adapter)
|
|
423
|
-
return;
|
|
638
|
+
exited(conversation, running, cause) {
|
|
424
639
|
this.detach(conversation, running);
|
|
425
640
|
if (conversation.turn !== null) {
|
|
426
|
-
this.end(conversation, conversation.turn,
|
|
641
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
427
642
|
}
|
|
428
643
|
this.next(conversation);
|
|
429
644
|
this.kit.changed();
|
|
430
645
|
}
|
|
431
646
|
detach(conversation, running) {
|
|
647
|
+
if (conversation.bridge === running.bridging)
|
|
648
|
+
conversation.bridge = null;
|
|
432
649
|
running.work.close();
|
|
433
650
|
conversation.leftovers.add(running.scope);
|
|
434
651
|
conversation.running = null;
|
|
435
652
|
conversation.commands = null;
|
|
653
|
+
conversation.options = null;
|
|
436
654
|
for (const [id, question] of this.questions) {
|
|
437
655
|
if (question.conversation === conversation)
|
|
438
656
|
this.questions.delete(id);
|
|
439
657
|
}
|
|
440
|
-
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);
|
|
441
669
|
}
|
|
442
670
|
commands(conversation, commands) {
|
|
443
671
|
conversation.commands = commands;
|
|
@@ -448,6 +676,7 @@ export class Conversations {
|
|
|
448
676
|
options(conversation, options) {
|
|
449
677
|
if (conversation.running === null)
|
|
450
678
|
return;
|
|
679
|
+
conversation.options = options;
|
|
451
680
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
452
681
|
}
|
|
453
682
|
deliver(conversation, turn, event, body) {
|
|
@@ -462,7 +691,7 @@ export class Conversations {
|
|
|
462
691
|
return delivered;
|
|
463
692
|
}
|
|
464
693
|
write(conversation, turn, operation) {
|
|
465
|
-
if (
|
|
694
|
+
if (turn.ended || !this.watched)
|
|
466
695
|
return;
|
|
467
696
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
468
697
|
}
|
|
@@ -473,12 +702,7 @@ export class Conversations {
|
|
|
473
702
|
begin(conversation) {
|
|
474
703
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
475
704
|
conversation.open++;
|
|
476
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
477
|
-
.then(() => {
|
|
478
|
-
turn.admitted = true;
|
|
479
|
-
this.replay(conversation, turn);
|
|
480
|
-
})
|
|
481
|
-
.catch(logged);
|
|
705
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
482
706
|
conversation.turn = turn;
|
|
483
707
|
if (conversation.context !== null)
|
|
484
708
|
turn.parts.measure(conversation.context);
|
|
@@ -487,45 +711,63 @@ export class Conversations {
|
|
|
487
711
|
this.kit.changed();
|
|
488
712
|
return turn;
|
|
489
713
|
}
|
|
490
|
-
end(conversation, turn, failed) {
|
|
714
|
+
end(conversation, turn, failed, notice = null) {
|
|
491
715
|
if (turn.ended)
|
|
492
716
|
return;
|
|
717
|
+
if (failed !== null)
|
|
718
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
493
719
|
if (conversation.turn === turn) {
|
|
494
720
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
495
721
|
this.apply(conversation, turn, event);
|
|
496
722
|
}
|
|
723
|
+
if (notice !== null)
|
|
724
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
497
725
|
turn.ended = true;
|
|
498
726
|
conversation.open--;
|
|
499
727
|
if (conversation.turn === turn)
|
|
500
728
|
conversation.turn = null;
|
|
501
729
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
502
|
-
parts: turn.parts.
|
|
730
|
+
parts: turn.parts.saved(),
|
|
503
731
|
context: turn.parts.context,
|
|
504
|
-
...failed,
|
|
732
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
505
733
|
}));
|
|
506
734
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
507
735
|
this.next(conversation);
|
|
508
736
|
this.kit.changed();
|
|
509
737
|
}
|
|
510
|
-
prompt(conversation, prompt) {
|
|
738
|
+
prompt(conversation, prompt, taken) {
|
|
511
739
|
const running = conversation.running;
|
|
512
740
|
const turn = this.begin(conversation);
|
|
513
741
|
turn.prompted = true;
|
|
514
|
-
|
|
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) => {
|
|
515
754
|
if (running.adapter.connection.signal.aborted) {
|
|
516
|
-
this.end(conversation, turn,
|
|
755
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
517
756
|
}
|
|
518
757
|
else {
|
|
758
|
+
this.took(turn);
|
|
519
759
|
if (conversation.open === 1 && conversation.running === running) {
|
|
520
|
-
running.
|
|
760
|
+
running.adapter.retire();
|
|
521
761
|
this.detach(conversation, running);
|
|
522
762
|
}
|
|
523
|
-
this.end(conversation, turn,
|
|
763
|
+
this.end(conversation, turn, answeredWith(error));
|
|
524
764
|
}
|
|
525
765
|
});
|
|
526
766
|
}
|
|
527
767
|
started(conversation) {
|
|
528
|
-
|
|
768
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
769
|
+
this.took(turn);
|
|
770
|
+
return turn;
|
|
529
771
|
}
|
|
530
772
|
ended(conversation) {
|
|
531
773
|
const turn = conversation.turn;
|
|
@@ -620,6 +862,11 @@ export class Conversations {
|
|
|
620
862
|
}
|
|
621
863
|
this.apply(conversation, this.started(conversation), event);
|
|
622
864
|
}
|
|
865
|
+
took(turn) {
|
|
866
|
+
const taken = turn.taken;
|
|
867
|
+
turn.taken = null;
|
|
868
|
+
taken?.();
|
|
869
|
+
}
|
|
623
870
|
apply(conversation, turn, event) {
|
|
624
871
|
switch (event.kind) {
|
|
625
872
|
case 'text':
|
|
@@ -644,7 +891,7 @@ export class Conversations {
|
|
|
644
891
|
turn.parts.plan(event.entries);
|
|
645
892
|
return;
|
|
646
893
|
case 'image':
|
|
647
|
-
turn.uploads.push(this.image(conversation, turn, event.data, event.mediaType));
|
|
894
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
648
895
|
this.kit.changed();
|
|
649
896
|
return;
|
|
650
897
|
}
|
|
@@ -664,23 +911,30 @@ export class Conversations {
|
|
|
664
911
|
else
|
|
665
912
|
conversation.turn.parts.start(part);
|
|
666
913
|
}
|
|
667
|
-
async image(conversation, turn, data, mediaType) {
|
|
914
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
668
915
|
const bytes = Buffer.from(data, 'base64');
|
|
669
916
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
917
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
670
918
|
try {
|
|
671
919
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
672
920
|
name,
|
|
673
921
|
media_type: mediaType,
|
|
674
922
|
bytes: bytes.length,
|
|
675
|
-
sha256
|
|
923
|
+
sha256,
|
|
676
924
|
}));
|
|
677
|
-
const saved =
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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);
|
|
681
934
|
}
|
|
682
935
|
catch (error) {
|
|
683
|
-
|
|
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);
|
|
684
938
|
}
|
|
685
939
|
}
|
|
686
940
|
}
|