@agentshouse/kit 0.2.1-cbdf76b9a90a3 → 0.2.1-cbff58027f078
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 +58 -7
- package/dist/afk.js +17 -0
- package/dist/agents.js +55 -32
- package/dist/api.js +1 -1
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chunks.js +49 -0
- package/dist/clis.js +45 -40
- package/dist/{stream.js → connection.js} +28 -46
- package/dist/conversations.js +330 -125
- package/dist/copies.js +286 -149
- package/dist/end.js +11 -5
- package/dist/events.js +66 -8
- package/dist/files.js +139 -37
- package/dist/git.js +47 -13
- 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/parts.js +10 -0
- package/dist/placement.js +10 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +49 -38
- package/dist/scope.js +12 -4
- package/dist/scripts.js +255 -0
- package/dist/service.js +1 -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/update.js +113 -124
- package/dist/wire.js +23 -5
- package/package.json +20 -21
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/inputs.js +0 -63
- 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
|
@@ -3,23 +3,24 @@ 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 {
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
|
+
import { formAnswered, grokQuestionAnswered, permissionAllowed, PLAN_APPROVED } from './afk.js';
|
|
8
|
+
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
9
|
import { openBridge } from './bridge.js';
|
|
9
10
|
import { CLIS } from './clis.js';
|
|
10
11
|
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
-
import {
|
|
12
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
12
13
|
import { agentBase } from './home.js';
|
|
13
14
|
import { instructions } from './instructions.js';
|
|
14
|
-
import { Parts } from './parts.js';
|
|
15
|
+
import { addedTokens, Parts } from './parts.js';
|
|
15
16
|
import { refusalOf } from './refusals.js';
|
|
17
|
+
import { relayed } from './relay.js';
|
|
16
18
|
import { killMarked, Scope } from './scope.js';
|
|
17
19
|
import { holdSecretInput } from './secret-input.js';
|
|
18
|
-
import { createHowWeWork } from './skills.js';
|
|
19
20
|
import { transcribe } from './transcription.js';
|
|
20
21
|
import { workFor } from './work.js';
|
|
21
|
-
const SIGNED_OUT = RequestError.authRequired().code;
|
|
22
22
|
const KILLED = 'the conversation was killed';
|
|
23
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
23
24
|
const REFUSED = 'the CLI refused the prompt';
|
|
24
25
|
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
26
|
const GONE = 'House no longer holds this conversation; start a new one';
|
|
@@ -33,8 +34,13 @@ const NO_SESSION = "the Agent's CLI did not open a session";
|
|
|
33
34
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
35
|
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
35
36
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
37
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
38
|
+
// A reader tells a working run from a stopped one by the minute, and House keeps no finer figure, so a run reports no oftener.
|
|
39
|
+
const ACTIVITY_REPORT_MS = 60_000;
|
|
36
40
|
class NotStarted extends Error {
|
|
37
41
|
}
|
|
42
|
+
class ResumeRefused extends NotStarted {
|
|
43
|
+
}
|
|
38
44
|
async function step(reason, work) {
|
|
39
45
|
try {
|
|
40
46
|
return await work;
|
|
@@ -43,9 +49,6 @@ async function step(reason, work) {
|
|
|
43
49
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
|
-
function signedOut(error) {
|
|
47
|
-
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
-
}
|
|
49
52
|
function unopened(reason) {
|
|
50
53
|
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
54
|
}
|
|
@@ -62,7 +65,12 @@ const EXIT_PLAN_MODE = {
|
|
|
62
65
|
return value;
|
|
63
66
|
},
|
|
64
67
|
};
|
|
65
|
-
|
|
68
|
+
const GROK_QUESTION = {
|
|
69
|
+
parse(value) {
|
|
70
|
+
return value;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'done', 'option', 'answer']);
|
|
66
74
|
function causeOf(error) {
|
|
67
75
|
return error instanceof Error ? error.message : String(error);
|
|
68
76
|
}
|
|
@@ -70,32 +78,51 @@ function answeredWith(error) {
|
|
|
70
78
|
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
79
|
}
|
|
72
80
|
function exitedWith(running, cause) {
|
|
73
|
-
|
|
81
|
+
if (running.killed)
|
|
82
|
+
return { failure: 'killed', cause: KILLED };
|
|
83
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
74
84
|
}
|
|
75
85
|
function answerMessage(request, response) {
|
|
76
86
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
77
87
|
}
|
|
88
|
+
async function sentPart(url, bytes, headers) {
|
|
89
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
90
|
+
await answer.arrayBuffer();
|
|
91
|
+
if (!answer.ok)
|
|
92
|
+
throw storeRefusal('upload', answer.status);
|
|
93
|
+
}
|
|
78
94
|
function logged(error) {
|
|
79
95
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
80
96
|
}
|
|
81
97
|
function refusal(conversation, error) {
|
|
82
98
|
if (conversation.kills > 0)
|
|
83
99
|
return KILLED;
|
|
84
|
-
const cause = error instanceof
|
|
100
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
85
101
|
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
86
102
|
return causeOf(error);
|
|
87
103
|
}
|
|
104
|
+
function audioOf(result) {
|
|
105
|
+
if (result.isError === true)
|
|
106
|
+
return null;
|
|
107
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
108
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
109
|
+
}
|
|
88
110
|
function marker(bridge) {
|
|
89
111
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
90
112
|
}
|
|
113
|
+
function steers(adapter) {
|
|
114
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
115
|
+
}
|
|
91
116
|
class Turn {
|
|
92
117
|
id = randomUUID();
|
|
93
118
|
parts;
|
|
94
119
|
questions = 0;
|
|
95
120
|
secrets = 0;
|
|
96
121
|
prompted = false;
|
|
122
|
+
steered = false;
|
|
97
123
|
taken = null;
|
|
98
124
|
ended = false;
|
|
125
|
+
quietUntil = Infinity;
|
|
99
126
|
images = 0;
|
|
100
127
|
uploads = [];
|
|
101
128
|
constructor(emit) {
|
|
@@ -110,20 +137,24 @@ class Conversation {
|
|
|
110
137
|
commands = null;
|
|
111
138
|
options = null;
|
|
112
139
|
exitUntold = false;
|
|
140
|
+
afk = false;
|
|
113
141
|
context = null;
|
|
142
|
+
tokens = null;
|
|
114
143
|
leftovers = new Set();
|
|
115
144
|
waiting = [];
|
|
145
|
+
done = false;
|
|
116
146
|
sending = null;
|
|
117
147
|
subagents = new Map();
|
|
118
148
|
held = [];
|
|
119
149
|
opening = null;
|
|
150
|
+
bridge = null;
|
|
120
151
|
launch = null;
|
|
121
152
|
prompted = null;
|
|
122
153
|
kills = 0;
|
|
123
154
|
killed = new AbortController();
|
|
124
155
|
interrupts = 0;
|
|
125
156
|
interrupted = new AbortController();
|
|
126
|
-
|
|
157
|
+
turns = new Set();
|
|
127
158
|
queue = Promise.resolve();
|
|
128
159
|
reports = Promise.resolve();
|
|
129
160
|
starting = null;
|
|
@@ -154,23 +185,25 @@ export class Conversations {
|
|
|
154
185
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
155
186
|
setInterval(() => this.look(), 2_000);
|
|
156
187
|
}
|
|
157
|
-
|
|
158
|
-
void this.kit.
|
|
159
|
-
const conversation = this.conversation(
|
|
160
|
-
if (
|
|
188
|
+
call(call) {
|
|
189
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
190
|
+
const conversation = this.conversation(call.conversation_id);
|
|
191
|
+
if (call.kind === 'open' || call.kind === 'message')
|
|
192
|
+
conversation.afk = call.afk === true;
|
|
193
|
+
if (call.kind === 'kill') {
|
|
161
194
|
conversation.kills++;
|
|
162
195
|
conversation.killed.abort(new Error(KILLED));
|
|
163
196
|
for (const waiting of conversation.waiting.splice(0))
|
|
164
197
|
waiting.settle({ refused: KILLED });
|
|
165
198
|
this.halt(conversation);
|
|
166
199
|
}
|
|
167
|
-
if (
|
|
200
|
+
if (call.kind === 'interrupt') {
|
|
168
201
|
conversation.interrupts++;
|
|
169
202
|
conversation.interrupted.abort();
|
|
170
203
|
}
|
|
171
|
-
const handled = conversation.queue.then(() => this.carry(conversation,
|
|
204
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
172
205
|
conversation.queue = handled.catch(() => undefined);
|
|
173
|
-
return handled.then(async ({
|
|
206
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
174
207
|
});
|
|
175
208
|
}
|
|
176
209
|
idle() {
|
|
@@ -179,7 +212,7 @@ export class Conversations {
|
|
|
179
212
|
for (const conversation of this.conversations.values()) {
|
|
180
213
|
const turn = conversation.turn;
|
|
181
214
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
182
|
-
if (conversation.
|
|
215
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
183
216
|
return false;
|
|
184
217
|
}
|
|
185
218
|
const conversations = [...this.conversations.values()];
|
|
@@ -188,7 +221,7 @@ export class Conversations {
|
|
|
188
221
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
189
222
|
}
|
|
190
223
|
quiet() {
|
|
191
|
-
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.
|
|
224
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
192
225
|
}
|
|
193
226
|
look() {
|
|
194
227
|
for (const conversation of this.conversations.values()) {
|
|
@@ -204,8 +237,10 @@ export class Conversations {
|
|
|
204
237
|
opened() {
|
|
205
238
|
this.watched = false;
|
|
206
239
|
for (const conversation of this.conversations.values()) {
|
|
207
|
-
if (conversation.running !== null)
|
|
240
|
+
if (conversation.running !== null) {
|
|
208
241
|
this.facts(conversation);
|
|
242
|
+
this.subagentsChanged(conversation);
|
|
243
|
+
}
|
|
209
244
|
else if (conversation.exitUntold)
|
|
210
245
|
this.process(conversation, false);
|
|
211
246
|
if (conversation.untold || conversation.told !== null)
|
|
@@ -230,40 +265,51 @@ export class Conversations {
|
|
|
230
265
|
}
|
|
231
266
|
return conversation;
|
|
232
267
|
}
|
|
233
|
-
async carry(conversation,
|
|
234
|
-
if (
|
|
235
|
-
return
|
|
236
|
-
if (
|
|
237
|
-
return this.message(conversation,
|
|
268
|
+
async carry(conversation, call) {
|
|
269
|
+
if (call.kind === 'open')
|
|
270
|
+
return this.open(conversation, call);
|
|
271
|
+
if (call.kind === 'message')
|
|
272
|
+
return this.message(conversation, call);
|
|
273
|
+
if (call.kind === 'done') {
|
|
274
|
+
conversation.done = true;
|
|
275
|
+
this.next(conversation);
|
|
276
|
+
return { result: Promise.resolve({}) };
|
|
277
|
+
}
|
|
238
278
|
await this.launched(conversation);
|
|
239
|
-
if (
|
|
279
|
+
if (call.kind === 'interrupt') {
|
|
240
280
|
if (--conversation.interrupts === 0)
|
|
241
281
|
conversation.interrupted = new AbortController();
|
|
242
|
-
await this.interrupt(conversation, String(
|
|
282
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
243
283
|
}
|
|
244
|
-
if (
|
|
284
|
+
if (call.kind === 'kill') {
|
|
245
285
|
await this.kill(conversation);
|
|
246
286
|
await conversation.sending;
|
|
247
287
|
if (--conversation.kills === 0)
|
|
248
288
|
conversation.killed = new AbortController();
|
|
249
289
|
}
|
|
250
|
-
if (
|
|
251
|
-
await this.option(conversation, String(
|
|
252
|
-
if (
|
|
253
|
-
const question = this.questions.get(String(
|
|
290
|
+
if (call.kind === 'option')
|
|
291
|
+
await this.option(conversation, String(call.option), call.value);
|
|
292
|
+
if (call.kind === 'answer') {
|
|
293
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
254
294
|
if (question === undefined) {
|
|
255
|
-
const text = answerMessage(
|
|
256
|
-
return this.message(conversation, { ...
|
|
295
|
+
const text = answerMessage(call.request, call.response);
|
|
296
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
257
297
|
}
|
|
258
|
-
question.answer(
|
|
298
|
+
question.answer(call.response);
|
|
259
299
|
}
|
|
260
|
-
return {
|
|
300
|
+
return { result: Promise.resolve({}) };
|
|
261
301
|
}
|
|
262
|
-
open(conversation,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
302
|
+
async open(conversation, call) {
|
|
303
|
+
await this.stop(conversation);
|
|
304
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
305
|
+
return {
|
|
306
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async stop(conversation) {
|
|
310
|
+
const running = conversation.running;
|
|
311
|
+
running?.scope.kill();
|
|
312
|
+
await running?.adapter.exited;
|
|
267
313
|
}
|
|
268
314
|
launch(conversation, route, sessionId, empty) {
|
|
269
315
|
let opened;
|
|
@@ -283,6 +329,7 @@ export class Conversations {
|
|
|
283
329
|
if (conversation.launch !== launch)
|
|
284
330
|
return;
|
|
285
331
|
conversation.launch = null;
|
|
332
|
+
this.closeIdleBridge(conversation);
|
|
286
333
|
this.next(conversation);
|
|
287
334
|
});
|
|
288
335
|
this.next(conversation);
|
|
@@ -300,44 +347,93 @@ export class Conversations {
|
|
|
300
347
|
read.catch(() => undefined);
|
|
301
348
|
return read;
|
|
302
349
|
}
|
|
303
|
-
async message(conversation,
|
|
304
|
-
const content = [
|
|
350
|
+
async message(conversation, call) {
|
|
351
|
+
const content = [
|
|
352
|
+
...(typeof call.recent === 'string' ? [{ type: 'text', text: call.recent }] : []),
|
|
353
|
+
{ type: 'text', text: String(call.text) },
|
|
354
|
+
];
|
|
305
355
|
let route;
|
|
306
356
|
let instructing = null;
|
|
307
357
|
try {
|
|
308
|
-
route = await this.route(conversation,
|
|
309
|
-
if (
|
|
358
|
+
route = await this.route(conversation, call.agent_id);
|
|
359
|
+
if (call.first === true)
|
|
310
360
|
instructing = this.instructed(conversation, route);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
this.prepares(conversation, 'files');
|
|
314
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
315
|
-
const lines = await this.transcribed(conversation, files, paths);
|
|
316
|
-
if (lines.length > 0)
|
|
317
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
361
|
+
if (call.message_id !== null)
|
|
362
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
318
363
|
if (conversation.kills > 0)
|
|
319
364
|
throw new Error(KILLED);
|
|
320
365
|
}
|
|
321
366
|
catch (error) {
|
|
367
|
+
this.closeIdleBridge(conversation);
|
|
322
368
|
this.prepares(conversation, null);
|
|
323
|
-
return {
|
|
369
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
324
370
|
}
|
|
325
371
|
conversation.preparing = null;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
372
|
+
const { promise, resolve: settle } = Promise.withResolvers();
|
|
373
|
+
const waiting = { call, route, content, instructing, settle };
|
|
374
|
+
if (call.at_once === true && conversation.turns.size > 0)
|
|
375
|
+
await this.atOnce(conversation, waiting);
|
|
376
|
+
else
|
|
377
|
+
conversation.waiting.push(waiting);
|
|
378
|
+
this.next(conversation);
|
|
379
|
+
return { result: promise };
|
|
380
|
+
}
|
|
381
|
+
async atOnce(conversation, waiting) {
|
|
382
|
+
while (conversation.sending !== null)
|
|
383
|
+
await conversation.sending;
|
|
384
|
+
const running = conversation.running;
|
|
385
|
+
if (running !== null && conversation.turns.size > 0 && steers(running.adapter)) {
|
|
386
|
+
conversation.sending = this.steer(conversation, running, waiting);
|
|
387
|
+
await conversation.sending;
|
|
388
|
+
conversation.sending = null;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
this.ahead(conversation, waiting);
|
|
392
|
+
if (conversation.turns.size > 0)
|
|
393
|
+
await this.cancel(conversation);
|
|
394
|
+
}
|
|
395
|
+
ahead(conversation, waiting) {
|
|
396
|
+
const behind = conversation.waiting.findIndex((held) => held.call.at_once !== true);
|
|
397
|
+
conversation.waiting.splice(behind === -1 ? conversation.waiting.length : behind, 0, waiting);
|
|
398
|
+
}
|
|
399
|
+
async steer(conversation, running, waiting) {
|
|
400
|
+
let outcome;
|
|
401
|
+
try {
|
|
402
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
403
|
+
sessionId: running.sessionId,
|
|
404
|
+
prompt: waiting.content,
|
|
405
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
outcome = error;
|
|
410
|
+
}
|
|
411
|
+
if (outcome === 'promptRequired') {
|
|
412
|
+
this.ahead(conversation, waiting);
|
|
413
|
+
}
|
|
414
|
+
else if (outcome === 'injected' || outcome === 'startedNewTurn') {
|
|
415
|
+
if (outcome === 'injected') {
|
|
416
|
+
for (const turn of conversation.turns)
|
|
417
|
+
turn.steered ||= turn.prompted;
|
|
418
|
+
}
|
|
419
|
+
this.kit.calls.keepResult(String(waiting.call.input_id), {});
|
|
420
|
+
waiting.settle({});
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const cause = outcome instanceof Error ? outcome : new Error(`the CLI answered ${JSON.stringify(outcome)}`);
|
|
424
|
+
waiting.settle({ refused: refusal(conversation, new NotStarted(UNSTEERED, { cause })) });
|
|
425
|
+
}
|
|
332
426
|
}
|
|
333
427
|
next(conversation) {
|
|
334
|
-
if (conversation.sending !== null || conversation.
|
|
428
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
335
429
|
return;
|
|
336
430
|
const waiting = conversation.waiting.shift();
|
|
337
431
|
if (waiting === undefined) {
|
|
338
432
|
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
433
|
if (conversation.starting !== step)
|
|
340
434
|
this.starts(conversation, step);
|
|
435
|
+
if (conversation.done && conversation.launch === null)
|
|
436
|
+
void this.stop(conversation).catch(logged);
|
|
341
437
|
return;
|
|
342
438
|
}
|
|
343
439
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
@@ -346,8 +442,8 @@ export class Conversations {
|
|
|
346
442
|
});
|
|
347
443
|
}
|
|
348
444
|
async send(conversation, waiting) {
|
|
349
|
-
const {
|
|
350
|
-
let
|
|
445
|
+
const { call } = waiting;
|
|
446
|
+
let result = {};
|
|
351
447
|
let started = false;
|
|
352
448
|
let instructing = waiting.instructing;
|
|
353
449
|
const prompt = [];
|
|
@@ -357,18 +453,18 @@ export class Conversations {
|
|
|
357
453
|
await this.launched(conversation);
|
|
358
454
|
}
|
|
359
455
|
if (conversation.running === null) {
|
|
360
|
-
const stored = conversation.session ??
|
|
361
|
-
const empty =
|
|
456
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
457
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
362
458
|
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
-
if (
|
|
459
|
+
if (call.first === true)
|
|
364
460
|
instructing = this.instructed(conversation, waiting.route);
|
|
365
461
|
started = (await launch.session) !== stored;
|
|
366
462
|
}
|
|
367
463
|
const session = conversation.running.sessionId;
|
|
368
|
-
if (started || (
|
|
369
|
-
|
|
464
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
465
|
+
result = { provider_session_id: session };
|
|
370
466
|
}
|
|
371
|
-
if (
|
|
467
|
+
if (call.first === true) {
|
|
372
468
|
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
373
469
|
}
|
|
374
470
|
if (conversation.kills > 0)
|
|
@@ -380,12 +476,14 @@ export class Conversations {
|
|
|
380
476
|
conversation.session = null;
|
|
381
477
|
await this.kill(conversation);
|
|
382
478
|
}
|
|
383
|
-
|
|
479
|
+
const refused = refusal(conversation, error);
|
|
480
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
481
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
384
482
|
return;
|
|
385
483
|
}
|
|
386
484
|
this.starts(conversation, null);
|
|
387
|
-
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.
|
|
388
|
-
waiting.settle(
|
|
485
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
486
|
+
waiting.settle(result);
|
|
389
487
|
}
|
|
390
488
|
starts(conversation, step) {
|
|
391
489
|
conversation.starting = step;
|
|
@@ -415,28 +513,72 @@ export class Conversations {
|
|
|
415
513
|
retell(conversation) {
|
|
416
514
|
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
417
515
|
}
|
|
418
|
-
async transcribed(conversation,
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
516
|
+
async transcribed(conversation, messageId, text) {
|
|
517
|
+
const paths = linkedPaths(text);
|
|
518
|
+
if (paths.length === 0)
|
|
519
|
+
return [];
|
|
520
|
+
const killed = conversation.killed.signal;
|
|
521
|
+
const interrupted = conversation.interrupted.signal;
|
|
522
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
523
|
+
let signal = killed;
|
|
524
|
+
const blocks = [];
|
|
525
|
+
try {
|
|
526
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
527
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
528
|
+
for (const path of paths) {
|
|
529
|
+
let answered = await read(path);
|
|
530
|
+
if (answered === null)
|
|
531
|
+
continue;
|
|
532
|
+
signal = transcribing;
|
|
533
|
+
this.prepares(conversation, 'transcription');
|
|
534
|
+
const said = await transcribe(async () => {
|
|
535
|
+
const address = answered ?? (await read(path));
|
|
536
|
+
answered = null;
|
|
537
|
+
if (address === null)
|
|
538
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
539
|
+
return address;
|
|
540
|
+
}, signal);
|
|
541
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
542
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
543
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
434
544
|
}
|
|
435
545
|
}
|
|
436
|
-
|
|
546
|
+
catch (error) {
|
|
547
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
548
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
return blocks;
|
|
552
|
+
}
|
|
553
|
+
bridged(conversation) {
|
|
554
|
+
if (conversation.bridge === null) {
|
|
555
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
556
|
+
conversation.bridge = bridging;
|
|
557
|
+
bridging.catch(() => {
|
|
558
|
+
if (conversation.bridge === bridging)
|
|
559
|
+
conversation.bridge = null;
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
return conversation.bridge;
|
|
563
|
+
}
|
|
564
|
+
closeBridge(conversation, bridging, bridge) {
|
|
565
|
+
bridge.close();
|
|
566
|
+
if (conversation.bridge === bridging)
|
|
567
|
+
conversation.bridge = null;
|
|
568
|
+
}
|
|
569
|
+
closeIdleBridge(conversation) {
|
|
570
|
+
const bridging = conversation.bridge;
|
|
571
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
572
|
+
return;
|
|
573
|
+
conversation.bridge = null;
|
|
574
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
437
575
|
}
|
|
438
576
|
async interrupt(conversation, turnId) {
|
|
439
|
-
if (conversation.
|
|
577
|
+
if ([...conversation.turns].some((turn) => turn.id === turnId))
|
|
578
|
+
await this.cancel(conversation);
|
|
579
|
+
}
|
|
580
|
+
async cancel(conversation) {
|
|
581
|
+
if (conversation.running === null)
|
|
440
582
|
return;
|
|
441
583
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
442
584
|
sessionId: conversation.running.sessionId,
|
|
@@ -493,13 +635,19 @@ export class Conversations {
|
|
|
493
635
|
const reader = readerFor(route.kind);
|
|
494
636
|
reader.session = sessionId;
|
|
495
637
|
const app = client({ name: '@agentshouse/kit' })
|
|
496
|
-
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
497
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
498
|
-
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal))
|
|
638
|
+
.onRequest('session/request_permission', ({ params, signal }) => conversation.afk ? permissionAllowed(params) : this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
639
|
+
.onRequest('elicitation/create', ({ params, signal }) => conversation.afk ? formAnswered(route.kind, params) : this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
640
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => conversation.afk ? PLAN_APPROVED : this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal))
|
|
641
|
+
.onRequest('_x.ai/ask_user_question', GROK_QUESTION, ({ params }) => {
|
|
642
|
+
if (!conversation.afk)
|
|
643
|
+
throw RequestError.methodNotFound('_x.ai/ask_user_question');
|
|
644
|
+
return grokQuestionAnswered(params);
|
|
645
|
+
});
|
|
499
646
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
500
|
-
const
|
|
647
|
+
const bridging = this.bridged(conversation);
|
|
648
|
+
const bridge = await step(unbridged, bridging);
|
|
501
649
|
if (conversation.kills > 0) {
|
|
502
|
-
|
|
650
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
503
651
|
throw new Error(KILLED);
|
|
504
652
|
}
|
|
505
653
|
conversation.opening = bridge;
|
|
@@ -507,7 +655,6 @@ export class Conversations {
|
|
|
507
655
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
656
|
let adapter = null;
|
|
509
657
|
try {
|
|
510
|
-
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
511
658
|
bridged(bridge);
|
|
512
659
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
513
660
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
@@ -515,7 +662,7 @@ export class Conversations {
|
|
|
515
662
|
work.read(message);
|
|
516
663
|
this.read(conversation, reader, message);
|
|
517
664
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
518
|
-
void adapter.exited.then(() =>
|
|
665
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
519
666
|
const agent = adapter.connection.agent;
|
|
520
667
|
const cwd = route.working_directory;
|
|
521
668
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
@@ -526,10 +673,11 @@ export class Conversations {
|
|
|
526
673
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
527
674
|
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
528
675
|
}
|
|
529
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
676
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
530
677
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
531
678
|
void work.settled().then(() => scope.record());
|
|
532
679
|
conversation.running = running;
|
|
680
|
+
adapter.turn(conversation.turns.size > 0);
|
|
533
681
|
conversation.session = running.sessionId;
|
|
534
682
|
conversation.options = options;
|
|
535
683
|
this.facts(conversation);
|
|
@@ -540,7 +688,7 @@ export class Conversations {
|
|
|
540
688
|
adapter?.retire();
|
|
541
689
|
work.close();
|
|
542
690
|
conversation.leftovers.add(scope);
|
|
543
|
-
|
|
691
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
544
692
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
545
693
|
}
|
|
546
694
|
finally {
|
|
@@ -553,7 +701,7 @@ export class Conversations {
|
|
|
553
701
|
}
|
|
554
702
|
catch (error) {
|
|
555
703
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
556
|
-
throw new NotStarted(
|
|
704
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
557
705
|
}
|
|
558
706
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
559
707
|
return null;
|
|
@@ -589,11 +737,17 @@ export class Conversations {
|
|
|
589
737
|
this.kit.changed();
|
|
590
738
|
}
|
|
591
739
|
detach(conversation, running) {
|
|
740
|
+
if (conversation.bridge === running.bridging)
|
|
741
|
+
conversation.bridge = null;
|
|
592
742
|
running.work.close();
|
|
593
743
|
conversation.leftovers.add(running.scope);
|
|
594
744
|
conversation.running = null;
|
|
595
745
|
conversation.commands = null;
|
|
596
746
|
conversation.options = null;
|
|
747
|
+
if (conversation.subagents.size > 0) {
|
|
748
|
+
conversation.subagents.clear();
|
|
749
|
+
this.subagentsChanged(conversation);
|
|
750
|
+
}
|
|
597
751
|
for (const [id, question] of this.questions) {
|
|
598
752
|
if (question.conversation === conversation)
|
|
599
753
|
this.questions.delete(id);
|
|
@@ -610,6 +764,10 @@ export class Conversations {
|
|
|
610
764
|
if (conversation.options !== null)
|
|
611
765
|
this.options(conversation, conversation.options);
|
|
612
766
|
}
|
|
767
|
+
subagentsChanged(conversation) {
|
|
768
|
+
const subagents = [...conversation.subagents.values()].map(({ description, activity }) => ({ description, activity }));
|
|
769
|
+
this.kit.send({ type: 'subagents', conversation_id: conversation.id, subagents });
|
|
770
|
+
}
|
|
613
771
|
commands(conversation, commands) {
|
|
614
772
|
conversation.commands = commands;
|
|
615
773
|
if (conversation.running === null)
|
|
@@ -622,9 +780,9 @@ export class Conversations {
|
|
|
622
780
|
conversation.options = options;
|
|
623
781
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
624
782
|
}
|
|
625
|
-
deliver(conversation, turn, event, body) {
|
|
783
|
+
deliver(conversation, turn, event, body, current) {
|
|
626
784
|
this.reporting++;
|
|
627
|
-
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
785
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body, current));
|
|
628
786
|
conversation.reports = delivered
|
|
629
787
|
.catch(() => undefined)
|
|
630
788
|
.finally(() => {
|
|
@@ -644,8 +802,14 @@ export class Conversations {
|
|
|
644
802
|
}
|
|
645
803
|
begin(conversation) {
|
|
646
804
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
647
|
-
conversation.
|
|
648
|
-
|
|
805
|
+
conversation.turns.add(turn);
|
|
806
|
+
conversation.running?.adapter.turn(true);
|
|
807
|
+
void this.deliver(conversation, turn, 'started', {})
|
|
808
|
+
.then((answer) => {
|
|
809
|
+
if (answer.run === true)
|
|
810
|
+
turn.quietUntil = Date.now() + ACTIVITY_REPORT_MS;
|
|
811
|
+
})
|
|
812
|
+
.catch(logged);
|
|
649
813
|
conversation.turn = turn;
|
|
650
814
|
if (conversation.context !== null)
|
|
651
815
|
turn.parts.measure(conversation.context);
|
|
@@ -666,12 +830,16 @@ export class Conversations {
|
|
|
666
830
|
if (notice !== null)
|
|
667
831
|
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
668
832
|
turn.ended = true;
|
|
669
|
-
conversation.
|
|
833
|
+
conversation.turns.delete(turn);
|
|
834
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
670
835
|
if (conversation.turn === turn)
|
|
671
836
|
conversation.turn = null;
|
|
837
|
+
const tokens = conversation.tokens;
|
|
838
|
+
conversation.tokens = null;
|
|
672
839
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
673
840
|
parts: turn.parts.saved(),
|
|
674
841
|
context: turn.parts.context,
|
|
842
|
+
...(tokens === null ? {} : { tokens }),
|
|
675
843
|
...(failed === null ? {} : { failed: failed.failure }),
|
|
676
844
|
}));
|
|
677
845
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
@@ -699,7 +867,7 @@ export class Conversations {
|
|
|
699
867
|
}
|
|
700
868
|
else {
|
|
701
869
|
this.took(turn);
|
|
702
|
-
if (conversation.
|
|
870
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
703
871
|
running.adapter.retire();
|
|
704
872
|
this.detach(conversation, running);
|
|
705
873
|
}
|
|
@@ -712,19 +880,22 @@ export class Conversations {
|
|
|
712
880
|
this.took(turn);
|
|
713
881
|
return turn;
|
|
714
882
|
}
|
|
715
|
-
ended(conversation) {
|
|
883
|
+
ended(conversation, stop) {
|
|
716
884
|
const turn = conversation.turn;
|
|
717
885
|
if (turn === null)
|
|
718
886
|
return;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
this.apply(conversation, turn, event);
|
|
725
|
-
conversation.turn = null;
|
|
726
|
-
this.kit.changed();
|
|
887
|
+
const held = stop !== null && !turn.steered && [...conversation.subagents.values()].some((subagent) => subagent.turn === turn);
|
|
888
|
+
if (!turn.prompted || held) {
|
|
889
|
+
this.took(turn);
|
|
890
|
+
this.end(conversation, turn, stop === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
891
|
+
return;
|
|
727
892
|
}
|
|
893
|
+
if (stop !== null)
|
|
894
|
+
return;
|
|
895
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
896
|
+
this.apply(conversation, turn, event);
|
|
897
|
+
conversation.turn = null;
|
|
898
|
+
this.kit.changed();
|
|
728
899
|
}
|
|
729
900
|
async ask(conversation, method, params, secret, signal) {
|
|
730
901
|
const turn = this.started(conversation);
|
|
@@ -773,6 +944,18 @@ export class Conversations {
|
|
|
773
944
|
read(conversation, reader, message) {
|
|
774
945
|
for (const event of reader.read(message))
|
|
775
946
|
this.event(conversation, event);
|
|
947
|
+
this.reportActivity(conversation);
|
|
948
|
+
}
|
|
949
|
+
reportActivity(conversation) {
|
|
950
|
+
const turn = conversation.turn;
|
|
951
|
+
if (turn === null || Date.now() < turn.quietUntil)
|
|
952
|
+
return;
|
|
953
|
+
turn.quietUntil = Infinity;
|
|
954
|
+
void this.deliver(conversation, turn, 'activity', {}, () => false)
|
|
955
|
+
.catch(logged)
|
|
956
|
+
.finally(() => {
|
|
957
|
+
turn.quietUntil = Date.now() + ACTIVITY_REPORT_MS;
|
|
958
|
+
});
|
|
776
959
|
}
|
|
777
960
|
event(conversation, event) {
|
|
778
961
|
switch (event.kind) {
|
|
@@ -780,7 +963,7 @@ export class Conversations {
|
|
|
780
963
|
this.started(conversation);
|
|
781
964
|
return;
|
|
782
965
|
case 'ended':
|
|
783
|
-
this.ended(conversation);
|
|
966
|
+
this.ended(conversation, event.stop);
|
|
784
967
|
return;
|
|
785
968
|
case 'commands':
|
|
786
969
|
this.commands(conversation, event.commands);
|
|
@@ -792,9 +975,15 @@ export class Conversations {
|
|
|
792
975
|
conversation.context = { used: event.used, window: event.window };
|
|
793
976
|
conversation.turn?.parts.measure(conversation.context);
|
|
794
977
|
return;
|
|
978
|
+
case 'tokens':
|
|
979
|
+
conversation.tokens = addedTokens(conversation.tokens, event.tokens);
|
|
980
|
+
return;
|
|
795
981
|
case 'subagent-done':
|
|
796
982
|
this.subagentDone(conversation, event.id, event.result);
|
|
797
983
|
return;
|
|
984
|
+
case 'subagent-activity':
|
|
985
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
986
|
+
return;
|
|
798
987
|
case 'marker':
|
|
799
988
|
case 'image':
|
|
800
989
|
if (conversation.turn !== null)
|
|
@@ -821,7 +1010,8 @@ export class Conversations {
|
|
|
821
1010
|
return;
|
|
822
1011
|
case 'subagent': {
|
|
823
1012
|
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
824
|
-
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
1013
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
1014
|
+
this.subagentsChanged(conversation);
|
|
825
1015
|
return;
|
|
826
1016
|
}
|
|
827
1017
|
case 'message':
|
|
@@ -844,6 +1034,7 @@ export class Conversations {
|
|
|
844
1034
|
if (subagent === undefined)
|
|
845
1035
|
return;
|
|
846
1036
|
conversation.subagents.delete(id);
|
|
1037
|
+
this.subagentsChanged(conversation);
|
|
847
1038
|
if (subagent.turn === conversation.turn) {
|
|
848
1039
|
subagent.turn.parts.done(subagent.index, result);
|
|
849
1040
|
return;
|
|
@@ -854,23 +1045,37 @@ export class Conversations {
|
|
|
854
1045
|
else
|
|
855
1046
|
conversation.turn.parts.start(part);
|
|
856
1047
|
}
|
|
1048
|
+
subagentActivity(conversation, id, activity) {
|
|
1049
|
+
const subagent = conversation.subagents.get(id);
|
|
1050
|
+
if (subagent === undefined || subagent.activity === activity)
|
|
1051
|
+
return;
|
|
1052
|
+
subagent.activity = activity;
|
|
1053
|
+
this.subagentsChanged(conversation);
|
|
1054
|
+
}
|
|
857
1055
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
858
1056
|
const bytes = Buffer.from(data, 'base64');
|
|
859
1057
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1058
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
860
1059
|
try {
|
|
861
1060
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
862
1061
|
name,
|
|
863
1062
|
media_type: mediaType,
|
|
864
1063
|
bytes: bytes.length,
|
|
865
|
-
sha256
|
|
1064
|
+
sha256,
|
|
866
1065
|
}));
|
|
867
|
-
const saved =
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1066
|
+
const saved = await uploaded(declared.upload, {
|
|
1067
|
+
name,
|
|
1068
|
+
bytes: bytes.length,
|
|
1069
|
+
sha256,
|
|
1070
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
1071
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
1072
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
1073
|
+
});
|
|
1074
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
871
1075
|
}
|
|
872
1076
|
catch (error) {
|
|
873
|
-
|
|
1077
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
1078
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
874
1079
|
}
|
|
875
1080
|
}
|
|
876
1081
|
}
|