@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf268dff1869b
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 +38 -37
- package/dist/acp.js +90 -19
- package/dist/agents.js +155 -45
- package/dist/api.js +5 -1
- package/dist/app-git.js +10 -26
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +4 -7
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +52 -42
- package/dist/connection.js +105 -0
- package/dist/conversations.js +526 -175
- package/dist/copies.js +89 -52
- package/dist/end.js +17 -2
- package/dist/events.js +33 -8
- package/dist/files.js +139 -31
- package/dist/git.js +13 -11
- package/dist/home.js +56 -12
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +7 -206
- package/dist/instructions.js +7 -11
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +49 -31
- package/dist/logout.js +0 -2
- package/dist/mcp.js +6 -2
- package/dist/placement.js +31 -0
- package/dist/relay.js +22 -1
- package/dist/resident.js +97 -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 -39
- package/dist/silence.js +28 -0
- package/dist/skills.js +49 -35
- package/dist/transcription.js +85 -45
- package/dist/translate.js +2 -8
- package/dist/update.js +113 -105
- package/dist/wire.js +40 -0
- package/dist/work.js +3 -5
- package/package.json +20 -20
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/stream.js +0 -74
- package/how-we-work.md +0 -23
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -3,39 +3,116 @@ 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';
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal, HouseRefusal } from './api.js';
|
|
7
8
|
import { openBridge } from './bridge.js';
|
|
8
9
|
import { CLIS } from './clis.js';
|
|
9
|
-
import { readerFor } from './events.js';
|
|
10
|
-
import {
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
14
17
|
import { killMarked, Scope } from './scope.js';
|
|
15
18
|
import { holdSecretInput } from './secret-input.js';
|
|
16
|
-
import { createHowWeWork } from './skills.js';
|
|
17
19
|
import { transcribe } from './transcription.js';
|
|
18
20
|
import { workFor } from './work.js';
|
|
19
21
|
const KILLED = 'the conversation was killed';
|
|
22
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
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
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
37
|
+
class NotStarted extends Error {
|
|
38
|
+
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
41
|
+
async function step(reason, work) {
|
|
42
|
+
try {
|
|
43
|
+
return await work;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function 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', 'done', 'option', 'answer']);
|
|
20
66
|
function causeOf(error) {
|
|
21
67
|
return error instanceof Error ? error.message : String(error);
|
|
22
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
if (running.killed)
|
|
74
|
+
return { failure: 'killed', cause: KILLED };
|
|
75
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
76
|
+
}
|
|
23
77
|
function answerMessage(request, response) {
|
|
24
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
25
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
|
+
}
|
|
26
86
|
function logged(error) {
|
|
27
87
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
28
88
|
}
|
|
89
|
+
function refusal(conversation, error) {
|
|
90
|
+
if (conversation.kills > 0)
|
|
91
|
+
return KILLED;
|
|
92
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
93
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
94
|
+
return causeOf(error);
|
|
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
|
+
}
|
|
29
102
|
function marker(bridge) {
|
|
30
103
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
104
|
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
107
|
+
}
|
|
32
108
|
class Turn {
|
|
33
109
|
id = randomUUID();
|
|
34
110
|
parts;
|
|
35
|
-
admitted = false;
|
|
36
111
|
questions = 0;
|
|
37
112
|
secrets = 0;
|
|
38
113
|
prompted = false;
|
|
114
|
+
steered = false;
|
|
115
|
+
taken = null;
|
|
39
116
|
ended = false;
|
|
40
117
|
images = 0;
|
|
41
118
|
uploads = [];
|
|
@@ -49,20 +126,31 @@ class Conversation {
|
|
|
49
126
|
session = null;
|
|
50
127
|
turn = null;
|
|
51
128
|
commands = null;
|
|
129
|
+
options = null;
|
|
130
|
+
exitUntold = false;
|
|
52
131
|
context = null;
|
|
53
132
|
leftovers = new Set();
|
|
54
133
|
waiting = [];
|
|
134
|
+
done = false;
|
|
55
135
|
sending = null;
|
|
56
136
|
subagents = new Map();
|
|
57
137
|
held = [];
|
|
58
138
|
opening = null;
|
|
139
|
+
bridge = null;
|
|
140
|
+
launch = null;
|
|
141
|
+
prompted = null;
|
|
59
142
|
kills = 0;
|
|
60
143
|
killed = new AbortController();
|
|
61
144
|
interrupts = 0;
|
|
62
145
|
interrupted = new AbortController();
|
|
63
|
-
|
|
146
|
+
turns = new Set();
|
|
64
147
|
queue = Promise.resolve();
|
|
65
148
|
reports = Promise.resolve();
|
|
149
|
+
starting = null;
|
|
150
|
+
preparing = null;
|
|
151
|
+
told = null;
|
|
152
|
+
untold = false;
|
|
153
|
+
shown = null;
|
|
66
154
|
constructor(id) {
|
|
67
155
|
this.id = id;
|
|
68
156
|
}
|
|
@@ -78,9 +166,7 @@ function asksSecret(request) {
|
|
|
78
166
|
export class Conversations {
|
|
79
167
|
kit;
|
|
80
168
|
conversations = new Map();
|
|
81
|
-
acks = new Map();
|
|
82
169
|
questions = new Map();
|
|
83
|
-
carrying = 0;
|
|
84
170
|
reporting = 0;
|
|
85
171
|
watched = false;
|
|
86
172
|
constructor(kit) {
|
|
@@ -88,43 +174,32 @@ export class Conversations {
|
|
|
88
174
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
89
175
|
setInterval(() => this.look(), 2_000);
|
|
90
176
|
}
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
conversation.queue = handled.catch(() => undefined);
|
|
109
|
-
const work = handled.then(({ ack }) => ack);
|
|
110
|
-
this.acks.set(input.input_id, work);
|
|
111
|
-
return work;
|
|
112
|
-
})();
|
|
113
|
-
carried
|
|
114
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
115
|
-
.catch(logged)
|
|
116
|
-
.finally(() => {
|
|
117
|
-
this.carrying--;
|
|
118
|
-
this.kit.changed();
|
|
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') {
|
|
181
|
+
conversation.kills++;
|
|
182
|
+
conversation.killed.abort(new Error(KILLED));
|
|
183
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
184
|
+
waiting.settle({ refused: KILLED });
|
|
185
|
+
this.halt(conversation);
|
|
186
|
+
}
|
|
187
|
+
if (call.kind === 'interrupt') {
|
|
188
|
+
conversation.interrupts++;
|
|
189
|
+
conversation.interrupted.abort();
|
|
190
|
+
}
|
|
191
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
192
|
+
conversation.queue = handled.catch(() => undefined);
|
|
193
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
119
194
|
});
|
|
120
195
|
}
|
|
121
196
|
idle() {
|
|
122
|
-
if (this.
|
|
197
|
+
if (this.reporting > 0)
|
|
123
198
|
return false;
|
|
124
199
|
for (const conversation of this.conversations.values()) {
|
|
125
200
|
const turn = conversation.turn;
|
|
126
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
127
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
128
203
|
return false;
|
|
129
204
|
}
|
|
130
205
|
const conversations = [...this.conversations.values()];
|
|
@@ -133,7 +208,7 @@ export class Conversations {
|
|
|
133
208
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
134
209
|
}
|
|
135
210
|
quiet() {
|
|
136
|
-
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
137
212
|
}
|
|
138
213
|
look() {
|
|
139
214
|
for (const conversation of this.conversations.values()) {
|
|
@@ -148,6 +223,16 @@ export class Conversations {
|
|
|
148
223
|
}
|
|
149
224
|
opened() {
|
|
150
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
|
+
}
|
|
151
236
|
}
|
|
152
237
|
watching(watched) {
|
|
153
238
|
const resend = watched && !this.watched;
|
|
@@ -167,130 +252,320 @@ export class Conversations {
|
|
|
167
252
|
}
|
|
168
253
|
return conversation;
|
|
169
254
|
}
|
|
170
|
-
async carry(conversation,
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
173
|
-
if (
|
|
174
|
-
return this.message(conversation,
|
|
175
|
-
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') {
|
|
176
267
|
if (--conversation.interrupts === 0)
|
|
177
268
|
conversation.interrupted = new AbortController();
|
|
178
|
-
await this.interrupt(conversation, String(
|
|
269
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
179
270
|
}
|
|
180
|
-
if (
|
|
271
|
+
if (call.kind === 'kill') {
|
|
181
272
|
await this.kill(conversation);
|
|
182
273
|
await conversation.sending;
|
|
183
274
|
if (--conversation.kills === 0)
|
|
184
275
|
conversation.killed = new AbortController();
|
|
185
276
|
}
|
|
186
|
-
if (
|
|
187
|
-
await this.option(conversation, String(
|
|
188
|
-
if (
|
|
189
|
-
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));
|
|
190
281
|
if (question === undefined) {
|
|
191
|
-
const text = answerMessage(
|
|
192
|
-
return this.message(conversation, { ...
|
|
282
|
+
const text = answerMessage(call.request, call.response);
|
|
283
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
193
284
|
}
|
|
194
|
-
question.answer(
|
|
285
|
+
question.answer(call.response);
|
|
195
286
|
}
|
|
196
|
-
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);
|
|
197
328
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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;
|
|
336
|
+
}
|
|
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;
|
|
201
344
|
try {
|
|
202
|
-
|
|
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);
|
|
203
352
|
}
|
|
204
353
|
catch (error) {
|
|
205
|
-
|
|
354
|
+
this.closeIdleBridge(conversation);
|
|
355
|
+
this.prepares(conversation, null);
|
|
356
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
206
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 };
|
|
207
367
|
}
|
|
208
|
-
async
|
|
209
|
-
|
|
210
|
-
|
|
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;
|
|
211
388
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
218
|
-
if (conversation.kills > 0)
|
|
219
|
-
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
|
+
}));
|
|
220
394
|
}
|
|
221
395
|
catch (error) {
|
|
222
|
-
|
|
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 })) });
|
|
223
412
|
}
|
|
224
|
-
return {
|
|
225
|
-
ack: new Promise((settle) => {
|
|
226
|
-
conversation.waiting.push({ input, route, content, settle });
|
|
227
|
-
this.next(conversation);
|
|
228
|
-
}),
|
|
229
|
-
};
|
|
230
413
|
}
|
|
231
414
|
next(conversation) {
|
|
232
|
-
if (conversation.sending !== null || conversation.
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
233
416
|
return;
|
|
234
417
|
const waiting = conversation.waiting.shift();
|
|
235
|
-
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);
|
|
236
424
|
return;
|
|
425
|
+
}
|
|
237
426
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
238
427
|
conversation.sending = null;
|
|
239
428
|
this.next(conversation);
|
|
240
429
|
});
|
|
241
430
|
}
|
|
242
431
|
async send(conversation, waiting) {
|
|
243
|
-
const {
|
|
244
|
-
let
|
|
432
|
+
const { call } = waiting;
|
|
433
|
+
let result = {};
|
|
434
|
+
let started = false;
|
|
435
|
+
let instructing = waiting.instructing;
|
|
245
436
|
const prompt = [];
|
|
246
437
|
try {
|
|
247
438
|
if (conversation.running === null) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (resumed === null)
|
|
251
|
-
ack = { provider_session_id: opened };
|
|
439
|
+
this.starts(conversation, 'session');
|
|
440
|
+
await this.launched(conversation);
|
|
252
441
|
}
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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;
|
|
449
|
+
}
|
|
450
|
+
const session = conversation.running.sessionId;
|
|
451
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
452
|
+
result = { provider_session_id: session };
|
|
453
|
+
}
|
|
454
|
+
if (call.first === true) {
|
|
455
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
258
456
|
}
|
|
259
457
|
if (conversation.kills > 0)
|
|
260
458
|
throw new Error(KILLED);
|
|
261
459
|
}
|
|
262
460
|
catch (error) {
|
|
263
|
-
|
|
461
|
+
this.starts(conversation, null);
|
|
462
|
+
if (started) {
|
|
264
463
|
conversation.session = null;
|
|
265
464
|
await this.kill(conversation);
|
|
266
465
|
}
|
|
267
|
-
|
|
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 });
|
|
268
469
|
return;
|
|
269
470
|
}
|
|
270
|
-
this.
|
|
271
|
-
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);
|
|
272
474
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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}` });
|
|
288
531
|
}
|
|
289
532
|
}
|
|
290
|
-
|
|
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);
|
|
291
562
|
}
|
|
292
563
|
async interrupt(conversation, turnId) {
|
|
293
|
-
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)
|
|
294
569
|
return;
|
|
295
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
296
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -342,63 +617,78 @@ export class Conversations {
|
|
|
342
617
|
}
|
|
343
618
|
return route;
|
|
344
619
|
}
|
|
345
|
-
async start(conversation,
|
|
620
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
621
|
+
const route = await routed;
|
|
346
622
|
const reader = readerFor(route.kind);
|
|
347
623
|
reader.session = sessionId;
|
|
348
624
|
const app = client({ name: '@agentshouse/kit' })
|
|
349
625
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
350
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
626
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
627
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
351
628
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
352
|
-
const
|
|
629
|
+
const bridging = this.bridged(conversation);
|
|
630
|
+
const bridge = await step(unbridged, bridging);
|
|
353
631
|
if (conversation.kills > 0) {
|
|
354
|
-
|
|
632
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
355
633
|
throw new Error(KILLED);
|
|
356
634
|
}
|
|
357
635
|
conversation.opening = bridge;
|
|
358
636
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
359
637
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
638
|
+
let adapter = null;
|
|
360
639
|
try {
|
|
361
|
-
|
|
640
|
+
bridged(bridge);
|
|
362
641
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
363
|
-
const
|
|
642
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
643
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
364
644
|
work.read(message);
|
|
365
645
|
this.read(conversation, reader, message);
|
|
366
|
-
}, { ...bridge.env, ...
|
|
367
|
-
void adapter.exited.then(() =>
|
|
646
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
647
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
368
648
|
const agent = adapter.connection.agent;
|
|
369
649
|
const cwd = route.working_directory;
|
|
370
650
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
651
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
652
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
374
653
|
reader.session = opened.sessionId;
|
|
375
|
-
if (sessionId
|
|
654
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
376
655
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
377
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
656
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
378
657
|
}
|
|
379
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
380
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
658
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
659
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
381
660
|
void work.settled().then(() => scope.record());
|
|
382
661
|
conversation.running = running;
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
383
663
|
conversation.session = running.sessionId;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.options(conversation, options);
|
|
388
|
-
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));
|
|
389
667
|
return running.sessionId;
|
|
390
668
|
}
|
|
391
669
|
catch (error) {
|
|
392
|
-
|
|
670
|
+
adapter?.retire();
|
|
393
671
|
work.close();
|
|
394
672
|
conversation.leftovers.add(scope);
|
|
395
|
-
|
|
673
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
396
674
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
397
675
|
}
|
|
398
676
|
finally {
|
|
399
677
|
conversation.opening = null;
|
|
400
678
|
}
|
|
401
679
|
}
|
|
680
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
681
|
+
try {
|
|
682
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
686
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
687
|
+
}
|
|
688
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
402
692
|
async launchSettings(running, route, offered) {
|
|
403
693
|
let options = offered;
|
|
404
694
|
const settings = [
|
|
@@ -420,27 +710,45 @@ export class Conversations {
|
|
|
420
710
|
}
|
|
421
711
|
return options;
|
|
422
712
|
}
|
|
423
|
-
exited(conversation,
|
|
424
|
-
const running = conversation.running;
|
|
425
|
-
if (running?.adapter !== adapter)
|
|
426
|
-
return;
|
|
713
|
+
exited(conversation, running, cause) {
|
|
427
714
|
this.detach(conversation, running);
|
|
428
715
|
if (conversation.turn !== null) {
|
|
429
|
-
this.end(conversation, conversation.turn,
|
|
716
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
430
717
|
}
|
|
431
718
|
this.next(conversation);
|
|
432
719
|
this.kit.changed();
|
|
433
720
|
}
|
|
434
721
|
detach(conversation, running) {
|
|
722
|
+
if (conversation.bridge === running.bridging)
|
|
723
|
+
conversation.bridge = null;
|
|
435
724
|
running.work.close();
|
|
436
725
|
conversation.leftovers.add(running.scope);
|
|
437
726
|
conversation.running = null;
|
|
438
727
|
conversation.commands = null;
|
|
728
|
+
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
439
733
|
for (const [id, question] of this.questions) {
|
|
440
734
|
if (question.conversation === conversation)
|
|
441
735
|
this.questions.delete(id);
|
|
442
736
|
}
|
|
443
|
-
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 });
|
|
444
752
|
}
|
|
445
753
|
commands(conversation, commands) {
|
|
446
754
|
conversation.commands = commands;
|
|
@@ -451,6 +759,7 @@ export class Conversations {
|
|
|
451
759
|
options(conversation, options) {
|
|
452
760
|
if (conversation.running === null)
|
|
453
761
|
return;
|
|
762
|
+
conversation.options = options;
|
|
454
763
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
455
764
|
}
|
|
456
765
|
deliver(conversation, turn, event, body) {
|
|
@@ -465,7 +774,7 @@ export class Conversations {
|
|
|
465
774
|
return delivered;
|
|
466
775
|
}
|
|
467
776
|
write(conversation, turn, operation) {
|
|
468
|
-
if (
|
|
777
|
+
if (turn.ended || !this.watched)
|
|
469
778
|
return;
|
|
470
779
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
471
780
|
}
|
|
@@ -475,13 +784,9 @@ export class Conversations {
|
|
|
475
784
|
}
|
|
476
785
|
begin(conversation) {
|
|
477
786
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
478
|
-
conversation.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
turn.admitted = true;
|
|
482
|
-
this.replay(conversation, turn);
|
|
483
|
-
})
|
|
484
|
-
.catch(logged);
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
789
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
485
790
|
conversation.turn = turn;
|
|
486
791
|
if (conversation.context !== null)
|
|
487
792
|
turn.parts.measure(conversation.context);
|
|
@@ -490,59 +795,81 @@ export class Conversations {
|
|
|
490
795
|
this.kit.changed();
|
|
491
796
|
return turn;
|
|
492
797
|
}
|
|
493
|
-
end(conversation, turn, failed) {
|
|
798
|
+
end(conversation, turn, failed, notice = null) {
|
|
494
799
|
if (turn.ended)
|
|
495
800
|
return;
|
|
801
|
+
if (failed !== null)
|
|
802
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
496
803
|
if (conversation.turn === turn) {
|
|
497
804
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
498
805
|
this.apply(conversation, turn, event);
|
|
499
806
|
}
|
|
807
|
+
if (notice !== null)
|
|
808
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
500
809
|
turn.ended = true;
|
|
501
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
502
812
|
if (conversation.turn === turn)
|
|
503
813
|
conversation.turn = null;
|
|
504
814
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
505
815
|
parts: turn.parts.saved(),
|
|
506
816
|
context: turn.parts.context,
|
|
507
|
-
...failed,
|
|
817
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
508
818
|
}));
|
|
509
819
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
510
820
|
this.next(conversation);
|
|
511
821
|
this.kit.changed();
|
|
512
822
|
}
|
|
513
|
-
prompt(conversation, prompt) {
|
|
823
|
+
prompt(conversation, prompt, taken) {
|
|
514
824
|
const running = conversation.running;
|
|
515
825
|
const turn = this.begin(conversation);
|
|
516
826
|
turn.prompted = true;
|
|
517
|
-
|
|
827
|
+
turn.taken = taken;
|
|
828
|
+
conversation.prompted = running.sessionId;
|
|
829
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
830
|
+
this.took(turn);
|
|
831
|
+
const notice = answeredFailure(answer);
|
|
832
|
+
if (notice !== null) {
|
|
833
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
837
|
+
}
|
|
838
|
+
}, async (error) => {
|
|
518
839
|
if (running.adapter.connection.signal.aborted) {
|
|
519
|
-
this.end(conversation, turn,
|
|
840
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
520
841
|
}
|
|
521
842
|
else {
|
|
522
|
-
|
|
523
|
-
|
|
843
|
+
this.took(turn);
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
845
|
+
running.adapter.retire();
|
|
524
846
|
this.detach(conversation, running);
|
|
525
847
|
}
|
|
526
|
-
this.end(conversation, turn,
|
|
848
|
+
this.end(conversation, turn, answeredWith(error));
|
|
527
849
|
}
|
|
528
850
|
});
|
|
529
851
|
}
|
|
530
852
|
started(conversation) {
|
|
531
|
-
|
|
853
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
854
|
+
this.took(turn);
|
|
855
|
+
return turn;
|
|
532
856
|
}
|
|
533
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
534
858
|
const turn = conversation.turn;
|
|
535
859
|
if (turn === null)
|
|
536
860
|
return;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
this.apply(conversation, turn, event);
|
|
543
|
-
conversation.turn = null;
|
|
544
|
-
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;
|
|
545
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();
|
|
546
873
|
}
|
|
547
874
|
async ask(conversation, method, params, secret, signal) {
|
|
548
875
|
const turn = this.started(conversation);
|
|
@@ -598,7 +925,7 @@ export class Conversations {
|
|
|
598
925
|
this.started(conversation);
|
|
599
926
|
return;
|
|
600
927
|
case 'ended':
|
|
601
|
-
this.ended(conversation);
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
602
929
|
return;
|
|
603
930
|
case 'commands':
|
|
604
931
|
this.commands(conversation, event.commands);
|
|
@@ -613,6 +940,9 @@ export class Conversations {
|
|
|
613
940
|
case 'subagent-done':
|
|
614
941
|
this.subagentDone(conversation, event.id, event.result);
|
|
615
942
|
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
616
946
|
case 'marker':
|
|
617
947
|
case 'image':
|
|
618
948
|
if (conversation.turn !== null)
|
|
@@ -623,6 +953,11 @@ export class Conversations {
|
|
|
623
953
|
}
|
|
624
954
|
this.apply(conversation, this.started(conversation), event);
|
|
625
955
|
}
|
|
956
|
+
took(turn) {
|
|
957
|
+
const taken = turn.taken;
|
|
958
|
+
turn.taken = null;
|
|
959
|
+
taken?.();
|
|
960
|
+
}
|
|
626
961
|
apply(conversation, turn, event) {
|
|
627
962
|
switch (event.kind) {
|
|
628
963
|
case 'text':
|
|
@@ -634,7 +969,8 @@ export class Conversations {
|
|
|
634
969
|
return;
|
|
635
970
|
case 'subagent': {
|
|
636
971
|
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
637
|
-
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);
|
|
638
974
|
return;
|
|
639
975
|
}
|
|
640
976
|
case 'message':
|
|
@@ -657,6 +993,7 @@ export class Conversations {
|
|
|
657
993
|
if (subagent === undefined)
|
|
658
994
|
return;
|
|
659
995
|
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
660
997
|
if (subagent.turn === conversation.turn) {
|
|
661
998
|
subagent.turn.parts.done(subagent.index, result);
|
|
662
999
|
return;
|
|
@@ -667,23 +1004,37 @@ export class Conversations {
|
|
|
667
1004
|
else
|
|
668
1005
|
conversation.turn.parts.start(part);
|
|
669
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
|
+
}
|
|
670
1014
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
671
1015
|
const bytes = Buffer.from(data, 'base64');
|
|
672
1016
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
673
1018
|
try {
|
|
674
1019
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
675
1020
|
name,
|
|
676
1021
|
media_type: mediaType,
|
|
677
1022
|
bytes: bytes.length,
|
|
678
|
-
sha256
|
|
1023
|
+
sha256,
|
|
679
1024
|
}));
|
|
680
|
-
const saved =
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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);
|
|
684
1034
|
}
|
|
685
1035
|
catch (error) {
|
|
686
|
-
|
|
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);
|
|
687
1038
|
}
|
|
688
1039
|
}
|
|
689
1040
|
}
|