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