@agentshouse/kit 0.2.1-c84a4fccf2835 → 0.2.1-c8850697c8230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -28
- package/dist/acp.js +90 -19
- package/dist/agents.js +86 -38
- package/dist/api.js +5 -5
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +71 -0
- package/dist/clis.js +58 -22
- package/dist/{stream.js → connection.js} +45 -52
- package/dist/conversations.js +429 -147
- package/dist/copies.js +53 -51
- package/dist/end.js +14 -4
- package/dist/events.js +29 -8
- package/dist/files.js +139 -37
- package/dist/git.js +3 -3
- package/dist/home.js +0 -7
- package/dist/house-cli.js +95 -87
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +6 -2
- package/dist/placement.js +11 -2
- package/dist/relay.js +21 -0
- package/dist/resident.js +46 -38
- package/dist/scope.js +15 -4
- package/dist/scripts.js +255 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +22 -21
- package/dist/silence.js +28 -0
- package/dist/skills.js +42 -97
- package/dist/transcription.js +83 -43
- package/dist/translate.js +2 -8
- package/dist/update.js +79 -37
- package/dist/wire.js +19 -3
- package/package.json +20 -21
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/inputs.js +0 -37
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { client,
|
|
1
|
+
import { client, } 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
|
-
import { startAdapter } from './acp.js';
|
|
7
|
-
import { finalRefusal } from './api.js';
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
10
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
-
import {
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
12
12
|
import { agentBase } from './home.js';
|
|
13
13
|
import { instructions } from './instructions.js';
|
|
14
14
|
import { Parts } from './parts.js';
|
|
15
15
|
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
16
17
|
import { killMarked, Scope } from './scope.js';
|
|
17
18
|
import { holdSecretInput } from './secret-input.js';
|
|
18
|
-
import { createHowWeWork } from './skills.js';
|
|
19
19
|
import { transcribe } from './transcription.js';
|
|
20
20
|
import { workFor } from './work.js';
|
|
21
|
-
const SIGNED_OUT = RequestError.authRequired().code;
|
|
22
21
|
const KILLED = 'the conversation was killed';
|
|
22
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
23
23
|
const REFUSED = 'the CLI refused the prompt';
|
|
24
24
|
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
25
|
const GONE = 'House no longer holds this conversation; start a new one';
|
|
@@ -31,9 +31,13 @@ const ENDED = {
|
|
|
31
31
|
const NO_START = "the Agent's CLI did not start";
|
|
32
32
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
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";
|
|
34
35
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
35
37
|
class NotStarted extends Error {
|
|
36
38
|
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
37
41
|
async function step(reason, work) {
|
|
38
42
|
try {
|
|
39
43
|
return await work;
|
|
@@ -42,6 +46,11 @@ async function step(reason, work) {
|
|
|
42
46
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
43
47
|
}
|
|
44
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;
|
|
45
54
|
function unbridged(error) {
|
|
46
55
|
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
56
|
}
|
|
@@ -53,40 +62,57 @@ const EXIT_PLAN_MODE = {
|
|
|
53
62
|
return value;
|
|
54
63
|
},
|
|
55
64
|
};
|
|
56
|
-
export const
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'done', 'option', 'answer']);
|
|
57
66
|
function causeOf(error) {
|
|
58
67
|
return error instanceof Error ? error.message : String(error);
|
|
59
68
|
}
|
|
60
69
|
function answeredWith(error) {
|
|
61
|
-
|
|
62
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
71
|
}
|
|
64
72
|
function exitedWith(running, cause) {
|
|
65
|
-
|
|
73
|
+
if (running.killed)
|
|
74
|
+
return { failure: 'killed', cause: KILLED };
|
|
75
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
66
76
|
}
|
|
67
77
|
function answerMessage(request, response) {
|
|
68
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
69
79
|
}
|
|
80
|
+
async function sentPart(url, bytes, headers) {
|
|
81
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
82
|
+
await answer.arrayBuffer();
|
|
83
|
+
if (!answer.ok)
|
|
84
|
+
throw storeRefusal('upload', answer.status);
|
|
85
|
+
}
|
|
70
86
|
function logged(error) {
|
|
71
87
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
72
88
|
}
|
|
73
89
|
function refusal(conversation, error) {
|
|
74
90
|
if (conversation.kills > 0)
|
|
75
91
|
return KILLED;
|
|
76
|
-
const cause = error instanceof
|
|
92
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
77
93
|
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
78
94
|
return causeOf(error);
|
|
79
95
|
}
|
|
96
|
+
function audioOf(result) {
|
|
97
|
+
if (result.isError === true)
|
|
98
|
+
return null;
|
|
99
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
100
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
101
|
+
}
|
|
80
102
|
function marker(bridge) {
|
|
81
103
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
82
104
|
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
107
|
+
}
|
|
83
108
|
class Turn {
|
|
84
109
|
id = randomUUID();
|
|
85
110
|
parts;
|
|
86
|
-
admitted = false;
|
|
87
111
|
questions = 0;
|
|
88
112
|
secrets = 0;
|
|
89
113
|
prompted = false;
|
|
114
|
+
steered = false;
|
|
115
|
+
taken = null;
|
|
90
116
|
ended = false;
|
|
91
117
|
images = 0;
|
|
92
118
|
uploads = [];
|
|
@@ -100,21 +126,31 @@ class Conversation {
|
|
|
100
126
|
session = null;
|
|
101
127
|
turn = null;
|
|
102
128
|
commands = null;
|
|
129
|
+
options = null;
|
|
130
|
+
exitUntold = false;
|
|
103
131
|
context = null;
|
|
104
132
|
leftovers = new Set();
|
|
105
133
|
waiting = [];
|
|
134
|
+
done = false;
|
|
106
135
|
sending = null;
|
|
107
136
|
subagents = new Map();
|
|
108
137
|
held = [];
|
|
109
138
|
opening = null;
|
|
139
|
+
bridge = null;
|
|
140
|
+
launch = null;
|
|
110
141
|
prompted = null;
|
|
111
142
|
kills = 0;
|
|
112
143
|
killed = new AbortController();
|
|
113
144
|
interrupts = 0;
|
|
114
145
|
interrupted = new AbortController();
|
|
115
|
-
|
|
146
|
+
turns = new Set();
|
|
116
147
|
queue = Promise.resolve();
|
|
117
148
|
reports = Promise.resolve();
|
|
149
|
+
starting = null;
|
|
150
|
+
preparing = null;
|
|
151
|
+
told = null;
|
|
152
|
+
untold = false;
|
|
153
|
+
shown = null;
|
|
118
154
|
constructor(id) {
|
|
119
155
|
this.id = id;
|
|
120
156
|
}
|
|
@@ -138,23 +174,23 @@ export class Conversations {
|
|
|
138
174
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
139
175
|
setInterval(() => this.look(), 2_000);
|
|
140
176
|
}
|
|
141
|
-
|
|
142
|
-
void this.kit.
|
|
143
|
-
const conversation = this.conversation(
|
|
144
|
-
if (
|
|
177
|
+
call(call) {
|
|
178
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
179
|
+
const conversation = this.conversation(call.conversation_id);
|
|
180
|
+
if (call.kind === 'kill') {
|
|
145
181
|
conversation.kills++;
|
|
146
182
|
conversation.killed.abort(new Error(KILLED));
|
|
147
183
|
for (const waiting of conversation.waiting.splice(0))
|
|
148
184
|
waiting.settle({ refused: KILLED });
|
|
149
185
|
this.halt(conversation);
|
|
150
186
|
}
|
|
151
|
-
if (
|
|
187
|
+
if (call.kind === 'interrupt') {
|
|
152
188
|
conversation.interrupts++;
|
|
153
189
|
conversation.interrupted.abort();
|
|
154
190
|
}
|
|
155
|
-
const handled = conversation.queue.then(() => this.carry(conversation,
|
|
191
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
156
192
|
conversation.queue = handled.catch(() => undefined);
|
|
157
|
-
return handled.then(async ({
|
|
193
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
158
194
|
});
|
|
159
195
|
}
|
|
160
196
|
idle() {
|
|
@@ -163,7 +199,7 @@ export class Conversations {
|
|
|
163
199
|
for (const conversation of this.conversations.values()) {
|
|
164
200
|
const turn = conversation.turn;
|
|
165
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
166
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
167
203
|
return false;
|
|
168
204
|
}
|
|
169
205
|
const conversations = [...this.conversations.values()];
|
|
@@ -172,7 +208,7 @@ export class Conversations {
|
|
|
172
208
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
173
209
|
}
|
|
174
210
|
quiet() {
|
|
175
|
-
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
176
212
|
}
|
|
177
213
|
look() {
|
|
178
214
|
for (const conversation of this.conversations.values()) {
|
|
@@ -187,6 +223,16 @@ export class Conversations {
|
|
|
187
223
|
}
|
|
188
224
|
opened() {
|
|
189
225
|
this.watched = false;
|
|
226
|
+
for (const conversation of this.conversations.values()) {
|
|
227
|
+
if (conversation.running !== null) {
|
|
228
|
+
this.facts(conversation);
|
|
229
|
+
this.subagentsChanged(conversation);
|
|
230
|
+
}
|
|
231
|
+
else if (conversation.exitUntold)
|
|
232
|
+
this.process(conversation, false);
|
|
233
|
+
if (conversation.untold || conversation.told !== null)
|
|
234
|
+
this.retell(conversation);
|
|
235
|
+
}
|
|
190
236
|
}
|
|
191
237
|
watching(watched) {
|
|
192
238
|
const resend = watched && !this.watched;
|
|
@@ -206,134 +252,320 @@ export class Conversations {
|
|
|
206
252
|
}
|
|
207
253
|
return conversation;
|
|
208
254
|
}
|
|
209
|
-
async carry(conversation,
|
|
210
|
-
if (
|
|
211
|
-
return
|
|
212
|
-
if (
|
|
213
|
-
return this.message(conversation,
|
|
214
|
-
if (
|
|
255
|
+
async carry(conversation, call) {
|
|
256
|
+
if (call.kind === 'open')
|
|
257
|
+
return this.open(conversation, call);
|
|
258
|
+
if (call.kind === 'message')
|
|
259
|
+
return this.message(conversation, call);
|
|
260
|
+
if (call.kind === 'done') {
|
|
261
|
+
conversation.done = true;
|
|
262
|
+
this.next(conversation);
|
|
263
|
+
return { result: Promise.resolve({}) };
|
|
264
|
+
}
|
|
265
|
+
await this.launched(conversation);
|
|
266
|
+
if (call.kind === 'interrupt') {
|
|
215
267
|
if (--conversation.interrupts === 0)
|
|
216
268
|
conversation.interrupted = new AbortController();
|
|
217
|
-
await this.interrupt(conversation, String(
|
|
269
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
218
270
|
}
|
|
219
|
-
if (
|
|
271
|
+
if (call.kind === 'kill') {
|
|
220
272
|
await this.kill(conversation);
|
|
221
273
|
await conversation.sending;
|
|
222
274
|
if (--conversation.kills === 0)
|
|
223
275
|
conversation.killed = new AbortController();
|
|
224
276
|
}
|
|
225
|
-
if (
|
|
226
|
-
await this.option(conversation, String(
|
|
227
|
-
if (
|
|
228
|
-
const question = this.questions.get(String(
|
|
277
|
+
if (call.kind === 'option')
|
|
278
|
+
await this.option(conversation, String(call.option), call.value);
|
|
279
|
+
if (call.kind === 'answer') {
|
|
280
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
229
281
|
if (question === undefined) {
|
|
230
|
-
const text = answerMessage(
|
|
231
|
-
return this.message(conversation, { ...
|
|
282
|
+
const text = answerMessage(call.request, call.response);
|
|
283
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
232
284
|
}
|
|
233
|
-
question.answer(
|
|
285
|
+
question.answer(call.response);
|
|
234
286
|
}
|
|
235
|
-
return {
|
|
287
|
+
return { result: Promise.resolve({}) };
|
|
288
|
+
}
|
|
289
|
+
async open(conversation, call) {
|
|
290
|
+
await this.stop(conversation);
|
|
291
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
292
|
+
return {
|
|
293
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async stop(conversation) {
|
|
297
|
+
const running = conversation.running;
|
|
298
|
+
running?.scope.kill();
|
|
299
|
+
await running?.adapter.exited;
|
|
300
|
+
}
|
|
301
|
+
launch(conversation, route, sessionId, empty) {
|
|
302
|
+
let opened;
|
|
303
|
+
let failed;
|
|
304
|
+
const bridge = new Promise((resolve, reject) => {
|
|
305
|
+
opened = resolve;
|
|
306
|
+
failed = reject;
|
|
307
|
+
});
|
|
308
|
+
bridge.catch(() => undefined);
|
|
309
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
310
|
+
session.catch(failed);
|
|
311
|
+
const launch = { bridge, session };
|
|
312
|
+
conversation.launch = launch;
|
|
313
|
+
void session
|
|
314
|
+
.catch(() => undefined)
|
|
315
|
+
.then(() => {
|
|
316
|
+
if (conversation.launch !== launch)
|
|
317
|
+
return;
|
|
318
|
+
conversation.launch = null;
|
|
319
|
+
this.closeIdleBridge(conversation);
|
|
320
|
+
this.next(conversation);
|
|
321
|
+
});
|
|
322
|
+
this.next(conversation);
|
|
323
|
+
return launch;
|
|
324
|
+
}
|
|
325
|
+
async launched(conversation) {
|
|
326
|
+
while (conversation.launch !== null)
|
|
327
|
+
await conversation.launch.session.catch(() => undefined);
|
|
328
|
+
}
|
|
329
|
+
instructed(conversation, route) {
|
|
330
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
331
|
+
if (bridge === undefined)
|
|
332
|
+
return null;
|
|
333
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
334
|
+
read.catch(() => undefined);
|
|
335
|
+
return read;
|
|
236
336
|
}
|
|
237
|
-
async
|
|
238
|
-
|
|
239
|
-
|
|
337
|
+
async message(conversation, call) {
|
|
338
|
+
const content = [
|
|
339
|
+
...(typeof call.recent === 'string' ? [{ type: 'text', text: call.recent }] : []),
|
|
340
|
+
{ type: 'text', text: String(call.text) },
|
|
341
|
+
];
|
|
342
|
+
let route;
|
|
343
|
+
let instructing = null;
|
|
240
344
|
try {
|
|
241
|
-
|
|
345
|
+
route = await this.route(conversation, call.agent_id);
|
|
346
|
+
if (call.first === true)
|
|
347
|
+
instructing = this.instructed(conversation, route);
|
|
348
|
+
if (call.message_id !== null)
|
|
349
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
350
|
+
if (conversation.kills > 0)
|
|
351
|
+
throw new Error(KILLED);
|
|
242
352
|
}
|
|
243
353
|
catch (error) {
|
|
244
|
-
|
|
354
|
+
this.closeIdleBridge(conversation);
|
|
355
|
+
this.prepares(conversation, null);
|
|
356
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
245
357
|
}
|
|
358
|
+
conversation.preparing = null;
|
|
359
|
+
const { promise, resolve: settle } = Promise.withResolvers();
|
|
360
|
+
const waiting = { call, route, content, instructing, settle };
|
|
361
|
+
if (call.at_once === true && conversation.turns.size > 0)
|
|
362
|
+
await this.atOnce(conversation, waiting);
|
|
363
|
+
else
|
|
364
|
+
conversation.waiting.push(waiting);
|
|
365
|
+
this.next(conversation);
|
|
366
|
+
return { result: promise };
|
|
246
367
|
}
|
|
247
|
-
async
|
|
248
|
-
|
|
249
|
-
|
|
368
|
+
async atOnce(conversation, waiting) {
|
|
369
|
+
while (conversation.sending !== null)
|
|
370
|
+
await conversation.sending;
|
|
371
|
+
const running = conversation.running;
|
|
372
|
+
if (running !== null && conversation.turns.size > 0 && steers(running.adapter)) {
|
|
373
|
+
conversation.sending = this.steer(conversation, running, waiting);
|
|
374
|
+
await conversation.sending;
|
|
375
|
+
conversation.sending = null;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.ahead(conversation, waiting);
|
|
379
|
+
if (conversation.turns.size > 0)
|
|
380
|
+
await this.cancel(conversation);
|
|
381
|
+
}
|
|
382
|
+
ahead(conversation, waiting) {
|
|
383
|
+
const behind = conversation.waiting.findIndex((held) => held.call.at_once !== true);
|
|
384
|
+
conversation.waiting.splice(behind === -1 ? conversation.waiting.length : behind, 0, waiting);
|
|
385
|
+
}
|
|
386
|
+
async steer(conversation, running, waiting) {
|
|
387
|
+
let outcome;
|
|
250
388
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
257
|
-
if (conversation.kills > 0)
|
|
258
|
-
throw new Error(KILLED);
|
|
389
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
390
|
+
sessionId: running.sessionId,
|
|
391
|
+
prompt: waiting.content,
|
|
392
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
393
|
+
}));
|
|
259
394
|
}
|
|
260
395
|
catch (error) {
|
|
261
|
-
|
|
396
|
+
outcome = error;
|
|
397
|
+
}
|
|
398
|
+
if (outcome === 'promptRequired') {
|
|
399
|
+
this.ahead(conversation, waiting);
|
|
400
|
+
}
|
|
401
|
+
else if (outcome === 'injected' || outcome === 'startedNewTurn') {
|
|
402
|
+
if (outcome === 'injected') {
|
|
403
|
+
for (const turn of conversation.turns)
|
|
404
|
+
turn.steered ||= turn.prompted;
|
|
405
|
+
}
|
|
406
|
+
this.kit.calls.keepResult(String(waiting.call.input_id), {});
|
|
407
|
+
waiting.settle({});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const cause = outcome instanceof Error ? outcome : new Error(`the CLI answered ${JSON.stringify(outcome)}`);
|
|
411
|
+
waiting.settle({ refused: refusal(conversation, new NotStarted(UNSTEERED, { cause })) });
|
|
262
412
|
}
|
|
263
|
-
return {
|
|
264
|
-
ack: new Promise((settle) => {
|
|
265
|
-
conversation.waiting.push({ input, route, content, settle });
|
|
266
|
-
this.next(conversation);
|
|
267
|
-
}),
|
|
268
|
-
};
|
|
269
413
|
}
|
|
270
414
|
next(conversation) {
|
|
271
|
-
if (conversation.sending !== null || conversation.
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
272
416
|
return;
|
|
273
417
|
const waiting = conversation.waiting.shift();
|
|
274
|
-
if (waiting === undefined)
|
|
418
|
+
if (waiting === undefined) {
|
|
419
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
420
|
+
if (conversation.starting !== step)
|
|
421
|
+
this.starts(conversation, step);
|
|
422
|
+
if (conversation.done && conversation.launch === null)
|
|
423
|
+
void this.stop(conversation).catch(logged);
|
|
275
424
|
return;
|
|
425
|
+
}
|
|
276
426
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
277
427
|
conversation.sending = null;
|
|
278
428
|
this.next(conversation);
|
|
279
429
|
});
|
|
280
430
|
}
|
|
281
431
|
async send(conversation, waiting) {
|
|
282
|
-
const {
|
|
283
|
-
let
|
|
432
|
+
const { call } = waiting;
|
|
433
|
+
let result = {};
|
|
284
434
|
let started = false;
|
|
435
|
+
let instructing = waiting.instructing;
|
|
285
436
|
const prompt = [];
|
|
286
437
|
try {
|
|
287
438
|
if (conversation.running === null) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
439
|
+
this.starts(conversation, 'session');
|
|
440
|
+
await this.launched(conversation);
|
|
441
|
+
}
|
|
442
|
+
if (conversation.running === null) {
|
|
443
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
444
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
445
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
446
|
+
if (call.first === true)
|
|
447
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
448
|
+
started = (await launch.session) !== stored;
|
|
291
449
|
}
|
|
292
450
|
const session = conversation.running.sessionId;
|
|
293
|
-
if (started || (
|
|
294
|
-
|
|
451
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
452
|
+
result = { provider_session_id: session };
|
|
295
453
|
}
|
|
296
|
-
if (
|
|
297
|
-
prompt.push({
|
|
298
|
-
type: 'text',
|
|
299
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
300
|
-
});
|
|
454
|
+
if (call.first === true) {
|
|
455
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
301
456
|
}
|
|
302
457
|
if (conversation.kills > 0)
|
|
303
458
|
throw new Error(KILLED);
|
|
304
459
|
}
|
|
305
460
|
catch (error) {
|
|
461
|
+
this.starts(conversation, null);
|
|
306
462
|
if (started) {
|
|
307
463
|
conversation.session = null;
|
|
308
464
|
await this.kill(conversation);
|
|
309
465
|
}
|
|
310
|
-
|
|
466
|
+
const refused = refusal(conversation, error);
|
|
467
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
468
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
311
469
|
return;
|
|
312
470
|
}
|
|
313
|
-
this.
|
|
314
|
-
waiting.
|
|
471
|
+
this.starts(conversation, null);
|
|
472
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
473
|
+
waiting.settle(result);
|
|
315
474
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
475
|
+
starts(conversation, step) {
|
|
476
|
+
conversation.starting = step;
|
|
477
|
+
if (conversation.shown !== null)
|
|
478
|
+
clearTimeout(conversation.shown);
|
|
479
|
+
conversation.shown = null;
|
|
480
|
+
if (step === null || conversation.told !== null) {
|
|
481
|
+
this.tell(conversation, step);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
conversation.shown = setTimeout(() => {
|
|
485
|
+
conversation.shown = null;
|
|
486
|
+
if (conversation.starting === step)
|
|
487
|
+
this.tell(conversation, step);
|
|
488
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
489
|
+
}
|
|
490
|
+
prepares(conversation, step) {
|
|
491
|
+
conversation.preparing = step;
|
|
492
|
+
this.next(conversation);
|
|
493
|
+
}
|
|
494
|
+
tell(conversation, step) {
|
|
495
|
+
if (conversation.told === step && !conversation.untold)
|
|
496
|
+
return;
|
|
497
|
+
conversation.told = step;
|
|
498
|
+
this.retell(conversation);
|
|
499
|
+
}
|
|
500
|
+
retell(conversation) {
|
|
501
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
502
|
+
}
|
|
503
|
+
async transcribed(conversation, messageId, text) {
|
|
504
|
+
const paths = linkedPaths(text);
|
|
505
|
+
if (paths.length === 0)
|
|
506
|
+
return [];
|
|
507
|
+
const killed = conversation.killed.signal;
|
|
508
|
+
const interrupted = conversation.interrupted.signal;
|
|
509
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
510
|
+
let signal = killed;
|
|
511
|
+
const blocks = [];
|
|
512
|
+
try {
|
|
513
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
514
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
515
|
+
for (const path of paths) {
|
|
516
|
+
let answered = await read(path);
|
|
517
|
+
if (answered === null)
|
|
518
|
+
continue;
|
|
519
|
+
signal = transcribing;
|
|
520
|
+
this.prepares(conversation, 'transcription');
|
|
521
|
+
const said = await transcribe(async () => {
|
|
522
|
+
const address = answered ?? (await read(path));
|
|
523
|
+
answered = null;
|
|
524
|
+
if (address === null)
|
|
525
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
526
|
+
return address;
|
|
527
|
+
}, signal);
|
|
528
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
529
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
530
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
331
531
|
}
|
|
332
532
|
}
|
|
333
|
-
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
535
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
return blocks;
|
|
539
|
+
}
|
|
540
|
+
bridged(conversation) {
|
|
541
|
+
if (conversation.bridge === null) {
|
|
542
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
543
|
+
conversation.bridge = bridging;
|
|
544
|
+
bridging.catch(() => {
|
|
545
|
+
if (conversation.bridge === bridging)
|
|
546
|
+
conversation.bridge = null;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return conversation.bridge;
|
|
550
|
+
}
|
|
551
|
+
closeBridge(conversation, bridging, bridge) {
|
|
552
|
+
bridge.close();
|
|
553
|
+
if (conversation.bridge === bridging)
|
|
554
|
+
conversation.bridge = null;
|
|
555
|
+
}
|
|
556
|
+
closeIdleBridge(conversation) {
|
|
557
|
+
const bridging = conversation.bridge;
|
|
558
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
559
|
+
return;
|
|
560
|
+
conversation.bridge = null;
|
|
561
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
334
562
|
}
|
|
335
563
|
async interrupt(conversation, turnId) {
|
|
336
|
-
if (conversation.
|
|
564
|
+
if ([...conversation.turns].some((turn) => turn.id === turnId))
|
|
565
|
+
await this.cancel(conversation);
|
|
566
|
+
}
|
|
567
|
+
async cancel(conversation) {
|
|
568
|
+
if (conversation.running === null)
|
|
337
569
|
return;
|
|
338
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
339
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -385,7 +617,8 @@ export class Conversations {
|
|
|
385
617
|
}
|
|
386
618
|
return route;
|
|
387
619
|
}
|
|
388
|
-
async start(conversation,
|
|
620
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
621
|
+
const route = await routed;
|
|
389
622
|
const reader = readerFor(route.kind);
|
|
390
623
|
reader.session = sessionId;
|
|
391
624
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -393,50 +626,51 @@ export class Conversations {
|
|
|
393
626
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
394
627
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
395
628
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
396
|
-
const
|
|
629
|
+
const bridging = this.bridged(conversation);
|
|
630
|
+
const bridge = await step(unbridged, bridging);
|
|
397
631
|
if (conversation.kills > 0) {
|
|
398
|
-
|
|
632
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
399
633
|
throw new Error(KILLED);
|
|
400
634
|
}
|
|
401
635
|
conversation.opening = bridge;
|
|
402
636
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
403
637
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
638
|
+
let adapter = null;
|
|
404
639
|
try {
|
|
405
|
-
|
|
640
|
+
bridged(bridge);
|
|
406
641
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
407
642
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
408
|
-
|
|
643
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
409
644
|
work.read(message);
|
|
410
645
|
this.read(conversation, reader, message);
|
|
411
646
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
412
|
-
void adapter.exited.then(() =>
|
|
647
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
413
648
|
const agent = adapter.connection.agent;
|
|
414
649
|
const cwd = route.working_directory;
|
|
415
650
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
416
651
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
417
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
652
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
418
653
|
reader.session = opened.sessionId;
|
|
419
654
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
420
655
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
421
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
656
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
422
657
|
}
|
|
423
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
658
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
424
659
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
425
660
|
void work.settled().then(() => scope.record());
|
|
426
661
|
conversation.running = running;
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
427
663
|
conversation.session = running.sessionId;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.options(conversation, options);
|
|
432
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
664
|
+
conversation.options = options;
|
|
665
|
+
this.facts(conversation);
|
|
666
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
433
667
|
return running.sessionId;
|
|
434
668
|
}
|
|
435
669
|
catch (error) {
|
|
436
|
-
|
|
670
|
+
adapter?.retire();
|
|
437
671
|
work.close();
|
|
438
672
|
conversation.leftovers.add(scope);
|
|
439
|
-
|
|
673
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
440
674
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
441
675
|
}
|
|
442
676
|
finally {
|
|
@@ -449,7 +683,7 @@ export class Conversations {
|
|
|
449
683
|
}
|
|
450
684
|
catch (error) {
|
|
451
685
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
452
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
686
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
453
687
|
}
|
|
454
688
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
455
689
|
return null;
|
|
@@ -476,10 +710,7 @@ export class Conversations {
|
|
|
476
710
|
}
|
|
477
711
|
return options;
|
|
478
712
|
}
|
|
479
|
-
exited(conversation,
|
|
480
|
-
const running = conversation.running;
|
|
481
|
-
if (running?.adapter !== adapter)
|
|
482
|
-
return;
|
|
713
|
+
exited(conversation, running, cause) {
|
|
483
714
|
this.detach(conversation, running);
|
|
484
715
|
if (conversation.turn !== null) {
|
|
485
716
|
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
@@ -488,15 +719,36 @@ export class Conversations {
|
|
|
488
719
|
this.kit.changed();
|
|
489
720
|
}
|
|
490
721
|
detach(conversation, running) {
|
|
722
|
+
if (conversation.bridge === running.bridging)
|
|
723
|
+
conversation.bridge = null;
|
|
491
724
|
running.work.close();
|
|
492
725
|
conversation.leftovers.add(running.scope);
|
|
493
726
|
conversation.running = null;
|
|
494
727
|
conversation.commands = null;
|
|
728
|
+
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
495
733
|
for (const [id, question] of this.questions) {
|
|
496
734
|
if (question.conversation === conversation)
|
|
497
735
|
this.questions.delete(id);
|
|
498
736
|
}
|
|
499
|
-
this.
|
|
737
|
+
this.process(conversation, false);
|
|
738
|
+
}
|
|
739
|
+
process(conversation, running) {
|
|
740
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
741
|
+
}
|
|
742
|
+
facts(conversation) {
|
|
743
|
+
this.process(conversation, true);
|
|
744
|
+
if (conversation.commands !== null)
|
|
745
|
+
this.commands(conversation, conversation.commands);
|
|
746
|
+
if (conversation.options !== null)
|
|
747
|
+
this.options(conversation, conversation.options);
|
|
748
|
+
}
|
|
749
|
+
subagentsChanged(conversation) {
|
|
750
|
+
const subagents = [...conversation.subagents.values()].map(({ description, activity }) => ({ description, activity }));
|
|
751
|
+
this.kit.send({ type: 'subagents', conversation_id: conversation.id, subagents });
|
|
500
752
|
}
|
|
501
753
|
commands(conversation, commands) {
|
|
502
754
|
conversation.commands = commands;
|
|
@@ -507,6 +759,7 @@ export class Conversations {
|
|
|
507
759
|
options(conversation, options) {
|
|
508
760
|
if (conversation.running === null)
|
|
509
761
|
return;
|
|
762
|
+
conversation.options = options;
|
|
510
763
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
511
764
|
}
|
|
512
765
|
deliver(conversation, turn, event, body) {
|
|
@@ -521,7 +774,7 @@ export class Conversations {
|
|
|
521
774
|
return delivered;
|
|
522
775
|
}
|
|
523
776
|
write(conversation, turn, operation) {
|
|
524
|
-
if (
|
|
777
|
+
if (turn.ended || !this.watched)
|
|
525
778
|
return;
|
|
526
779
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
527
780
|
}
|
|
@@ -531,13 +784,9 @@ export class Conversations {
|
|
|
531
784
|
}
|
|
532
785
|
begin(conversation) {
|
|
533
786
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
534
|
-
conversation.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
turn.admitted = true;
|
|
538
|
-
this.replay(conversation, turn);
|
|
539
|
-
})
|
|
540
|
-
.catch(logged);
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
789
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
541
790
|
conversation.turn = turn;
|
|
542
791
|
if (conversation.context !== null)
|
|
543
792
|
turn.parts.measure(conversation.context);
|
|
@@ -558,7 +807,8 @@ export class Conversations {
|
|
|
558
807
|
if (notice !== null)
|
|
559
808
|
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
560
809
|
turn.ended = true;
|
|
561
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
562
812
|
if (conversation.turn === turn)
|
|
563
813
|
conversation.turn = null;
|
|
564
814
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
@@ -570,12 +820,14 @@ export class Conversations {
|
|
|
570
820
|
this.next(conversation);
|
|
571
821
|
this.kit.changed();
|
|
572
822
|
}
|
|
573
|
-
prompt(conversation, prompt) {
|
|
823
|
+
prompt(conversation, prompt, taken) {
|
|
574
824
|
const running = conversation.running;
|
|
575
825
|
const turn = this.begin(conversation);
|
|
576
826
|
turn.prompted = true;
|
|
827
|
+
turn.taken = taken;
|
|
577
828
|
conversation.prompted = running.sessionId;
|
|
578
829
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
830
|
+
this.took(turn);
|
|
579
831
|
const notice = answeredFailure(answer);
|
|
580
832
|
if (notice !== null) {
|
|
581
833
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -588,8 +840,9 @@ export class Conversations {
|
|
|
588
840
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
589
841
|
}
|
|
590
842
|
else {
|
|
591
|
-
|
|
592
|
-
|
|
843
|
+
this.took(turn);
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
845
|
+
running.adapter.retire();
|
|
593
846
|
this.detach(conversation, running);
|
|
594
847
|
}
|
|
595
848
|
this.end(conversation, turn, answeredWith(error));
|
|
@@ -597,21 +850,26 @@ export class Conversations {
|
|
|
597
850
|
});
|
|
598
851
|
}
|
|
599
852
|
started(conversation) {
|
|
600
|
-
|
|
853
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
854
|
+
this.took(turn);
|
|
855
|
+
return turn;
|
|
601
856
|
}
|
|
602
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
603
858
|
const turn = conversation.turn;
|
|
604
859
|
if (turn === null)
|
|
605
860
|
return;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
this.apply(conversation, turn, event);
|
|
612
|
-
conversation.turn = null;
|
|
613
|
-
this.kit.changed();
|
|
861
|
+
const held = stop !== null && !turn.steered && [...conversation.subagents.values()].some((subagent) => subagent.turn === turn);
|
|
862
|
+
if (!turn.prompted || held) {
|
|
863
|
+
this.took(turn);
|
|
864
|
+
this.end(conversation, turn, stop === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
865
|
+
return;
|
|
614
866
|
}
|
|
867
|
+
if (stop !== null)
|
|
868
|
+
return;
|
|
869
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
870
|
+
this.apply(conversation, turn, event);
|
|
871
|
+
conversation.turn = null;
|
|
872
|
+
this.kit.changed();
|
|
615
873
|
}
|
|
616
874
|
async ask(conversation, method, params, secret, signal) {
|
|
617
875
|
const turn = this.started(conversation);
|
|
@@ -667,7 +925,7 @@ export class Conversations {
|
|
|
667
925
|
this.started(conversation);
|
|
668
926
|
return;
|
|
669
927
|
case 'ended':
|
|
670
|
-
this.ended(conversation);
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
671
929
|
return;
|
|
672
930
|
case 'commands':
|
|
673
931
|
this.commands(conversation, event.commands);
|
|
@@ -682,6 +940,9 @@ export class Conversations {
|
|
|
682
940
|
case 'subagent-done':
|
|
683
941
|
this.subagentDone(conversation, event.id, event.result);
|
|
684
942
|
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
685
946
|
case 'marker':
|
|
686
947
|
case 'image':
|
|
687
948
|
if (conversation.turn !== null)
|
|
@@ -692,6 +953,11 @@ export class Conversations {
|
|
|
692
953
|
}
|
|
693
954
|
this.apply(conversation, this.started(conversation), event);
|
|
694
955
|
}
|
|
956
|
+
took(turn) {
|
|
957
|
+
const taken = turn.taken;
|
|
958
|
+
turn.taken = null;
|
|
959
|
+
taken?.();
|
|
960
|
+
}
|
|
695
961
|
apply(conversation, turn, event) {
|
|
696
962
|
switch (event.kind) {
|
|
697
963
|
case 'text':
|
|
@@ -703,7 +969,8 @@ export class Conversations {
|
|
|
703
969
|
return;
|
|
704
970
|
case 'subagent': {
|
|
705
971
|
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
706
|
-
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
972
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
973
|
+
this.subagentsChanged(conversation);
|
|
707
974
|
return;
|
|
708
975
|
}
|
|
709
976
|
case 'message':
|
|
@@ -726,6 +993,7 @@ export class Conversations {
|
|
|
726
993
|
if (subagent === undefined)
|
|
727
994
|
return;
|
|
728
995
|
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
729
997
|
if (subagent.turn === conversation.turn) {
|
|
730
998
|
subagent.turn.parts.done(subagent.index, result);
|
|
731
999
|
return;
|
|
@@ -736,23 +1004,37 @@ export class Conversations {
|
|
|
736
1004
|
else
|
|
737
1005
|
conversation.turn.parts.start(part);
|
|
738
1006
|
}
|
|
1007
|
+
subagentActivity(conversation, id, activity) {
|
|
1008
|
+
const subagent = conversation.subagents.get(id);
|
|
1009
|
+
if (subagent === undefined || subagent.activity === activity)
|
|
1010
|
+
return;
|
|
1011
|
+
subagent.activity = activity;
|
|
1012
|
+
this.subagentsChanged(conversation);
|
|
1013
|
+
}
|
|
739
1014
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
740
1015
|
const bytes = Buffer.from(data, 'base64');
|
|
741
1016
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
742
1018
|
try {
|
|
743
1019
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
744
1020
|
name,
|
|
745
1021
|
media_type: mediaType,
|
|
746
1022
|
bytes: bytes.length,
|
|
747
|
-
sha256
|
|
1023
|
+
sha256,
|
|
748
1024
|
}));
|
|
749
|
-
const saved =
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1025
|
+
const saved = await uploaded(declared.upload, {
|
|
1026
|
+
name,
|
|
1027
|
+
bytes: bytes.length,
|
|
1028
|
+
sha256,
|
|
1029
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
1030
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
1031
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
1032
|
+
});
|
|
1033
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
753
1034
|
}
|
|
754
1035
|
catch (error) {
|
|
755
|
-
|
|
1036
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
1037
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
756
1038
|
}
|
|
757
1039
|
}
|
|
758
1040
|
}
|