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