@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-cf3a50c90b895
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 +40 -39
- package/dist/acp.js +48 -105
- package/dist/agents.js +153 -43
- package/dist/api.js +34 -2
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +36 -57
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +127 -70
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +125 -0
- package/dist/conversations.js +540 -305
- package/dist/copies.js +188 -128
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +22 -2
- package/dist/git.js +11 -9
- package/dist/home.js +96 -7
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +118 -103
- package/dist/logout.js +14 -13
- package/dist/mcp.js +2 -2
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +106 -50
- package/dist/scope.js +206 -0
- package/dist/secret-input.js +3 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +37 -31
- package/dist/skills.js +84 -15
- package/dist/transcription.js +57 -9
- package/dist/translate.js +2 -8
- package/dist/update.js +71 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/dist/blocks.js +0 -51
- package/dist/stream.js +0 -47
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/conversations.js
CHANGED
|
@@ -1,56 +1,124 @@
|
|
|
1
|
-
import { client, } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
1
|
+
import { client, RequestError, } from '@agentclientprotocol/sdk';
|
|
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
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
10
|
-
import {
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
+
import { placeFiles, uploadBytes } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
14
|
+
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { killMarked, Scope } from './scope.js';
|
|
13
17
|
import { holdSecretInput } from './secret-input.js';
|
|
14
18
|
import { createHowWeWork } from './skills.js';
|
|
15
19
|
import { transcribe } from './transcription.js';
|
|
20
|
+
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
16
22
|
const KILLED = 'the conversation was killed';
|
|
23
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
24
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
26
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
27
|
+
const ENDED = {
|
|
28
|
+
conversation_not_found: GONE,
|
|
29
|
+
conversation_historical: RETIRED,
|
|
30
|
+
};
|
|
31
|
+
const NO_START = "the Agent's CLI did not start";
|
|
32
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
35
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
class NotStarted extends Error {
|
|
37
|
+
}
|
|
38
|
+
async function step(reason, work) {
|
|
39
|
+
try {
|
|
40
|
+
return await work;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function signedOut(error) {
|
|
47
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
54
|
+
function unbridged(error) {
|
|
55
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
56
|
+
}
|
|
57
|
+
const EXIT_PLAN_MODE = {
|
|
58
|
+
parse(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
60
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
17
66
|
function causeOf(error) {
|
|
18
67
|
return error instanceof Error ? error.message : String(error);
|
|
19
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
74
|
+
}
|
|
20
75
|
function answerMessage(request, response) {
|
|
21
76
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
22
77
|
}
|
|
23
78
|
function logged(error) {
|
|
24
79
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
25
80
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
81
|
+
function refusal(conversation, error) {
|
|
82
|
+
if (conversation.kills > 0)
|
|
83
|
+
return KILLED;
|
|
84
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
85
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
86
|
+
return causeOf(error);
|
|
87
|
+
}
|
|
88
|
+
function marker(bridge) {
|
|
89
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
28
90
|
}
|
|
29
91
|
class Turn {
|
|
30
92
|
id = randomUUID();
|
|
31
|
-
|
|
32
|
-
segment = null;
|
|
33
|
-
admitted = false;
|
|
34
|
-
notes = [];
|
|
35
|
-
sent = [];
|
|
36
|
-
unsentFrom = null;
|
|
37
|
-
draftSequence = 0;
|
|
38
|
-
planSequence = 0;
|
|
39
|
-
entries = null;
|
|
93
|
+
parts;
|
|
40
94
|
questions = 0;
|
|
41
95
|
secrets = 0;
|
|
42
|
-
|
|
96
|
+
prompted = false;
|
|
97
|
+
taken = null;
|
|
43
98
|
ended = false;
|
|
99
|
+
images = 0;
|
|
100
|
+
uploads = [];
|
|
101
|
+
constructor(emit) {
|
|
102
|
+
this.parts = new Parts(emit);
|
|
103
|
+
}
|
|
44
104
|
}
|
|
45
105
|
class Conversation {
|
|
46
106
|
id;
|
|
47
107
|
running = null;
|
|
108
|
+
session = null;
|
|
48
109
|
turn = null;
|
|
49
110
|
commands = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
111
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
113
|
+
context = null;
|
|
114
|
+
leftovers = new Set();
|
|
115
|
+
waiting = [];
|
|
116
|
+
sending = null;
|
|
117
|
+
subagents = new Map();
|
|
118
|
+
held = [];
|
|
53
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
121
|
+
prompted = null;
|
|
54
122
|
kills = 0;
|
|
55
123
|
killed = new AbortController();
|
|
56
124
|
interrupts = 0;
|
|
@@ -58,6 +126,11 @@ class Conversation {
|
|
|
58
126
|
open = 0;
|
|
59
127
|
queue = Promise.resolve();
|
|
60
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
61
134
|
constructor(id) {
|
|
62
135
|
this.id = id;
|
|
63
136
|
}
|
|
@@ -73,55 +146,80 @@ function asksSecret(request) {
|
|
|
73
146
|
export class Conversations {
|
|
74
147
|
kit;
|
|
75
148
|
conversations = new Map();
|
|
76
|
-
acks = new Map();
|
|
77
149
|
questions = new Map();
|
|
78
|
-
carrying = 0;
|
|
79
150
|
reporting = 0;
|
|
151
|
+
watched = false;
|
|
80
152
|
constructor(kit) {
|
|
81
153
|
this.kit = kit;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
})();
|
|
102
|
-
carried
|
|
103
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
104
|
-
.catch(logged)
|
|
105
|
-
.finally(() => {
|
|
106
|
-
this.carrying--;
|
|
107
|
-
this.kit.changed();
|
|
154
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
155
|
+
setInterval(() => this.look(), 2_000);
|
|
156
|
+
}
|
|
157
|
+
call(call) {
|
|
158
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
159
|
+
const conversation = this.conversation(call.conversation_id);
|
|
160
|
+
if (call.kind === 'kill') {
|
|
161
|
+
conversation.kills++;
|
|
162
|
+
conversation.killed.abort(new Error(KILLED));
|
|
163
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
164
|
+
waiting.settle({ refused: KILLED });
|
|
165
|
+
this.halt(conversation);
|
|
166
|
+
}
|
|
167
|
+
if (call.kind === 'interrupt') {
|
|
168
|
+
conversation.interrupts++;
|
|
169
|
+
conversation.interrupted.abort();
|
|
170
|
+
}
|
|
171
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
172
|
+
conversation.queue = handled.catch(() => undefined);
|
|
173
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
108
174
|
});
|
|
109
175
|
}
|
|
110
176
|
idle() {
|
|
111
|
-
if (this.
|
|
177
|
+
if (this.reporting > 0)
|
|
112
178
|
return false;
|
|
113
179
|
for (const conversation of this.conversations.values()) {
|
|
114
180
|
const turn = conversation.turn;
|
|
115
181
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
182
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
117
183
|
return false;
|
|
118
184
|
}
|
|
119
|
-
|
|
185
|
+
const conversations = [...this.conversations.values()];
|
|
186
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
187
|
+
return false;
|
|
188
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
189
|
+
}
|
|
190
|
+
quiet() {
|
|
191
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
192
|
+
}
|
|
193
|
+
look() {
|
|
194
|
+
for (const conversation of this.conversations.values()) {
|
|
195
|
+
for (const scope of conversation.leftovers) {
|
|
196
|
+
if (!scope.empty())
|
|
197
|
+
continue;
|
|
198
|
+
scope.close();
|
|
199
|
+
conversation.leftovers.delete(scope);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.kit.changed();
|
|
120
203
|
}
|
|
121
204
|
opened() {
|
|
205
|
+
this.watched = false;
|
|
206
|
+
for (const conversation of this.conversations.values()) {
|
|
207
|
+
if (conversation.running !== null)
|
|
208
|
+
this.facts(conversation);
|
|
209
|
+
else if (conversation.exitUntold)
|
|
210
|
+
this.process(conversation, false);
|
|
211
|
+
if (conversation.untold || conversation.told !== null)
|
|
212
|
+
this.retell(conversation);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
watching(watched) {
|
|
216
|
+
const resend = watched && !this.watched;
|
|
217
|
+
this.watched = watched;
|
|
218
|
+
if (!resend)
|
|
219
|
+
return;
|
|
122
220
|
for (const conversation of this.conversations.values()) {
|
|
123
221
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
222
|
+
this.replay(conversation, conversation.turn);
|
|
125
223
|
}
|
|
126
224
|
}
|
|
127
225
|
conversation(id) {
|
|
@@ -132,74 +230,190 @@ export class Conversations {
|
|
|
132
230
|
}
|
|
133
231
|
return conversation;
|
|
134
232
|
}
|
|
135
|
-
async carry(conversation,
|
|
136
|
-
if (
|
|
137
|
-
return this.open(conversation,
|
|
138
|
-
if (
|
|
139
|
-
return this.message(conversation,
|
|
140
|
-
|
|
233
|
+
async carry(conversation, call) {
|
|
234
|
+
if (call.kind === 'open')
|
|
235
|
+
return { result: this.open(conversation, call) };
|
|
236
|
+
if (call.kind === 'message')
|
|
237
|
+
return this.message(conversation, call);
|
|
238
|
+
await this.launched(conversation);
|
|
239
|
+
if (call.kind === 'interrupt') {
|
|
141
240
|
if (--conversation.interrupts === 0)
|
|
142
241
|
conversation.interrupted = new AbortController();
|
|
143
|
-
await this.interrupt(conversation, String(
|
|
242
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
144
243
|
}
|
|
145
|
-
if (
|
|
244
|
+
if (call.kind === 'kill') {
|
|
146
245
|
await this.kill(conversation);
|
|
246
|
+
await conversation.sending;
|
|
147
247
|
if (--conversation.kills === 0)
|
|
148
248
|
conversation.killed = new AbortController();
|
|
149
249
|
}
|
|
150
|
-
if (
|
|
151
|
-
await this.option(conversation, String(
|
|
152
|
-
if (
|
|
153
|
-
const question = this.questions.get(String(
|
|
250
|
+
if (call.kind === 'option')
|
|
251
|
+
await this.option(conversation, String(call.option), call.value);
|
|
252
|
+
if (call.kind === 'answer') {
|
|
253
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
154
254
|
if (question === undefined) {
|
|
155
|
-
|
|
255
|
+
const text = answerMessage(call.request, call.response);
|
|
256
|
+
return this.message(conversation, { ...call, text, files: [], first: false });
|
|
156
257
|
}
|
|
157
|
-
question.answer(
|
|
258
|
+
question.answer(call.response);
|
|
158
259
|
}
|
|
159
|
-
return {};
|
|
260
|
+
return { result: Promise.resolve({}) };
|
|
160
261
|
}
|
|
161
|
-
|
|
262
|
+
open(conversation, call) {
|
|
162
263
|
if (conversation.running !== null)
|
|
163
|
-
return {};
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
266
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
267
|
+
}
|
|
268
|
+
launch(conversation, route, sessionId, empty) {
|
|
269
|
+
let opened;
|
|
270
|
+
let failed;
|
|
271
|
+
const bridge = new Promise((resolve, reject) => {
|
|
272
|
+
opened = resolve;
|
|
273
|
+
failed = reject;
|
|
274
|
+
});
|
|
275
|
+
bridge.catch(() => undefined);
|
|
276
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
277
|
+
session.catch(failed);
|
|
278
|
+
const launch = { bridge, session };
|
|
279
|
+
conversation.launch = launch;
|
|
280
|
+
void session
|
|
281
|
+
.catch(() => undefined)
|
|
282
|
+
.then(() => {
|
|
283
|
+
if (conversation.launch !== launch)
|
|
284
|
+
return;
|
|
285
|
+
conversation.launch = null;
|
|
286
|
+
this.next(conversation);
|
|
287
|
+
});
|
|
288
|
+
this.next(conversation);
|
|
289
|
+
return launch;
|
|
290
|
+
}
|
|
291
|
+
async launched(conversation) {
|
|
292
|
+
while (conversation.launch !== null)
|
|
293
|
+
await conversation.launch.session.catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
instructed(conversation, route) {
|
|
296
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
297
|
+
if (bridge === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
300
|
+
read.catch(() => undefined);
|
|
301
|
+
return read;
|
|
302
|
+
}
|
|
303
|
+
async message(conversation, call) {
|
|
304
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
305
|
+
let route;
|
|
306
|
+
let instructing = null;
|
|
164
307
|
try {
|
|
165
|
-
|
|
308
|
+
route = await this.route(conversation, call.agent_id);
|
|
309
|
+
if (call.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
311
|
+
const files = call.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
314
|
+
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
315
|
+
const lines = await this.transcribed(conversation, files, paths);
|
|
316
|
+
if (lines.length > 0)
|
|
317
|
+
content.push({ type: 'text', text: lines.join('\n') });
|
|
318
|
+
if (conversation.kills > 0)
|
|
319
|
+
throw new Error(KILLED);
|
|
166
320
|
}
|
|
167
321
|
catch (error) {
|
|
168
|
-
|
|
322
|
+
this.prepares(conversation, null);
|
|
323
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
324
|
+
}
|
|
325
|
+
conversation.preparing = null;
|
|
326
|
+
return {
|
|
327
|
+
result: new Promise((settle) => {
|
|
328
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
329
|
+
this.next(conversation);
|
|
330
|
+
}),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
next(conversation) {
|
|
334
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
335
|
+
return;
|
|
336
|
+
const waiting = conversation.waiting.shift();
|
|
337
|
+
if (waiting === undefined) {
|
|
338
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
|
+
if (conversation.starting !== step)
|
|
340
|
+
this.starts(conversation, step);
|
|
341
|
+
return;
|
|
169
342
|
}
|
|
343
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
344
|
+
conversation.sending = null;
|
|
345
|
+
this.next(conversation);
|
|
346
|
+
});
|
|
170
347
|
}
|
|
171
|
-
async
|
|
172
|
-
|
|
348
|
+
async send(conversation, waiting) {
|
|
349
|
+
const { call } = waiting;
|
|
350
|
+
let result = {};
|
|
351
|
+
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
173
353
|
const prompt = [];
|
|
174
354
|
try {
|
|
175
|
-
const route = await this.route(conversation, input.agent_id);
|
|
176
|
-
const files = input.files;
|
|
177
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
|
-
const lines = await this.transcribed(conversation, files, paths);
|
|
179
355
|
if (conversation.running === null) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
359
|
+
if (conversation.running === null) {
|
|
360
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
361
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (call.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
183
366
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
367
|
+
const session = conversation.running.sessionId;
|
|
368
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
369
|
+
result = { provider_session_id: session };
|
|
370
|
+
}
|
|
371
|
+
if (call.first === true) {
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
189
373
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
374
|
if (conversation.kills > 0)
|
|
194
375
|
throw new Error(KILLED);
|
|
195
376
|
}
|
|
196
377
|
catch (error) {
|
|
197
|
-
|
|
378
|
+
this.starts(conversation, null);
|
|
379
|
+
if (started) {
|
|
380
|
+
conversation.session = null;
|
|
198
381
|
await this.kill(conversation);
|
|
199
|
-
|
|
382
|
+
}
|
|
383
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
388
|
+
waiting.settle(result);
|
|
389
|
+
}
|
|
390
|
+
starts(conversation, step) {
|
|
391
|
+
conversation.starting = step;
|
|
392
|
+
if (conversation.shown !== null)
|
|
393
|
+
clearTimeout(conversation.shown);
|
|
394
|
+
conversation.shown = null;
|
|
395
|
+
if (step === null || conversation.told !== null) {
|
|
396
|
+
this.tell(conversation, step);
|
|
397
|
+
return;
|
|
200
398
|
}
|
|
201
|
-
|
|
202
|
-
|
|
399
|
+
conversation.shown = setTimeout(() => {
|
|
400
|
+
conversation.shown = null;
|
|
401
|
+
if (conversation.starting === step)
|
|
402
|
+
this.tell(conversation, step);
|
|
403
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
404
|
+
}
|
|
405
|
+
prepares(conversation, step) {
|
|
406
|
+
conversation.preparing = step;
|
|
407
|
+
this.next(conversation);
|
|
408
|
+
}
|
|
409
|
+
tell(conversation, step) {
|
|
410
|
+
if (conversation.told === step && !conversation.untold)
|
|
411
|
+
return;
|
|
412
|
+
conversation.told = step;
|
|
413
|
+
this.retell(conversation);
|
|
414
|
+
}
|
|
415
|
+
retell(conversation) {
|
|
416
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
203
417
|
}
|
|
204
418
|
async transcribed(conversation, files, paths) {
|
|
205
419
|
const lines = [];
|
|
@@ -207,9 +421,10 @@ export class Conversations {
|
|
|
207
421
|
lines.push(path);
|
|
208
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
209
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
210
425
|
try {
|
|
211
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
212
|
-
if (text !== '')
|
|
427
|
+
if (text !== null && text !== '')
|
|
213
428
|
lines.push(`Transcript: ${text}`);
|
|
214
429
|
}
|
|
215
430
|
catch (error) {
|
|
@@ -229,11 +444,13 @@ export class Conversations {
|
|
|
229
444
|
}
|
|
230
445
|
halt(conversation) {
|
|
231
446
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
447
|
+
killMarked(marker(conversation.opening));
|
|
448
|
+
for (const scope of conversation.leftovers)
|
|
449
|
+
scope.kill();
|
|
233
450
|
if (conversation.running === null)
|
|
234
451
|
return;
|
|
235
452
|
conversation.running.killed = true;
|
|
236
|
-
|
|
453
|
+
conversation.running.scope.kill();
|
|
237
454
|
}
|
|
238
455
|
async kill(conversation) {
|
|
239
456
|
const running = conversation.running;
|
|
@@ -245,6 +462,7 @@ export class Conversations {
|
|
|
245
462
|
if (running === null)
|
|
246
463
|
return;
|
|
247
464
|
try {
|
|
465
|
+
running.reader.requested(option, value);
|
|
248
466
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
467
|
sessionId: running.sessionId,
|
|
250
468
|
configId: option,
|
|
@@ -253,6 +471,7 @@ export class Conversations {
|
|
|
253
471
|
this.options(conversation, answered.configOptions);
|
|
254
472
|
}
|
|
255
473
|
catch (error) {
|
|
474
|
+
running.reader.refused(option);
|
|
256
475
|
logged(error);
|
|
257
476
|
}
|
|
258
477
|
}
|
|
@@ -269,50 +488,58 @@ export class Conversations {
|
|
|
269
488
|
}
|
|
270
489
|
return route;
|
|
271
490
|
}
|
|
272
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
493
|
+
const reader = readerFor(route.kind);
|
|
494
|
+
reader.session = sessionId;
|
|
273
495
|
const app = client({ name: '@agentshouse/kit' })
|
|
274
|
-
.onNotification('session/update', ({ params }) => this.update(conversation, params))
|
|
275
|
-
.onNotification(TURN_STARTED, (params) => params, () => void this.started(conversation))
|
|
276
|
-
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
277
496
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
278
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
497
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
498
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
279
499
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
280
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
500
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
281
501
|
if (conversation.kills > 0) {
|
|
282
502
|
bridge.close();
|
|
283
503
|
throw new Error(KILLED);
|
|
284
504
|
}
|
|
285
505
|
conversation.opening = bridge;
|
|
506
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
507
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
|
+
let adapter = null;
|
|
286
509
|
try {
|
|
287
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
288
|
-
|
|
289
|
-
|
|
511
|
+
bridged(bridge);
|
|
512
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
513
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
514
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
515
|
+
work.read(message);
|
|
516
|
+
this.read(conversation, reader, message);
|
|
517
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
290
518
|
void adapter.exited.then(() => bridge.close());
|
|
291
519
|
const agent = adapter.connection.agent;
|
|
292
520
|
const cwd = route.working_directory;
|
|
293
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
521
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
522
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
523
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
524
|
+
reader.session = opened.sessionId;
|
|
525
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
526
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
528
|
+
}
|
|
529
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
530
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
531
|
+
void work.settled().then(() => scope.record());
|
|
306
532
|
conversation.running = running;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.
|
|
311
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
533
|
+
conversation.session = running.sessionId;
|
|
534
|
+
conversation.options = options;
|
|
535
|
+
this.facts(conversation);
|
|
536
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
312
537
|
return running.sessionId;
|
|
313
538
|
}
|
|
314
539
|
catch (error) {
|
|
315
|
-
|
|
540
|
+
adapter?.retire();
|
|
541
|
+
work.close();
|
|
542
|
+
conversation.leftovers.add(scope);
|
|
316
543
|
bridge.close();
|
|
317
544
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
318
545
|
}
|
|
@@ -320,6 +547,18 @@ export class Conversations {
|
|
|
320
547
|
conversation.opening = null;
|
|
321
548
|
}
|
|
322
549
|
}
|
|
550
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
551
|
+
try {
|
|
552
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
556
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
557
|
+
}
|
|
558
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
323
562
|
async launchSettings(running, route, offered) {
|
|
324
563
|
let options = offered;
|
|
325
564
|
const settings = [
|
|
@@ -331,6 +570,7 @@ export class Conversations {
|
|
|
331
570
|
if (value === null)
|
|
332
571
|
continue;
|
|
333
572
|
const option = options.find((candidate) => candidate.category === category);
|
|
573
|
+
running.reader.requested(option?.id ?? category, value);
|
|
334
574
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
335
575
|
sessionId: running.sessionId,
|
|
336
576
|
configId: option?.id ?? category,
|
|
@@ -340,23 +580,35 @@ export class Conversations {
|
|
|
340
580
|
}
|
|
341
581
|
return options;
|
|
342
582
|
}
|
|
343
|
-
exited(conversation,
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
583
|
+
exited(conversation, running, cause) {
|
|
584
|
+
this.detach(conversation, running);
|
|
585
|
+
if (conversation.turn !== null) {
|
|
586
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
587
|
+
}
|
|
588
|
+
this.next(conversation);
|
|
589
|
+
this.kit.changed();
|
|
590
|
+
}
|
|
591
|
+
detach(conversation, running) {
|
|
592
|
+
running.work.close();
|
|
593
|
+
conversation.leftovers.add(running.scope);
|
|
347
594
|
conversation.running = null;
|
|
348
595
|
conversation.commands = null;
|
|
349
|
-
conversation.
|
|
350
|
-
conversation.queued.length = 0;
|
|
596
|
+
conversation.options = null;
|
|
351
597
|
for (const [id, question] of this.questions) {
|
|
352
598
|
if (question.conversation === conversation)
|
|
353
599
|
this.questions.delete(id);
|
|
354
600
|
}
|
|
355
|
-
this.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
601
|
+
this.process(conversation, false);
|
|
602
|
+
}
|
|
603
|
+
process(conversation, running) {
|
|
604
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
605
|
+
}
|
|
606
|
+
facts(conversation) {
|
|
607
|
+
this.process(conversation, true);
|
|
608
|
+
if (conversation.commands !== null)
|
|
609
|
+
this.commands(conversation, conversation.commands);
|
|
610
|
+
if (conversation.options !== null)
|
|
611
|
+
this.options(conversation, conversation.options);
|
|
360
612
|
}
|
|
361
613
|
commands(conversation, commands) {
|
|
362
614
|
conversation.commands = commands;
|
|
@@ -367,11 +619,12 @@ export class Conversations {
|
|
|
367
619
|
options(conversation, options) {
|
|
368
620
|
if (conversation.running === null)
|
|
369
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
370
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
371
624
|
}
|
|
372
625
|
deliver(conversation, turn, event, body) {
|
|
373
626
|
this.reporting++;
|
|
374
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
627
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
375
628
|
conversation.reports = delivered
|
|
376
629
|
.catch(() => undefined)
|
|
377
630
|
.finally(() => {
|
|
@@ -380,119 +633,95 @@ export class Conversations {
|
|
|
380
633
|
});
|
|
381
634
|
return delivered;
|
|
382
635
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
.then(() => this.admitted(conversation, turn))
|
|
388
|
-
.catch(logged);
|
|
389
|
-
return turn;
|
|
636
|
+
write(conversation, turn, operation) {
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
638
|
+
return;
|
|
639
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
390
640
|
}
|
|
391
|
-
|
|
392
|
-
turn.
|
|
393
|
-
|
|
394
|
-
this.kit.send(note);
|
|
395
|
-
if (turn.text !== '') {
|
|
396
|
-
turn.unsentFrom = 0;
|
|
397
|
-
this.draft(conversation, turn);
|
|
398
|
-
}
|
|
399
|
-
if (turn.entries !== null)
|
|
400
|
-
this.plan(conversation, turn);
|
|
641
|
+
replay(conversation, turn) {
|
|
642
|
+
for (const operation of turn.parts.operations())
|
|
643
|
+
this.write(conversation, turn, operation);
|
|
401
644
|
}
|
|
402
645
|
begin(conversation) {
|
|
403
|
-
const turn = this.
|
|
646
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
647
|
+
conversation.open++;
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
404
649
|
conversation.turn = turn;
|
|
650
|
+
if (conversation.context !== null)
|
|
651
|
+
turn.parts.measure(conversation.context);
|
|
652
|
+
for (const part of conversation.held.splice(0))
|
|
653
|
+
turn.parts.start(part);
|
|
405
654
|
this.kit.changed();
|
|
406
655
|
return turn;
|
|
407
656
|
}
|
|
408
|
-
|
|
409
|
-
if (turn.ended)
|
|
410
|
-
return;
|
|
411
|
-
const segment = turn.segment;
|
|
412
|
-
turn.segment = null;
|
|
413
|
-
if (segment?.phase === 'note')
|
|
414
|
-
this.note(conversation, turn, segment.text);
|
|
415
|
-
else if (segment !== null)
|
|
416
|
-
this.answer(conversation, turn, segment.text);
|
|
417
|
-
this.end(conversation, turn, { text: turn.text });
|
|
418
|
-
}
|
|
419
|
-
end(conversation, turn, outcome) {
|
|
657
|
+
end(conversation, turn, failed, notice = null) {
|
|
420
658
|
if (turn.ended)
|
|
421
659
|
return;
|
|
660
|
+
if (failed !== null)
|
|
661
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
662
|
+
if (conversation.turn === turn) {
|
|
663
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
664
|
+
this.apply(conversation, turn, event);
|
|
665
|
+
}
|
|
666
|
+
if (notice !== null)
|
|
667
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
422
668
|
turn.ended = true;
|
|
423
669
|
conversation.open--;
|
|
424
670
|
if (conversation.turn === turn)
|
|
425
671
|
conversation.turn = null;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
672
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
673
|
+
parts: turn.parts.saved(),
|
|
674
|
+
context: turn.parts.context,
|
|
675
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
676
|
+
}));
|
|
677
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
678
|
+
this.next(conversation);
|
|
430
679
|
this.kit.changed();
|
|
431
680
|
}
|
|
432
|
-
|
|
433
|
-
if (conversation.open > 0)
|
|
434
|
-
conversation.late.push(cause);
|
|
435
|
-
else
|
|
436
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
437
|
-
}
|
|
438
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
439
682
|
const running = conversation.running;
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
this.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
else
|
|
451
|
-
this.end(conversation,
|
|
683
|
+
const turn = this.begin(conversation);
|
|
684
|
+
turn.prompted = true;
|
|
685
|
+
turn.taken = taken;
|
|
686
|
+
conversation.prompted = running.sessionId;
|
|
687
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
688
|
+
this.took(turn);
|
|
689
|
+
const notice = answeredFailure(answer);
|
|
690
|
+
if (notice !== null) {
|
|
691
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
695
|
+
}
|
|
696
|
+
}, async (error) => {
|
|
697
|
+
if (running.adapter.connection.signal.aborted) {
|
|
698
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
this.took(turn);
|
|
702
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
703
|
+
running.adapter.retire();
|
|
704
|
+
this.detach(conversation, running);
|
|
705
|
+
}
|
|
706
|
+
this.end(conversation, turn, answeredWith(error));
|
|
452
707
|
}
|
|
453
708
|
});
|
|
454
709
|
}
|
|
455
|
-
attach(turn, sent) {
|
|
456
|
-
turn.prompt = sent;
|
|
457
|
-
sent.turn = turn;
|
|
458
|
-
}
|
|
459
|
-
answered(conversation, sent, failed) {
|
|
460
|
-
const turn = sent.turn;
|
|
461
|
-
if (turn === null) {
|
|
462
|
-
const queued = conversation.queued.indexOf(sent);
|
|
463
|
-
if (queued < 0)
|
|
464
|
-
return;
|
|
465
|
-
conversation.queued.splice(queued, 1);
|
|
466
|
-
if (failed !== null)
|
|
467
|
-
this.failed(conversation, failed);
|
|
468
|
-
}
|
|
469
|
-
else if (failed !== null) {
|
|
470
|
-
if (turn.ended)
|
|
471
|
-
this.failed(conversation, failed);
|
|
472
|
-
else
|
|
473
|
-
this.end(conversation, turn, { failed });
|
|
474
|
-
}
|
|
475
|
-
else if (turn.prompt === sent) {
|
|
476
|
-
this.finish(conversation, turn);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
710
|
started(conversation) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const turn = this.begin(conversation);
|
|
483
|
-
const queued = conversation.queued.shift();
|
|
484
|
-
if (queued !== undefined)
|
|
485
|
-
this.attach(turn, queued);
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
486
713
|
return turn;
|
|
487
714
|
}
|
|
488
715
|
ended(conversation) {
|
|
489
716
|
const turn = conversation.turn;
|
|
490
717
|
if (turn === null)
|
|
491
718
|
return;
|
|
492
|
-
if (turn.
|
|
493
|
-
this.
|
|
719
|
+
if (!turn.prompted) {
|
|
720
|
+
this.end(conversation, turn, null);
|
|
494
721
|
}
|
|
495
722
|
else {
|
|
723
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
724
|
+
this.apply(conversation, turn, event);
|
|
496
725
|
conversation.turn = null;
|
|
497
726
|
this.kit.changed();
|
|
498
727
|
}
|
|
@@ -541,101 +770,107 @@ export class Conversations {
|
|
|
541
770
|
if (content !== null)
|
|
542
771
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
543
772
|
}
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
773
|
+
read(conversation, reader, message) {
|
|
774
|
+
for (const event of reader.read(message))
|
|
775
|
+
this.event(conversation, event);
|
|
776
|
+
}
|
|
777
|
+
event(conversation, event) {
|
|
778
|
+
switch (event.kind) {
|
|
779
|
+
case 'started':
|
|
780
|
+
this.started(conversation);
|
|
781
|
+
return;
|
|
782
|
+
case 'ended':
|
|
783
|
+
this.ended(conversation);
|
|
784
|
+
return;
|
|
785
|
+
case 'commands':
|
|
786
|
+
this.commands(conversation, event.commands);
|
|
787
|
+
return;
|
|
788
|
+
case 'options':
|
|
789
|
+
this.options(conversation, event.options);
|
|
790
|
+
return;
|
|
791
|
+
case 'context':
|
|
792
|
+
conversation.context = { used: event.used, window: event.window };
|
|
793
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
794
|
+
return;
|
|
795
|
+
case 'subagent-done':
|
|
796
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
797
|
+
return;
|
|
798
|
+
case 'marker':
|
|
799
|
+
case 'image':
|
|
800
|
+
if (conversation.turn !== null)
|
|
801
|
+
break;
|
|
802
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
803
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
804
|
+
return;
|
|
568
805
|
}
|
|
806
|
+
this.apply(conversation, this.started(conversation), event);
|
|
569
807
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
conversation.jobs.delete(job.id);
|
|
575
|
-
this.kit.changed();
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
576
812
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
813
|
+
apply(conversation, turn, event) {
|
|
814
|
+
switch (event.kind) {
|
|
815
|
+
case 'text':
|
|
816
|
+
case 'reasoning':
|
|
817
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
818
|
+
return;
|
|
819
|
+
case 'command':
|
|
820
|
+
turn.parts.command();
|
|
821
|
+
return;
|
|
822
|
+
case 'subagent': {
|
|
823
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
824
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
case 'message':
|
|
828
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
829
|
+
return;
|
|
830
|
+
case 'marker':
|
|
831
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
832
|
+
return;
|
|
833
|
+
case 'plan':
|
|
834
|
+
turn.parts.plan(event.entries);
|
|
835
|
+
return;
|
|
836
|
+
case 'image':
|
|
837
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
838
|
+
this.kit.changed();
|
|
839
|
+
return;
|
|
585
840
|
}
|
|
586
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
587
|
-
turn.segment.text += text;
|
|
588
841
|
}
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
if (
|
|
842
|
+
subagentDone(conversation, id, result) {
|
|
843
|
+
const subagent = conversation.subagents.get(id);
|
|
844
|
+
if (subagent === undefined)
|
|
592
845
|
return;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
note(conversation, turn, text) {
|
|
597
|
-
if (text.trim() === '')
|
|
846
|
+
conversation.subagents.delete(id);
|
|
847
|
+
if (subagent.turn === conversation.turn) {
|
|
848
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
598
849
|
return;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
850
|
+
}
|
|
851
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
852
|
+
if (conversation.turn === null)
|
|
853
|
+
conversation.held.push(part);
|
|
602
854
|
else
|
|
603
|
-
turn.
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
});
|
|
620
|
-
if (sent) {
|
|
621
|
-
turn.draftSequence++;
|
|
622
|
-
turn.sent = blocks;
|
|
623
|
-
turn.unsentFrom = null;
|
|
855
|
+
conversation.turn.parts.start(part);
|
|
856
|
+
}
|
|
857
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
858
|
+
const bytes = Buffer.from(data, 'base64');
|
|
859
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
860
|
+
try {
|
|
861
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
862
|
+
name,
|
|
863
|
+
media_type: mediaType,
|
|
864
|
+
bytes: bytes.length,
|
|
865
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
866
|
+
}));
|
|
867
|
+
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
868
|
+
if (saved.save.status !== 'saved')
|
|
869
|
+
throw new Error(saved.save.failure ?? saved.save.status);
|
|
870
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
624
871
|
}
|
|
625
|
-
|
|
626
|
-
turn.
|
|
872
|
+
catch (error) {
|
|
873
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
627
874
|
}
|
|
628
875
|
}
|
|
629
|
-
plan(conversation, turn) {
|
|
630
|
-
const sent = this.kit.send({
|
|
631
|
-
type: 'plan',
|
|
632
|
-
conversation_id: conversation.id,
|
|
633
|
-
turn_id: turn.id,
|
|
634
|
-
sequence: turn.planSequence + 1,
|
|
635
|
-
from: 0,
|
|
636
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
637
|
-
});
|
|
638
|
-
if (sent)
|
|
639
|
-
turn.planSequence++;
|
|
640
|
-
}
|
|
641
876
|
}
|