@agentshouse/kit 0.2.1-c2997cbe74597 → 0.2.1-c2eef9f27f3ab
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 +28 -28
- package/dist/acp.js +38 -17
- package/dist/agents.js +52 -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 +1 -1
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +45 -13
- package/dist/{stream.js → connection.js} +33 -30
- package/dist/conversations.js +394 -153
- 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 +27 -13
- package/dist/house-cli.js +96 -88
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +28 -7
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +15 -8
- package/dist/logout.js +0 -2
- package/dist/mcp.js +4 -2
- package/dist/placement.js +9 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +88 -37
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +22 -39
- package/dist/skills.js +12 -93
- package/dist/transcription.js +83 -43
- package/dist/translate.js +2 -8
- package/dist/update.js +71 -48
- package/dist/web-wizard.js +101 -0
- package/dist/wire.js +19 -3
- package/dist/work.js +3 -5
- package/package.json +16 -17
- package/dist/bootstrap.json +0 -24
- 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,22 +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
|
-
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
|
+
}
|
|
20
60
|
const EXIT_PLAN_MODE = {
|
|
21
61
|
parse(value) {
|
|
22
62
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -25,26 +65,51 @@ const EXIT_PLAN_MODE = {
|
|
|
25
65
|
return value;
|
|
26
66
|
},
|
|
27
67
|
};
|
|
28
|
-
export const
|
|
68
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
29
69
|
function causeOf(error) {
|
|
30
70
|
return error instanceof Error ? error.message : String(error);
|
|
31
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
|
+
}
|
|
32
78
|
function answerMessage(request, response) {
|
|
33
79
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
34
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
|
+
}
|
|
35
87
|
function logged(error) {
|
|
36
88
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
37
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
|
+
}
|
|
38
103
|
function marker(bridge) {
|
|
39
104
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
40
105
|
}
|
|
41
106
|
class Turn {
|
|
42
107
|
id = randomUUID();
|
|
43
108
|
parts;
|
|
44
|
-
admitted = false;
|
|
45
109
|
questions = 0;
|
|
46
110
|
secrets = 0;
|
|
47
111
|
prompted = false;
|
|
112
|
+
taken = null;
|
|
48
113
|
ended = false;
|
|
49
114
|
images = 0;
|
|
50
115
|
uploads = [];
|
|
@@ -58,6 +123,8 @@ class Conversation {
|
|
|
58
123
|
session = null;
|
|
59
124
|
turn = null;
|
|
60
125
|
commands = null;
|
|
126
|
+
options = null;
|
|
127
|
+
exitUntold = false;
|
|
61
128
|
context = null;
|
|
62
129
|
leftovers = new Set();
|
|
63
130
|
waiting = [];
|
|
@@ -65,6 +132,9 @@ class Conversation {
|
|
|
65
132
|
subagents = new Map();
|
|
66
133
|
held = [];
|
|
67
134
|
opening = null;
|
|
135
|
+
bridge = null;
|
|
136
|
+
launch = null;
|
|
137
|
+
prompted = null;
|
|
68
138
|
kills = 0;
|
|
69
139
|
killed = new AbortController();
|
|
70
140
|
interrupts = 0;
|
|
@@ -72,6 +142,11 @@ class Conversation {
|
|
|
72
142
|
open = 0;
|
|
73
143
|
queue = Promise.resolve();
|
|
74
144
|
reports = Promise.resolve();
|
|
145
|
+
starting = null;
|
|
146
|
+
preparing = null;
|
|
147
|
+
told = null;
|
|
148
|
+
untold = false;
|
|
149
|
+
shown = null;
|
|
75
150
|
constructor(id) {
|
|
76
151
|
this.id = id;
|
|
77
152
|
}
|
|
@@ -87,9 +162,7 @@ function asksSecret(request) {
|
|
|
87
162
|
export class Conversations {
|
|
88
163
|
kit;
|
|
89
164
|
conversations = new Map();
|
|
90
|
-
acks = new Map();
|
|
91
165
|
questions = new Map();
|
|
92
|
-
carrying = 0;
|
|
93
166
|
reporting = 0;
|
|
94
167
|
watched = false;
|
|
95
168
|
constructor(kit) {
|
|
@@ -97,38 +170,27 @@ export class Conversations {
|
|
|
97
170
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
98
171
|
setInterval(() => this.look(), 2_000);
|
|
99
172
|
}
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
conversation.queue = handled.catch(() => undefined);
|
|
118
|
-
const work = handled.then(({ ack }) => ack);
|
|
119
|
-
this.acks.set(input.input_id, work);
|
|
120
|
-
return work;
|
|
121
|
-
})();
|
|
122
|
-
carried
|
|
123
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
124
|
-
.catch(logged)
|
|
125
|
-
.finally(() => {
|
|
126
|
-
this.carrying--;
|
|
127
|
-
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 }));
|
|
128
190
|
});
|
|
129
191
|
}
|
|
130
192
|
idle() {
|
|
131
|
-
if (this.
|
|
193
|
+
if (this.reporting > 0)
|
|
132
194
|
return false;
|
|
133
195
|
for (const conversation of this.conversations.values()) {
|
|
134
196
|
const turn = conversation.turn;
|
|
@@ -157,6 +219,14 @@ export class Conversations {
|
|
|
157
219
|
}
|
|
158
220
|
opened() {
|
|
159
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
|
+
}
|
|
160
230
|
}
|
|
161
231
|
watching(watched) {
|
|
162
232
|
const resend = watched && !this.watched;
|
|
@@ -176,63 +246,99 @@ export class Conversations {
|
|
|
176
246
|
}
|
|
177
247
|
return conversation;
|
|
178
248
|
}
|
|
179
|
-
async carry(conversation,
|
|
180
|
-
if (
|
|
181
|
-
return {
|
|
182
|
-
if (
|
|
183
|
-
return this.message(conversation,
|
|
184
|
-
|
|
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') {
|
|
185
256
|
if (--conversation.interrupts === 0)
|
|
186
257
|
conversation.interrupted = new AbortController();
|
|
187
|
-
await this.interrupt(conversation, String(
|
|
258
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
188
259
|
}
|
|
189
|
-
if (
|
|
260
|
+
if (call.kind === 'kill') {
|
|
190
261
|
await this.kill(conversation);
|
|
191
262
|
await conversation.sending;
|
|
192
263
|
if (--conversation.kills === 0)
|
|
193
264
|
conversation.killed = new AbortController();
|
|
194
265
|
}
|
|
195
|
-
if (
|
|
196
|
-
await this.option(conversation, String(
|
|
197
|
-
if (
|
|
198
|
-
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));
|
|
199
270
|
if (question === undefined) {
|
|
200
|
-
const text = answerMessage(
|
|
201
|
-
return this.message(conversation, { ...
|
|
271
|
+
const text = answerMessage(call.request, call.response);
|
|
272
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
202
273
|
}
|
|
203
|
-
question.answer(
|
|
274
|
+
question.answer(call.response);
|
|
204
275
|
}
|
|
205
|
-
return {
|
|
276
|
+
return { result: Promise.resolve({}) };
|
|
206
277
|
}
|
|
207
|
-
|
|
278
|
+
open(conversation, call) {
|
|
208
279
|
if (conversation.running !== null)
|
|
209
|
-
return {};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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) }];
|
|
219
322
|
let route;
|
|
323
|
+
let instructing = null;
|
|
220
324
|
try {
|
|
221
|
-
route = await this.route(conversation,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
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))));
|
|
227
330
|
if (conversation.kills > 0)
|
|
228
331
|
throw new Error(KILLED);
|
|
229
332
|
}
|
|
230
333
|
catch (error) {
|
|
231
|
-
|
|
334
|
+
this.closeIdleBridge(conversation);
|
|
335
|
+
this.prepares(conversation, null);
|
|
336
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
232
337
|
}
|
|
338
|
+
conversation.preparing = null;
|
|
233
339
|
return {
|
|
234
|
-
|
|
235
|
-
conversation.waiting.push({
|
|
340
|
+
result: new Promise((settle) => {
|
|
341
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
236
342
|
this.next(conversation);
|
|
237
343
|
}),
|
|
238
344
|
};
|
|
@@ -241,62 +347,148 @@ export class Conversations {
|
|
|
241
347
|
if (conversation.sending !== null || conversation.open > 0)
|
|
242
348
|
return;
|
|
243
349
|
const waiting = conversation.waiting.shift();
|
|
244
|
-
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);
|
|
245
354
|
return;
|
|
355
|
+
}
|
|
246
356
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
247
357
|
conversation.sending = null;
|
|
248
358
|
this.next(conversation);
|
|
249
359
|
});
|
|
250
360
|
}
|
|
251
361
|
async send(conversation, waiting) {
|
|
252
|
-
const {
|
|
253
|
-
let
|
|
362
|
+
const { call } = waiting;
|
|
363
|
+
let result = {};
|
|
364
|
+
let started = false;
|
|
365
|
+
let instructing = waiting.instructing;
|
|
254
366
|
const prompt = [];
|
|
255
367
|
try {
|
|
256
368
|
if (conversation.running === null) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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;
|
|
261
379
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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)) });
|
|
267
386
|
}
|
|
268
387
|
if (conversation.kills > 0)
|
|
269
388
|
throw new Error(KILLED);
|
|
270
389
|
}
|
|
271
390
|
catch (error) {
|
|
272
|
-
|
|
391
|
+
this.starts(conversation, null);
|
|
392
|
+
if (started) {
|
|
273
393
|
conversation.session = null;
|
|
274
394
|
await this.kill(conversation);
|
|
275
395
|
}
|
|
276
|
-
|
|
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);
|
|
277
412
|
return;
|
|
278
413
|
}
|
|
279
|
-
|
|
280
|
-
|
|
414
|
+
conversation.shown = setTimeout(() => {
|
|
415
|
+
conversation.shown = null;
|
|
416
|
+
if (conversation.starting === step)
|
|
417
|
+
this.tell(conversation, step);
|
|
418
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
281
419
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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}` });
|
|
297
461
|
}
|
|
298
462
|
}
|
|
299
|
-
|
|
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);
|
|
300
492
|
}
|
|
301
493
|
async interrupt(conversation, turnId) {
|
|
302
494
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -351,7 +543,8 @@ export class Conversations {
|
|
|
351
543
|
}
|
|
352
544
|
return route;
|
|
353
545
|
}
|
|
354
|
-
async start(conversation,
|
|
546
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
547
|
+
const route = await routed;
|
|
355
548
|
const reader = readerFor(route.kind);
|
|
356
549
|
reader.session = sessionId;
|
|
357
550
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -359,56 +552,68 @@ export class Conversations {
|
|
|
359
552
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
360
553
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
361
554
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
362
|
-
const
|
|
555
|
+
const bridging = this.bridged(conversation);
|
|
556
|
+
const bridge = await step(unbridged, bridging);
|
|
363
557
|
if (conversation.kills > 0) {
|
|
364
|
-
|
|
558
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
365
559
|
throw new Error(KILLED);
|
|
366
560
|
}
|
|
367
561
|
conversation.opening = bridge;
|
|
368
562
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
369
563
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
564
|
+
let adapter = null;
|
|
370
565
|
try {
|
|
371
|
-
|
|
566
|
+
bridged(bridge);
|
|
372
567
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
373
|
-
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) => {
|
|
374
570
|
work.read(message);
|
|
375
571
|
this.read(conversation, reader, message);
|
|
376
|
-
}, { ...bridge.env, ...
|
|
377
|
-
void adapter.exited.then(() =>
|
|
572
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
573
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
378
574
|
const agent = adapter.connection.agent;
|
|
379
575
|
const cwd = route.working_directory;
|
|
380
576
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
: { ...(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 })));
|
|
384
579
|
reader.session = opened.sessionId;
|
|
385
|
-
if (sessionId
|
|
580
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
386
581
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
387
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
582
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
388
583
|
}
|
|
389
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
390
|
-
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 ?? []));
|
|
391
586
|
void work.settled().then(() => scope.record());
|
|
392
587
|
conversation.running = running;
|
|
393
588
|
conversation.session = running.sessionId;
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.options(conversation, options);
|
|
398
|
-
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));
|
|
399
592
|
return running.sessionId;
|
|
400
593
|
}
|
|
401
594
|
catch (error) {
|
|
402
|
-
|
|
595
|
+
adapter?.retire();
|
|
403
596
|
work.close();
|
|
404
597
|
conversation.leftovers.add(scope);
|
|
405
|
-
|
|
598
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
406
599
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
407
600
|
}
|
|
408
601
|
finally {
|
|
409
602
|
conversation.opening = null;
|
|
410
603
|
}
|
|
411
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
|
+
}
|
|
412
617
|
async launchSettings(running, route, offered) {
|
|
413
618
|
let options = offered;
|
|
414
619
|
const settings = [
|
|
@@ -430,27 +635,37 @@ export class Conversations {
|
|
|
430
635
|
}
|
|
431
636
|
return options;
|
|
432
637
|
}
|
|
433
|
-
exited(conversation,
|
|
434
|
-
const running = conversation.running;
|
|
435
|
-
if (running?.adapter !== adapter)
|
|
436
|
-
return;
|
|
638
|
+
exited(conversation, running, cause) {
|
|
437
639
|
this.detach(conversation, running);
|
|
438
640
|
if (conversation.turn !== null) {
|
|
439
|
-
this.end(conversation, conversation.turn,
|
|
641
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
440
642
|
}
|
|
441
643
|
this.next(conversation);
|
|
442
644
|
this.kit.changed();
|
|
443
645
|
}
|
|
444
646
|
detach(conversation, running) {
|
|
647
|
+
if (conversation.bridge === running.bridging)
|
|
648
|
+
conversation.bridge = null;
|
|
445
649
|
running.work.close();
|
|
446
650
|
conversation.leftovers.add(running.scope);
|
|
447
651
|
conversation.running = null;
|
|
448
652
|
conversation.commands = null;
|
|
653
|
+
conversation.options = null;
|
|
449
654
|
for (const [id, question] of this.questions) {
|
|
450
655
|
if (question.conversation === conversation)
|
|
451
656
|
this.questions.delete(id);
|
|
452
657
|
}
|
|
453
|
-
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);
|
|
454
669
|
}
|
|
455
670
|
commands(conversation, commands) {
|
|
456
671
|
conversation.commands = commands;
|
|
@@ -461,6 +676,7 @@ export class Conversations {
|
|
|
461
676
|
options(conversation, options) {
|
|
462
677
|
if (conversation.running === null)
|
|
463
678
|
return;
|
|
679
|
+
conversation.options = options;
|
|
464
680
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
465
681
|
}
|
|
466
682
|
deliver(conversation, turn, event, body) {
|
|
@@ -475,7 +691,7 @@ export class Conversations {
|
|
|
475
691
|
return delivered;
|
|
476
692
|
}
|
|
477
693
|
write(conversation, turn, operation) {
|
|
478
|
-
if (
|
|
694
|
+
if (turn.ended || !this.watched)
|
|
479
695
|
return;
|
|
480
696
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
481
697
|
}
|
|
@@ -486,12 +702,7 @@ export class Conversations {
|
|
|
486
702
|
begin(conversation) {
|
|
487
703
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
488
704
|
conversation.open++;
|
|
489
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
490
|
-
.then(() => {
|
|
491
|
-
turn.admitted = true;
|
|
492
|
-
this.replay(conversation, turn);
|
|
493
|
-
})
|
|
494
|
-
.catch(logged);
|
|
705
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
495
706
|
conversation.turn = turn;
|
|
496
707
|
if (conversation.context !== null)
|
|
497
708
|
turn.parts.measure(conversation.context);
|
|
@@ -500,13 +711,17 @@ export class Conversations {
|
|
|
500
711
|
this.kit.changed();
|
|
501
712
|
return turn;
|
|
502
713
|
}
|
|
503
|
-
end(conversation, turn, failed) {
|
|
714
|
+
end(conversation, turn, failed, notice = null) {
|
|
504
715
|
if (turn.ended)
|
|
505
716
|
return;
|
|
717
|
+
if (failed !== null)
|
|
718
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
506
719
|
if (conversation.turn === turn) {
|
|
507
720
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
508
721
|
this.apply(conversation, turn, event);
|
|
509
722
|
}
|
|
723
|
+
if (notice !== null)
|
|
724
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
510
725
|
turn.ended = true;
|
|
511
726
|
conversation.open--;
|
|
512
727
|
if (conversation.turn === turn)
|
|
@@ -514,31 +729,45 @@ export class Conversations {
|
|
|
514
729
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
515
730
|
parts: turn.parts.saved(),
|
|
516
731
|
context: turn.parts.context,
|
|
517
|
-
...failed,
|
|
732
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
518
733
|
}));
|
|
519
734
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
520
735
|
this.next(conversation);
|
|
521
736
|
this.kit.changed();
|
|
522
737
|
}
|
|
523
|
-
prompt(conversation, prompt) {
|
|
738
|
+
prompt(conversation, prompt, taken) {
|
|
524
739
|
const running = conversation.running;
|
|
525
740
|
const turn = this.begin(conversation);
|
|
526
741
|
turn.prompted = true;
|
|
527
|
-
|
|
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) => {
|
|
528
754
|
if (running.adapter.connection.signal.aborted) {
|
|
529
|
-
this.end(conversation, turn,
|
|
755
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
530
756
|
}
|
|
531
757
|
else {
|
|
758
|
+
this.took(turn);
|
|
532
759
|
if (conversation.open === 1 && conversation.running === running) {
|
|
533
|
-
running.
|
|
760
|
+
running.adapter.retire();
|
|
534
761
|
this.detach(conversation, running);
|
|
535
762
|
}
|
|
536
|
-
this.end(conversation, turn,
|
|
763
|
+
this.end(conversation, turn, answeredWith(error));
|
|
537
764
|
}
|
|
538
765
|
});
|
|
539
766
|
}
|
|
540
767
|
started(conversation) {
|
|
541
|
-
|
|
768
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
769
|
+
this.took(turn);
|
|
770
|
+
return turn;
|
|
542
771
|
}
|
|
543
772
|
ended(conversation) {
|
|
544
773
|
const turn = conversation.turn;
|
|
@@ -633,6 +862,11 @@ export class Conversations {
|
|
|
633
862
|
}
|
|
634
863
|
this.apply(conversation, this.started(conversation), event);
|
|
635
864
|
}
|
|
865
|
+
took(turn) {
|
|
866
|
+
const taken = turn.taken;
|
|
867
|
+
turn.taken = null;
|
|
868
|
+
taken?.();
|
|
869
|
+
}
|
|
636
870
|
apply(conversation, turn, event) {
|
|
637
871
|
switch (event.kind) {
|
|
638
872
|
case 'text':
|
|
@@ -680,20 +914,27 @@ export class Conversations {
|
|
|
680
914
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
681
915
|
const bytes = Buffer.from(data, 'base64');
|
|
682
916
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
917
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
683
918
|
try {
|
|
684
919
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
685
920
|
name,
|
|
686
921
|
media_type: mediaType,
|
|
687
922
|
bytes: bytes.length,
|
|
688
|
-
sha256
|
|
923
|
+
sha256,
|
|
689
924
|
}));
|
|
690
|
-
const saved =
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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);
|
|
694
934
|
}
|
|
695
935
|
catch (error) {
|
|
696
|
-
|
|
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);
|
|
697
938
|
}
|
|
698
939
|
}
|
|
699
940
|
}
|