@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c02dacffca87c
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 +48 -29
- package/dist/abort.js +9 -0
- package/dist/acp.js +102 -88
- package/dist/agents.js +356 -52
- package/dist/api.js +40 -4
- package/dist/app-git.js +48 -0
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +93 -78
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/chunks.js +49 -0
- package/dist/clis.js +224 -88
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +105 -0
- package/dist/conversations.js +749 -270
- package/dist/copies.js +475 -260
- package/dist/end.js +33 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +546 -0
- package/dist/files.js +140 -17
- package/dist/git-main.js +2 -2
- package/dist/git.js +62 -22
- package/dist/home.js +120 -9
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +5 -102
- package/dist/instructions.js +17 -18
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -16
- package/dist/login.js +130 -95
- package/dist/logout.js +19 -0
- package/dist/mcp.js +43 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +82 -0
- package/dist/placement.js +31 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +41 -0
- package/dist/resident.js +144 -52
- package/dist/scope.js +214 -0
- package/dist/scripts.js +255 -0
- package/dist/secret-input.js +7 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +52 -32
- package/dist/silence.js +28 -0
- package/dist/skills.js +48 -39
- package/dist/transcription.js +210 -0
- package/dist/translate.js +2 -8
- package/dist/update.js +127 -0
- package/dist/wire.js +40 -0
- package/dist/work.js +346 -0
- package/package.json +21 -24
- package/dist/blocks.js +0 -51
- package/dist/bootstrap.json +0 -28
- package/dist/stream.js +0 -47
- package/how-we-work.md +0 -25
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/conversations.js
CHANGED
|
@@ -1,65 +1,156 @@
|
|
|
1
1
|
import { client, } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { unlessAborted } from './abort.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 {
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
12
|
+
import { agentBase } from './home.js';
|
|
10
13
|
import { instructions } from './instructions.js';
|
|
14
|
+
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
17
|
+
import { killMarked, Scope } from './scope.js';
|
|
11
18
|
import { holdSecretInput } from './secret-input.js';
|
|
12
|
-
import {
|
|
13
|
-
|
|
19
|
+
import { transcribe } from './transcription.js';
|
|
20
|
+
import { workFor } from './work.js';
|
|
14
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']);
|
|
15
66
|
function causeOf(error) {
|
|
16
67
|
return error instanceof Error ? error.message : String(error);
|
|
17
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
|
+
}
|
|
18
77
|
function answerMessage(request, response) {
|
|
19
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
20
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
|
+
}
|
|
21
86
|
function logged(error) {
|
|
22
87
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
23
88
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
32
101
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
102
|
+
function marker(bridge) {
|
|
103
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
104
|
+
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
35
107
|
}
|
|
36
108
|
class Turn {
|
|
37
109
|
id = randomUUID();
|
|
38
|
-
|
|
39
|
-
sent = [];
|
|
40
|
-
unsentFrom = null;
|
|
41
|
-
draftSequence = 0;
|
|
42
|
-
planSequence = 0;
|
|
43
|
-
entries = null;
|
|
110
|
+
parts;
|
|
44
111
|
questions = 0;
|
|
45
112
|
secrets = 0;
|
|
46
|
-
|
|
113
|
+
prompted = false;
|
|
114
|
+
steered = false;
|
|
115
|
+
taken = null;
|
|
47
116
|
ended = false;
|
|
117
|
+
images = 0;
|
|
118
|
+
uploads = [];
|
|
119
|
+
constructor(emit) {
|
|
120
|
+
this.parts = new Parts(emit);
|
|
121
|
+
}
|
|
48
122
|
}
|
|
49
123
|
class Conversation {
|
|
50
124
|
id;
|
|
51
125
|
running = null;
|
|
126
|
+
session = null;
|
|
52
127
|
turn = null;
|
|
53
128
|
commands = null;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
129
|
+
options = null;
|
|
130
|
+
exitUntold = false;
|
|
131
|
+
context = null;
|
|
132
|
+
leftovers = new Set();
|
|
133
|
+
waiting = [];
|
|
134
|
+
done = false;
|
|
135
|
+
sending = null;
|
|
136
|
+
subagents = new Map();
|
|
137
|
+
held = [];
|
|
57
138
|
opening = null;
|
|
139
|
+
bridge = null;
|
|
140
|
+
launch = null;
|
|
141
|
+
prompted = null;
|
|
58
142
|
kills = 0;
|
|
59
143
|
killed = new AbortController();
|
|
60
|
-
|
|
144
|
+
interrupts = 0;
|
|
145
|
+
interrupted = new AbortController();
|
|
146
|
+
turns = new Set();
|
|
61
147
|
queue = Promise.resolve();
|
|
62
148
|
reports = Promise.resolve();
|
|
149
|
+
starting = null;
|
|
150
|
+
preparing = null;
|
|
151
|
+
told = null;
|
|
152
|
+
untold = false;
|
|
153
|
+
shown = null;
|
|
63
154
|
constructor(id) {
|
|
64
155
|
this.id = id;
|
|
65
156
|
}
|
|
@@ -75,51 +166,82 @@ function asksSecret(request) {
|
|
|
75
166
|
export class Conversations {
|
|
76
167
|
kit;
|
|
77
168
|
conversations = new Map();
|
|
78
|
-
acks = new Map();
|
|
79
169
|
questions = new Map();
|
|
80
|
-
carrying = 0;
|
|
81
170
|
reporting = 0;
|
|
171
|
+
watched = false;
|
|
82
172
|
constructor(kit) {
|
|
83
173
|
this.kit = kit;
|
|
174
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
175
|
+
setInterval(() => this.look(), 2_000);
|
|
84
176
|
}
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
conversation.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.then((
|
|
102
|
-
.catch(logged)
|
|
103
|
-
.finally(() => {
|
|
104
|
-
this.carrying--;
|
|
105
|
-
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 }));
|
|
106
194
|
});
|
|
107
195
|
}
|
|
108
196
|
idle() {
|
|
109
|
-
if (this.
|
|
197
|
+
if (this.reporting > 0)
|
|
110
198
|
return false;
|
|
111
199
|
for (const conversation of this.conversations.values()) {
|
|
112
200
|
const turn = conversation.turn;
|
|
113
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
114
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
115
203
|
return false;
|
|
116
204
|
}
|
|
117
|
-
|
|
205
|
+
const conversations = [...this.conversations.values()];
|
|
206
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
207
|
+
return false;
|
|
208
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
209
|
+
}
|
|
210
|
+
quiet() {
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
212
|
+
}
|
|
213
|
+
look() {
|
|
214
|
+
for (const conversation of this.conversations.values()) {
|
|
215
|
+
for (const scope of conversation.leftovers) {
|
|
216
|
+
if (!scope.empty())
|
|
217
|
+
continue;
|
|
218
|
+
scope.close();
|
|
219
|
+
conversation.leftovers.delete(scope);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.kit.changed();
|
|
118
223
|
}
|
|
119
224
|
opened() {
|
|
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
|
+
}
|
|
236
|
+
}
|
|
237
|
+
watching(watched) {
|
|
238
|
+
const resend = watched && !this.watched;
|
|
239
|
+
this.watched = watched;
|
|
240
|
+
if (!resend)
|
|
241
|
+
return;
|
|
120
242
|
for (const conversation of this.conversations.values()) {
|
|
121
243
|
if (conversation.turn !== null)
|
|
122
|
-
conversation.turn
|
|
244
|
+
this.replay(conversation, conversation.turn);
|
|
123
245
|
}
|
|
124
246
|
}
|
|
125
247
|
conversation(id) {
|
|
@@ -130,72 +252,320 @@ export class Conversations {
|
|
|
130
252
|
}
|
|
131
253
|
return conversation;
|
|
132
254
|
}
|
|
133
|
-
async carry(conversation,
|
|
134
|
-
if (
|
|
135
|
-
return this.open(conversation,
|
|
136
|
-
if (
|
|
137
|
-
return this.message(conversation,
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
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') {
|
|
267
|
+
if (--conversation.interrupts === 0)
|
|
268
|
+
conversation.interrupted = new AbortController();
|
|
269
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
270
|
+
}
|
|
271
|
+
if (call.kind === 'kill') {
|
|
141
272
|
await this.kill(conversation);
|
|
273
|
+
await conversation.sending;
|
|
142
274
|
if (--conversation.kills === 0)
|
|
143
275
|
conversation.killed = new AbortController();
|
|
144
276
|
}
|
|
145
|
-
if (
|
|
146
|
-
await this.option(conversation, String(
|
|
147
|
-
if (
|
|
148
|
-
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));
|
|
149
281
|
if (question === undefined) {
|
|
150
|
-
|
|
282
|
+
const text = answerMessage(call.request, call.response);
|
|
283
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
151
284
|
}
|
|
152
|
-
question.answer(
|
|
285
|
+
question.answer(call.response);
|
|
153
286
|
}
|
|
154
|
-
return {};
|
|
287
|
+
return { result: Promise.resolve({}) };
|
|
288
|
+
}
|
|
289
|
+
async open(conversation, call) {
|
|
290
|
+
await this.stop(conversation);
|
|
291
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
292
|
+
return {
|
|
293
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async stop(conversation) {
|
|
297
|
+
const running = conversation.running;
|
|
298
|
+
running?.scope.kill();
|
|
299
|
+
await running?.adapter.exited;
|
|
300
|
+
}
|
|
301
|
+
launch(conversation, route, sessionId, empty) {
|
|
302
|
+
let opened;
|
|
303
|
+
let failed;
|
|
304
|
+
const bridge = new Promise((resolve, reject) => {
|
|
305
|
+
opened = resolve;
|
|
306
|
+
failed = reject;
|
|
307
|
+
});
|
|
308
|
+
bridge.catch(() => undefined);
|
|
309
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
310
|
+
session.catch(failed);
|
|
311
|
+
const launch = { bridge, session };
|
|
312
|
+
conversation.launch = launch;
|
|
313
|
+
void session
|
|
314
|
+
.catch(() => undefined)
|
|
315
|
+
.then(() => {
|
|
316
|
+
if (conversation.launch !== launch)
|
|
317
|
+
return;
|
|
318
|
+
conversation.launch = null;
|
|
319
|
+
this.closeIdleBridge(conversation);
|
|
320
|
+
this.next(conversation);
|
|
321
|
+
});
|
|
322
|
+
this.next(conversation);
|
|
323
|
+
return launch;
|
|
324
|
+
}
|
|
325
|
+
async launched(conversation) {
|
|
326
|
+
while (conversation.launch !== null)
|
|
327
|
+
await conversation.launch.session.catch(() => undefined);
|
|
328
|
+
}
|
|
329
|
+
instructed(conversation, route) {
|
|
330
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
331
|
+
if (bridge === undefined)
|
|
332
|
+
return null;
|
|
333
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
334
|
+
read.catch(() => undefined);
|
|
335
|
+
return read;
|
|
155
336
|
}
|
|
156
|
-
async
|
|
157
|
-
|
|
158
|
-
|
|
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;
|
|
159
344
|
try {
|
|
160
|
-
|
|
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);
|
|
161
352
|
}
|
|
162
353
|
catch (error) {
|
|
163
|
-
|
|
354
|
+
this.closeIdleBridge(conversation);
|
|
355
|
+
this.prepares(conversation, null);
|
|
356
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
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 };
|
|
367
|
+
}
|
|
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;
|
|
164
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);
|
|
165
385
|
}
|
|
166
|
-
async
|
|
167
|
-
let
|
|
386
|
+
async steer(conversation, running, waiting) {
|
|
387
|
+
let outcome;
|
|
388
|
+
try {
|
|
389
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
390
|
+
sessionId: running.sessionId,
|
|
391
|
+
prompt: waiting.content,
|
|
392
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
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 })) });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
next(conversation) {
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
416
|
+
return;
|
|
417
|
+
const waiting = conversation.waiting.shift();
|
|
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);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
427
|
+
conversation.sending = null;
|
|
428
|
+
this.next(conversation);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async send(conversation, waiting) {
|
|
432
|
+
const { call } = waiting;
|
|
433
|
+
let result = {};
|
|
434
|
+
let started = false;
|
|
435
|
+
let instructing = waiting.instructing;
|
|
168
436
|
const prompt = [];
|
|
169
437
|
try {
|
|
170
|
-
const route = await this.route(conversation, input.agent_id);
|
|
171
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, input.files, conversation.killed.signal);
|
|
172
438
|
if (conversation.running === null) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
439
|
+
this.starts(conversation, 'session');
|
|
440
|
+
await this.launched(conversation);
|
|
441
|
+
}
|
|
442
|
+
if (conversation.running === null) {
|
|
443
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
444
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
445
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
446
|
+
if (call.first === true)
|
|
447
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
448
|
+
started = (await launch.session) !== stored;
|
|
449
|
+
}
|
|
450
|
+
const session = conversation.running.sessionId;
|
|
451
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
452
|
+
result = { provider_session_id: session };
|
|
176
453
|
}
|
|
177
|
-
if (
|
|
178
|
-
prompt.push({
|
|
179
|
-
type: 'text',
|
|
180
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
181
|
-
});
|
|
454
|
+
if (call.first === true) {
|
|
455
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
182
456
|
}
|
|
183
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
184
|
-
if (paths.length > 0)
|
|
185
|
-
prompt.push({ type: 'text', text: paths.join('\n') });
|
|
186
457
|
if (conversation.kills > 0)
|
|
187
458
|
throw new Error(KILLED);
|
|
188
459
|
}
|
|
189
460
|
catch (error) {
|
|
190
|
-
|
|
461
|
+
this.starts(conversation, null);
|
|
462
|
+
if (started) {
|
|
463
|
+
conversation.session = null;
|
|
191
464
|
await this.kill(conversation);
|
|
192
|
-
|
|
465
|
+
}
|
|
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 });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
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);
|
|
474
|
+
}
|
|
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}` });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
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
|
+
});
|
|
193
548
|
}
|
|
194
|
-
|
|
195
|
-
|
|
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);
|
|
196
562
|
}
|
|
197
563
|
async interrupt(conversation, turnId) {
|
|
198
|
-
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)
|
|
199
569
|
return;
|
|
200
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
201
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -203,11 +573,13 @@ export class Conversations {
|
|
|
203
573
|
}
|
|
204
574
|
halt(conversation) {
|
|
205
575
|
if (conversation.opening !== null)
|
|
206
|
-
|
|
576
|
+
killMarked(marker(conversation.opening));
|
|
577
|
+
for (const scope of conversation.leftovers)
|
|
578
|
+
scope.kill();
|
|
207
579
|
if (conversation.running === null)
|
|
208
580
|
return;
|
|
209
581
|
conversation.running.killed = true;
|
|
210
|
-
|
|
582
|
+
conversation.running.scope.kill();
|
|
211
583
|
}
|
|
212
584
|
async kill(conversation) {
|
|
213
585
|
const running = conversation.running;
|
|
@@ -219,6 +591,7 @@ export class Conversations {
|
|
|
219
591
|
if (running === null)
|
|
220
592
|
return;
|
|
221
593
|
try {
|
|
594
|
+
running.reader.requested(option, value);
|
|
222
595
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
223
596
|
sessionId: running.sessionId,
|
|
224
597
|
configId: option,
|
|
@@ -227,73 +600,99 @@ export class Conversations {
|
|
|
227
600
|
this.options(conversation, answered.configOptions);
|
|
228
601
|
}
|
|
229
602
|
catch (error) {
|
|
603
|
+
running.reader.refused(option);
|
|
230
604
|
logged(error);
|
|
231
605
|
}
|
|
232
606
|
}
|
|
233
607
|
async route(conversation, agentId) {
|
|
234
608
|
const signal = conversation.killed.signal;
|
|
235
|
-
await
|
|
609
|
+
await unlessAborted(this.kit.agents.read(), signal);
|
|
236
610
|
if (this.kit.agents.route(agentId) === undefined)
|
|
237
|
-
await
|
|
611
|
+
await unlessAborted(this.kit.agents.refresh(), signal);
|
|
238
612
|
const route = this.kit.agents.route(agentId);
|
|
239
613
|
if (route === undefined)
|
|
240
614
|
throw new Error(`this Environment hosts no Agent ${agentId}`);
|
|
241
|
-
if (route.working_directory === join(
|
|
615
|
+
if (route.working_directory === join(agentBase(), route.agent_id)) {
|
|
242
616
|
await mkdir(route.working_directory, { recursive: true });
|
|
243
617
|
}
|
|
244
618
|
return route;
|
|
245
619
|
}
|
|
246
|
-
async start(conversation,
|
|
620
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
621
|
+
const route = await routed;
|
|
622
|
+
const reader = readerFor(route.kind);
|
|
623
|
+
reader.session = sessionId;
|
|
247
624
|
const app = client({ name: '@agentshouse/kit' })
|
|
248
|
-
.onNotification('session/update', ({ params }) => this.update(conversation, params))
|
|
249
|
-
.onNotification(TURN_STARTED, (params) => params, () => void this.started(conversation))
|
|
250
|
-
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
251
625
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
252
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
253
|
-
|
|
254
|
-
const
|
|
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));
|
|
628
|
+
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
629
|
+
const bridging = this.bridged(conversation);
|
|
630
|
+
const bridge = await step(unbridged, bridging);
|
|
255
631
|
if (conversation.kills > 0) {
|
|
256
|
-
|
|
632
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
257
633
|
throw new Error(KILLED);
|
|
258
634
|
}
|
|
259
635
|
conversation.opening = bridge;
|
|
636
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
637
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
638
|
+
let adapter = null;
|
|
260
639
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
640
|
+
bridged(bridge);
|
|
641
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
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) => {
|
|
644
|
+
work.read(message);
|
|
645
|
+
this.read(conversation, reader, message);
|
|
646
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
647
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
264
648
|
const agent = adapter.connection.agent;
|
|
265
649
|
const cwd = route.working_directory;
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
sessionId: opened.sessionId,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
650
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
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 })));
|
|
653
|
+
reader.session = opened.sessionId;
|
|
654
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
655
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
656
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
657
|
+
}
|
|
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 ?? []));
|
|
660
|
+
void work.settled().then(() => scope.record());
|
|
277
661
|
conversation.running = running;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.
|
|
282
|
-
void adapter.
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
663
|
+
conversation.session = running.sessionId;
|
|
664
|
+
conversation.options = options;
|
|
665
|
+
this.facts(conversation);
|
|
666
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
283
667
|
return running.sessionId;
|
|
284
668
|
}
|
|
285
669
|
catch (error) {
|
|
286
|
-
|
|
287
|
-
|
|
670
|
+
adapter?.retire();
|
|
671
|
+
work.close();
|
|
672
|
+
conversation.leftovers.add(scope);
|
|
673
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
288
674
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
289
675
|
}
|
|
290
676
|
finally {
|
|
291
677
|
conversation.opening = null;
|
|
292
678
|
}
|
|
293
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
|
+
}
|
|
294
692
|
async launchSettings(running, route, offered) {
|
|
295
693
|
let options = offered;
|
|
296
694
|
const settings = [
|
|
695
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
297
696
|
['model', route.model],
|
|
298
697
|
['thought_level', route.effort],
|
|
299
698
|
];
|
|
@@ -301,6 +700,7 @@ export class Conversations {
|
|
|
301
700
|
if (value === null)
|
|
302
701
|
continue;
|
|
303
702
|
const option = options.find((candidate) => candidate.category === category);
|
|
703
|
+
running.reader.requested(option?.id ?? category, value);
|
|
304
704
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
305
705
|
sessionId: running.sessionId,
|
|
306
706
|
configId: option?.id ?? category,
|
|
@@ -310,23 +710,45 @@ export class Conversations {
|
|
|
310
710
|
}
|
|
311
711
|
return options;
|
|
312
712
|
}
|
|
313
|
-
exited(conversation,
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
713
|
+
exited(conversation, running, cause) {
|
|
714
|
+
this.detach(conversation, running);
|
|
715
|
+
if (conversation.turn !== null) {
|
|
716
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
717
|
+
}
|
|
718
|
+
this.next(conversation);
|
|
719
|
+
this.kit.changed();
|
|
720
|
+
}
|
|
721
|
+
detach(conversation, running) {
|
|
722
|
+
if (conversation.bridge === running.bridging)
|
|
723
|
+
conversation.bridge = null;
|
|
724
|
+
running.work.close();
|
|
725
|
+
conversation.leftovers.add(running.scope);
|
|
317
726
|
conversation.running = null;
|
|
318
727
|
conversation.commands = null;
|
|
319
|
-
conversation.
|
|
320
|
-
conversation.
|
|
728
|
+
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
321
733
|
for (const [id, question] of this.questions) {
|
|
322
734
|
if (question.conversation === conversation)
|
|
323
735
|
this.questions.delete(id);
|
|
324
736
|
}
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
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 });
|
|
330
752
|
}
|
|
331
753
|
commands(conversation, commands) {
|
|
332
754
|
conversation.commands = commands;
|
|
@@ -337,11 +759,12 @@ export class Conversations {
|
|
|
337
759
|
options(conversation, options) {
|
|
338
760
|
if (conversation.running === null)
|
|
339
761
|
return;
|
|
762
|
+
conversation.options = options;
|
|
340
763
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
341
764
|
}
|
|
342
765
|
deliver(conversation, turn, event, body) {
|
|
343
766
|
this.reporting++;
|
|
344
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
767
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
345
768
|
conversation.reports = delivered
|
|
346
769
|
.catch(() => undefined)
|
|
347
770
|
.finally(() => {
|
|
@@ -350,108 +773,103 @@ export class Conversations {
|
|
|
350
773
|
});
|
|
351
774
|
return delivered;
|
|
352
775
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
.then(() => this.admitted(conversation, turn))
|
|
358
|
-
.catch(logged);
|
|
359
|
-
return turn;
|
|
776
|
+
write(conversation, turn, operation) {
|
|
777
|
+
if (turn.ended || !this.watched)
|
|
778
|
+
return;
|
|
779
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
360
780
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.draft(conversation, turn);
|
|
365
|
-
}
|
|
366
|
-
if (turn.entries !== null)
|
|
367
|
-
this.plan(conversation, turn);
|
|
781
|
+
replay(conversation, turn) {
|
|
782
|
+
for (const operation of turn.parts.operations())
|
|
783
|
+
this.write(conversation, turn, operation);
|
|
368
784
|
}
|
|
369
785
|
begin(conversation) {
|
|
370
|
-
const turn = this.
|
|
786
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
789
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
371
790
|
conversation.turn = turn;
|
|
791
|
+
if (conversation.context !== null)
|
|
792
|
+
turn.parts.measure(conversation.context);
|
|
793
|
+
for (const part of conversation.held.splice(0))
|
|
794
|
+
turn.parts.start(part);
|
|
372
795
|
this.kit.changed();
|
|
373
796
|
return turn;
|
|
374
797
|
}
|
|
375
|
-
end(conversation, turn,
|
|
798
|
+
end(conversation, turn, failed, notice = null) {
|
|
376
799
|
if (turn.ended)
|
|
377
800
|
return;
|
|
801
|
+
if (failed !== null)
|
|
802
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
803
|
+
if (conversation.turn === turn) {
|
|
804
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
805
|
+
this.apply(conversation, turn, event);
|
|
806
|
+
}
|
|
807
|
+
if (notice !== null)
|
|
808
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
378
809
|
turn.ended = true;
|
|
379
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
380
812
|
if (conversation.turn === turn)
|
|
381
813
|
conversation.turn = null;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
814
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
815
|
+
parts: turn.parts.saved(),
|
|
816
|
+
context: turn.parts.context,
|
|
817
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
818
|
+
}));
|
|
819
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
820
|
+
this.next(conversation);
|
|
386
821
|
this.kit.changed();
|
|
387
822
|
}
|
|
388
|
-
|
|
389
|
-
if (conversation.open > 0)
|
|
390
|
-
conversation.late.push(cause);
|
|
391
|
-
else
|
|
392
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
393
|
-
}
|
|
394
|
-
prompt(conversation, prompt) {
|
|
823
|
+
prompt(conversation, prompt, taken) {
|
|
395
824
|
const running = conversation.running;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
else
|
|
407
|
-
this.end(conversation,
|
|
825
|
+
const turn = this.begin(conversation);
|
|
826
|
+
turn.prompted = true;
|
|
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) => {
|
|
839
|
+
if (running.adapter.connection.signal.aborted) {
|
|
840
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
this.took(turn);
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
845
|
+
running.adapter.retire();
|
|
846
|
+
this.detach(conversation, running);
|
|
847
|
+
}
|
|
848
|
+
this.end(conversation, turn, answeredWith(error));
|
|
408
849
|
}
|
|
409
850
|
});
|
|
410
851
|
}
|
|
411
|
-
attach(turn, sent) {
|
|
412
|
-
turn.prompt = sent;
|
|
413
|
-
sent.turn = turn;
|
|
414
|
-
}
|
|
415
|
-
answered(conversation, sent, failed) {
|
|
416
|
-
const turn = sent.turn;
|
|
417
|
-
if (turn === null) {
|
|
418
|
-
const queued = conversation.queued.indexOf(sent);
|
|
419
|
-
if (queued < 0)
|
|
420
|
-
return;
|
|
421
|
-
conversation.queued.splice(queued, 1);
|
|
422
|
-
if (failed !== null)
|
|
423
|
-
this.failed(conversation, failed);
|
|
424
|
-
}
|
|
425
|
-
else if (failed !== null) {
|
|
426
|
-
if (turn.ended)
|
|
427
|
-
this.failed(conversation, failed);
|
|
428
|
-
else
|
|
429
|
-
this.end(conversation, turn, { failed });
|
|
430
|
-
}
|
|
431
|
-
else if (turn.prompt === sent) {
|
|
432
|
-
this.end(conversation, turn, { text: turn.text });
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
852
|
started(conversation) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const turn = this.begin(conversation);
|
|
439
|
-
const queued = conversation.queued.shift();
|
|
440
|
-
if (queued !== undefined)
|
|
441
|
-
this.attach(turn, queued);
|
|
853
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
854
|
+
this.took(turn);
|
|
442
855
|
return turn;
|
|
443
856
|
}
|
|
444
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
445
858
|
const turn = conversation.turn;
|
|
446
859
|
if (turn === null)
|
|
447
860
|
return;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
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;
|
|
454
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();
|
|
455
873
|
}
|
|
456
874
|
async ask(conversation, method, params, secret, signal) {
|
|
457
875
|
const turn = this.started(conversation);
|
|
@@ -497,65 +915,126 @@ export class Conversations {
|
|
|
497
915
|
if (content !== null)
|
|
498
916
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
499
917
|
}
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
918
|
+
read(conversation, reader, message) {
|
|
919
|
+
for (const event of reader.read(message))
|
|
920
|
+
this.event(conversation, event);
|
|
921
|
+
}
|
|
922
|
+
event(conversation, event) {
|
|
923
|
+
switch (event.kind) {
|
|
924
|
+
case 'started':
|
|
925
|
+
this.started(conversation);
|
|
926
|
+
return;
|
|
927
|
+
case 'ended':
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
929
|
+
return;
|
|
930
|
+
case 'commands':
|
|
931
|
+
this.commands(conversation, event.commands);
|
|
932
|
+
return;
|
|
933
|
+
case 'options':
|
|
934
|
+
this.options(conversation, event.options);
|
|
935
|
+
return;
|
|
936
|
+
case 'context':
|
|
937
|
+
conversation.context = { used: event.used, window: event.window };
|
|
938
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
939
|
+
return;
|
|
940
|
+
case 'subagent-done':
|
|
941
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
942
|
+
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
946
|
+
case 'marker':
|
|
947
|
+
case 'image':
|
|
948
|
+
if (conversation.turn !== null)
|
|
949
|
+
break;
|
|
950
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
951
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
952
|
+
return;
|
|
517
953
|
}
|
|
518
|
-
|
|
519
|
-
|
|
954
|
+
this.apply(conversation, this.started(conversation), event);
|
|
955
|
+
}
|
|
956
|
+
took(turn) {
|
|
957
|
+
const taken = turn.taken;
|
|
958
|
+
turn.taken = null;
|
|
959
|
+
taken?.();
|
|
960
|
+
}
|
|
961
|
+
apply(conversation, turn, event) {
|
|
962
|
+
switch (event.kind) {
|
|
963
|
+
case 'text':
|
|
964
|
+
case 'reasoning':
|
|
965
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
966
|
+
return;
|
|
967
|
+
case 'command':
|
|
968
|
+
turn.parts.command();
|
|
969
|
+
return;
|
|
970
|
+
case 'subagent': {
|
|
971
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
972
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
973
|
+
this.subagentsChanged(conversation);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
case 'message':
|
|
977
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
978
|
+
return;
|
|
979
|
+
case 'marker':
|
|
980
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
981
|
+
return;
|
|
982
|
+
case 'plan':
|
|
983
|
+
turn.parts.plan(event.entries);
|
|
984
|
+
return;
|
|
985
|
+
case 'image':
|
|
986
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
987
|
+
this.kit.changed();
|
|
988
|
+
return;
|
|
520
989
|
}
|
|
521
990
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
991
|
+
subagentDone(conversation, id, result) {
|
|
992
|
+
const subagent = conversation.subagents.get(id);
|
|
993
|
+
if (subagent === undefined)
|
|
994
|
+
return;
|
|
995
|
+
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
997
|
+
if (subagent.turn === conversation.turn) {
|
|
998
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
1002
|
+
if (conversation.turn === null)
|
|
1003
|
+
conversation.held.push(part);
|
|
525
1004
|
else
|
|
526
|
-
conversation.
|
|
527
|
-
this.kit.changed();
|
|
1005
|
+
conversation.turn.parts.start(part);
|
|
528
1006
|
}
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
turn
|
|
542
|
-
|
|
543
|
-
|
|
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
|
+
}
|
|
1014
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
1015
|
+
const bytes = Buffer.from(data, 'base64');
|
|
1016
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
1018
|
+
try {
|
|
1019
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
1020
|
+
name,
|
|
1021
|
+
media_type: mediaType,
|
|
1022
|
+
bytes: bytes.length,
|
|
1023
|
+
sha256,
|
|
1024
|
+
}));
|
|
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);
|
|
544
1034
|
}
|
|
545
|
-
|
|
546
|
-
|
|
1035
|
+
catch (error) {
|
|
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);
|
|
547
1038
|
}
|
|
548
1039
|
}
|
|
549
|
-
plan(conversation, turn) {
|
|
550
|
-
const sent = this.kit.send({
|
|
551
|
-
type: 'plan',
|
|
552
|
-
conversation_id: conversation.id,
|
|
553
|
-
turn_id: turn.id,
|
|
554
|
-
sequence: turn.planSequence + 1,
|
|
555
|
-
from: 0,
|
|
556
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
557
|
-
});
|
|
558
|
-
if (sent)
|
|
559
|
-
turn.planSequence++;
|
|
560
|
-
}
|
|
561
1040
|
}
|