@agentshouse/kit 0.2.1-c70284f7a7466 → 0.2.1-c7a397cb0cce7
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/dist/acp.js +34 -14
- package/dist/agents.js +34 -14
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +1 -1
- package/dist/conversations.js +195 -79
- package/dist/copies.js +38 -22
- package/dist/files.js +1 -1
- package/dist/inputs.js +63 -0
- package/dist/resident.js +9 -5
- package/dist/scope.js +3 -0
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/stream.js +4 -4
- package/dist/translate.js +2 -8
- package/dist/update.js +20 -23
- package/dist/wire.js +1 -1
- package/package.json +2 -2
package/dist/conversations.js
CHANGED
|
@@ -4,6 +4,7 @@ import { mkdir } from 'node:fs/promises';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
6
|
import { startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal } from './api.js';
|
|
7
8
|
import { openBridge } from './bridge.js';
|
|
8
9
|
import { CLIS } from './clis.js';
|
|
9
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
@@ -11,6 +12,7 @@ import { placeFiles, uploadBytes } from './files.js';
|
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
14
16
|
import { killMarked, Scope } from './scope.js';
|
|
15
17
|
import { holdSecretInput } from './secret-input.js';
|
|
16
18
|
import { createHowWeWork } from './skills.js';
|
|
@@ -19,10 +21,17 @@ import { workFor } from './work.js';
|
|
|
19
21
|
const SIGNED_OUT = RequestError.authRequired().code;
|
|
20
22
|
const KILLED = 'the conversation was killed';
|
|
21
23
|
const REFUSED = 'the CLI refused the prompt';
|
|
22
|
-
const NO_CONNECTION = "the
|
|
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
|
+
};
|
|
23
31
|
const NO_START = "the Agent's CLI did not start";
|
|
24
32
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
25
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";
|
|
26
35
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
27
36
|
class NotStarted extends Error {
|
|
28
37
|
}
|
|
@@ -31,9 +40,20 @@ async function step(reason, work) {
|
|
|
31
40
|
return await work;
|
|
32
41
|
}
|
|
33
42
|
catch (error) {
|
|
34
|
-
throw new NotStarted(reason, { cause: error });
|
|
43
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
35
44
|
}
|
|
36
45
|
}
|
|
46
|
+
function signedOut(error) {
|
|
47
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
54
|
+
function unbridged(error) {
|
|
55
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
56
|
+
}
|
|
37
57
|
const EXIT_PLAN_MODE = {
|
|
38
58
|
parse(value) {
|
|
39
59
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -47,8 +67,7 @@ function causeOf(error) {
|
|
|
47
67
|
return error instanceof Error ? error.message : String(error);
|
|
48
68
|
}
|
|
49
69
|
function answeredWith(error) {
|
|
50
|
-
|
|
51
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
52
71
|
}
|
|
53
72
|
function exitedWith(running, cause) {
|
|
54
73
|
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
@@ -72,10 +91,10 @@ function marker(bridge) {
|
|
|
72
91
|
class Turn {
|
|
73
92
|
id = randomUUID();
|
|
74
93
|
parts;
|
|
75
|
-
admitted = false;
|
|
76
94
|
questions = 0;
|
|
77
95
|
secrets = 0;
|
|
78
96
|
prompted = false;
|
|
97
|
+
taken = null;
|
|
79
98
|
ended = false;
|
|
80
99
|
images = 0;
|
|
81
100
|
uploads = [];
|
|
@@ -89,6 +108,8 @@ class Conversation {
|
|
|
89
108
|
session = null;
|
|
90
109
|
turn = null;
|
|
91
110
|
commands = null;
|
|
111
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
92
113
|
context = null;
|
|
93
114
|
leftovers = new Set();
|
|
94
115
|
waiting = [];
|
|
@@ -96,6 +117,7 @@ class Conversation {
|
|
|
96
117
|
subagents = new Map();
|
|
97
118
|
held = [];
|
|
98
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
99
121
|
prompted = null;
|
|
100
122
|
kills = 0;
|
|
101
123
|
killed = new AbortController();
|
|
@@ -104,6 +126,11 @@ class Conversation {
|
|
|
104
126
|
open = 0;
|
|
105
127
|
queue = Promise.resolve();
|
|
106
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
107
134
|
constructor(id) {
|
|
108
135
|
this.id = id;
|
|
109
136
|
}
|
|
@@ -119,9 +146,7 @@ function asksSecret(request) {
|
|
|
119
146
|
export class Conversations {
|
|
120
147
|
kit;
|
|
121
148
|
conversations = new Map();
|
|
122
|
-
acks = new Map();
|
|
123
149
|
questions = new Map();
|
|
124
|
-
carrying = 0;
|
|
125
150
|
reporting = 0;
|
|
126
151
|
watched = false;
|
|
127
152
|
constructor(kit) {
|
|
@@ -130,37 +155,26 @@ export class Conversations {
|
|
|
130
155
|
setInterval(() => this.look(), 2_000);
|
|
131
156
|
}
|
|
132
157
|
input(input) {
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
conversation.queue = handled.catch(() => undefined);
|
|
150
|
-
const work = handled.then(({ ack }) => ack);
|
|
151
|
-
this.acks.set(input.input_id, work);
|
|
152
|
-
return work;
|
|
153
|
-
})();
|
|
154
|
-
carried
|
|
155
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
156
|
-
.catch(logged)
|
|
157
|
-
.finally(() => {
|
|
158
|
-
this.carrying--;
|
|
159
|
-
this.kit.changed();
|
|
158
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
159
|
+
const conversation = this.conversation(input.conversation_id);
|
|
160
|
+
if (input.kind === 'kill') {
|
|
161
|
+
conversation.kills++;
|
|
162
|
+
conversation.killed.abort(new Error(KILLED));
|
|
163
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
164
|
+
waiting.settle({ refused: KILLED });
|
|
165
|
+
this.halt(conversation);
|
|
166
|
+
}
|
|
167
|
+
if (input.kind === 'interrupt') {
|
|
168
|
+
conversation.interrupts++;
|
|
169
|
+
conversation.interrupted.abort();
|
|
170
|
+
}
|
|
171
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
172
|
+
conversation.queue = handled.catch(() => undefined);
|
|
173
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
160
174
|
});
|
|
161
175
|
}
|
|
162
176
|
idle() {
|
|
163
|
-
if (this.
|
|
177
|
+
if (this.reporting > 0)
|
|
164
178
|
return false;
|
|
165
179
|
for (const conversation of this.conversations.values()) {
|
|
166
180
|
const turn = conversation.turn;
|
|
@@ -189,6 +203,14 @@ export class Conversations {
|
|
|
189
203
|
}
|
|
190
204
|
opened() {
|
|
191
205
|
this.watched = false;
|
|
206
|
+
for (const conversation of this.conversations.values()) {
|
|
207
|
+
if (conversation.running !== null)
|
|
208
|
+
this.facts(conversation);
|
|
209
|
+
else if (conversation.exitUntold)
|
|
210
|
+
this.process(conversation, false);
|
|
211
|
+
if (conversation.untold || conversation.told !== null)
|
|
212
|
+
this.retell(conversation);
|
|
213
|
+
}
|
|
192
214
|
}
|
|
193
215
|
watching(watched) {
|
|
194
216
|
const resend = watched && !this.watched;
|
|
@@ -210,9 +232,10 @@ export class Conversations {
|
|
|
210
232
|
}
|
|
211
233
|
async carry(conversation, input) {
|
|
212
234
|
if (input.kind === 'open')
|
|
213
|
-
return { ack:
|
|
235
|
+
return { ack: this.open(conversation, input) };
|
|
214
236
|
if (input.kind === 'message')
|
|
215
237
|
return this.message(conversation, input);
|
|
238
|
+
await this.launched(conversation);
|
|
216
239
|
if (input.kind === 'interrupt') {
|
|
217
240
|
if (--conversation.interrupts === 0)
|
|
218
241
|
conversation.interrupted = new AbortController();
|
|
@@ -236,22 +259,58 @@ export class Conversations {
|
|
|
236
259
|
}
|
|
237
260
|
return { ack: Promise.resolve({}) };
|
|
238
261
|
}
|
|
239
|
-
|
|
262
|
+
open(conversation, input) {
|
|
240
263
|
if (conversation.running !== null)
|
|
241
|
-
return {};
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, input.agent_id), null, true);
|
|
266
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
267
|
+
}
|
|
268
|
+
launch(conversation, route, sessionId, empty) {
|
|
269
|
+
let opened;
|
|
270
|
+
let failed;
|
|
271
|
+
const bridge = new Promise((resolve, reject) => {
|
|
272
|
+
opened = resolve;
|
|
273
|
+
failed = reject;
|
|
274
|
+
});
|
|
275
|
+
bridge.catch(() => undefined);
|
|
276
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
277
|
+
session.catch(failed);
|
|
278
|
+
const launch = { bridge, session };
|
|
279
|
+
conversation.launch = launch;
|
|
280
|
+
void session
|
|
281
|
+
.catch(() => undefined)
|
|
282
|
+
.then(() => {
|
|
283
|
+
if (conversation.launch !== launch)
|
|
284
|
+
return;
|
|
285
|
+
conversation.launch = null;
|
|
286
|
+
this.next(conversation);
|
|
287
|
+
});
|
|
288
|
+
this.next(conversation);
|
|
289
|
+
return launch;
|
|
290
|
+
}
|
|
291
|
+
async launched(conversation) {
|
|
292
|
+
while (conversation.launch !== null)
|
|
293
|
+
await conversation.launch.session.catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
instructed(conversation, route) {
|
|
296
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
297
|
+
if (bridge === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
300
|
+
read.catch(() => undefined);
|
|
301
|
+
return read;
|
|
248
302
|
}
|
|
249
303
|
async message(conversation, input) {
|
|
250
304
|
const content = [{ type: 'text', text: String(input.text) }];
|
|
251
305
|
let route;
|
|
306
|
+
let instructing = null;
|
|
252
307
|
try {
|
|
253
308
|
route = await this.route(conversation, input.agent_id);
|
|
309
|
+
if (input.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
254
311
|
const files = input.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
255
314
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
256
315
|
const lines = await this.transcribed(conversation, files, paths);
|
|
257
316
|
if (lines.length > 0)
|
|
@@ -260,11 +319,13 @@ export class Conversations {
|
|
|
260
319
|
throw new Error(KILLED);
|
|
261
320
|
}
|
|
262
321
|
catch (error) {
|
|
322
|
+
this.prepares(conversation, null);
|
|
263
323
|
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
264
324
|
}
|
|
325
|
+
conversation.preparing = null;
|
|
265
326
|
return {
|
|
266
327
|
ack: new Promise((settle) => {
|
|
267
|
-
conversation.waiting.push({ input, route, content, settle });
|
|
328
|
+
conversation.waiting.push({ input, route, content, instructing, settle });
|
|
268
329
|
this.next(conversation);
|
|
269
330
|
}),
|
|
270
331
|
};
|
|
@@ -273,8 +334,12 @@ export class Conversations {
|
|
|
273
334
|
if (conversation.sending !== null || conversation.open > 0)
|
|
274
335
|
return;
|
|
275
336
|
const waiting = conversation.waiting.shift();
|
|
276
|
-
if (waiting === undefined)
|
|
337
|
+
if (waiting === undefined) {
|
|
338
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
|
+
if (conversation.starting !== step)
|
|
340
|
+
this.starts(conversation, step);
|
|
277
341
|
return;
|
|
342
|
+
}
|
|
278
343
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
279
344
|
conversation.sending = null;
|
|
280
345
|
this.next(conversation);
|
|
@@ -284,27 +349,33 @@ export class Conversations {
|
|
|
284
349
|
const { input } = waiting;
|
|
285
350
|
let ack = {};
|
|
286
351
|
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
287
353
|
const prompt = [];
|
|
288
354
|
try {
|
|
355
|
+
if (conversation.running === null) {
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
289
359
|
if (conversation.running === null) {
|
|
290
360
|
const stored = conversation.session ?? input.provider_session_id;
|
|
291
361
|
const empty = input.first === true && conversation.prompted !== stored;
|
|
292
|
-
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (input.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
293
366
|
}
|
|
294
367
|
const session = conversation.running.sessionId;
|
|
295
368
|
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
296
369
|
ack = { provider_session_id: session };
|
|
297
370
|
}
|
|
298
371
|
if (input.first === true) {
|
|
299
|
-
prompt.push({
|
|
300
|
-
type: 'text',
|
|
301
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
302
|
-
});
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
303
373
|
}
|
|
304
374
|
if (conversation.kills > 0)
|
|
305
375
|
throw new Error(KILLED);
|
|
306
376
|
}
|
|
307
377
|
catch (error) {
|
|
378
|
+
this.starts(conversation, null);
|
|
308
379
|
if (started) {
|
|
309
380
|
conversation.session = null;
|
|
310
381
|
await this.kill(conversation);
|
|
@@ -312,15 +383,45 @@ export class Conversations {
|
|
|
312
383
|
waiting.settle({ refused: refusal(conversation, error) });
|
|
313
384
|
return;
|
|
314
385
|
}
|
|
315
|
-
this.
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
316
388
|
waiting.settle(ack);
|
|
317
389
|
}
|
|
390
|
+
starts(conversation, step) {
|
|
391
|
+
conversation.starting = step;
|
|
392
|
+
if (conversation.shown !== null)
|
|
393
|
+
clearTimeout(conversation.shown);
|
|
394
|
+
conversation.shown = null;
|
|
395
|
+
if (step === null || conversation.told !== null) {
|
|
396
|
+
this.tell(conversation, step);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
conversation.shown = setTimeout(() => {
|
|
400
|
+
conversation.shown = null;
|
|
401
|
+
if (conversation.starting === step)
|
|
402
|
+
this.tell(conversation, step);
|
|
403
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
404
|
+
}
|
|
405
|
+
prepares(conversation, step) {
|
|
406
|
+
conversation.preparing = step;
|
|
407
|
+
this.next(conversation);
|
|
408
|
+
}
|
|
409
|
+
tell(conversation, step) {
|
|
410
|
+
if (conversation.told === step && !conversation.untold)
|
|
411
|
+
return;
|
|
412
|
+
conversation.told = step;
|
|
413
|
+
this.retell(conversation);
|
|
414
|
+
}
|
|
415
|
+
retell(conversation) {
|
|
416
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
417
|
+
}
|
|
318
418
|
async transcribed(conversation, files, paths) {
|
|
319
419
|
const lines = [];
|
|
320
420
|
for (const [index, path] of paths.entries()) {
|
|
321
421
|
lines.push(path);
|
|
322
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
323
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
324
425
|
try {
|
|
325
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
326
427
|
if (text !== null && text !== '')
|
|
@@ -387,7 +488,8 @@ export class Conversations {
|
|
|
387
488
|
}
|
|
388
489
|
return route;
|
|
389
490
|
}
|
|
390
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
391
493
|
const reader = readerFor(route.kind);
|
|
392
494
|
reader.session = sessionId;
|
|
393
495
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -395,7 +497,7 @@ export class Conversations {
|
|
|
395
497
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
396
498
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
397
499
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
398
|
-
const bridge = await step(
|
|
500
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
399
501
|
if (conversation.kills > 0) {
|
|
400
502
|
bridge.close();
|
|
401
503
|
throw new Error(KILLED);
|
|
@@ -403,11 +505,13 @@ export class Conversations {
|
|
|
403
505
|
conversation.opening = bridge;
|
|
404
506
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
405
507
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
|
+
let adapter = null;
|
|
406
509
|
try {
|
|
407
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
511
|
+
bridged(bridge);
|
|
408
512
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
409
513
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
410
|
-
|
|
514
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
411
515
|
work.read(message);
|
|
412
516
|
this.read(conversation, reader, message);
|
|
413
517
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
@@ -416,26 +520,24 @@ export class Conversations {
|
|
|
416
520
|
const cwd = route.working_directory;
|
|
417
521
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
418
522
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
419
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
523
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
420
524
|
reader.session = opened.sessionId;
|
|
421
525
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
422
526
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
423
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
424
528
|
}
|
|
425
529
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
426
530
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
427
531
|
void work.settled().then(() => scope.record());
|
|
428
532
|
conversation.running = running;
|
|
429
533
|
conversation.session = running.sessionId;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.options(conversation, options);
|
|
434
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
534
|
+
conversation.options = options;
|
|
535
|
+
this.facts(conversation);
|
|
536
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
435
537
|
return running.sessionId;
|
|
436
538
|
}
|
|
437
539
|
catch (error) {
|
|
438
|
-
|
|
540
|
+
adapter?.retire();
|
|
439
541
|
work.close();
|
|
440
542
|
conversation.leftovers.add(scope);
|
|
441
543
|
bridge.close();
|
|
@@ -451,7 +553,7 @@ export class Conversations {
|
|
|
451
553
|
}
|
|
452
554
|
catch (error) {
|
|
453
555
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
454
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
556
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
455
557
|
}
|
|
456
558
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
457
559
|
return null;
|
|
@@ -478,10 +580,7 @@ export class Conversations {
|
|
|
478
580
|
}
|
|
479
581
|
return options;
|
|
480
582
|
}
|
|
481
|
-
exited(conversation,
|
|
482
|
-
const running = conversation.running;
|
|
483
|
-
if (running?.adapter !== adapter)
|
|
484
|
-
return;
|
|
583
|
+
exited(conversation, running, cause) {
|
|
485
584
|
this.detach(conversation, running);
|
|
486
585
|
if (conversation.turn !== null) {
|
|
487
586
|
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
@@ -494,11 +593,22 @@ export class Conversations {
|
|
|
494
593
|
conversation.leftovers.add(running.scope);
|
|
495
594
|
conversation.running = null;
|
|
496
595
|
conversation.commands = null;
|
|
596
|
+
conversation.options = null;
|
|
497
597
|
for (const [id, question] of this.questions) {
|
|
498
598
|
if (question.conversation === conversation)
|
|
499
599
|
this.questions.delete(id);
|
|
500
600
|
}
|
|
501
|
-
this.
|
|
601
|
+
this.process(conversation, false);
|
|
602
|
+
}
|
|
603
|
+
process(conversation, running) {
|
|
604
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
605
|
+
}
|
|
606
|
+
facts(conversation) {
|
|
607
|
+
this.process(conversation, true);
|
|
608
|
+
if (conversation.commands !== null)
|
|
609
|
+
this.commands(conversation, conversation.commands);
|
|
610
|
+
if (conversation.options !== null)
|
|
611
|
+
this.options(conversation, conversation.options);
|
|
502
612
|
}
|
|
503
613
|
commands(conversation, commands) {
|
|
504
614
|
conversation.commands = commands;
|
|
@@ -509,6 +619,7 @@ export class Conversations {
|
|
|
509
619
|
options(conversation, options) {
|
|
510
620
|
if (conversation.running === null)
|
|
511
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
512
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
513
624
|
}
|
|
514
625
|
deliver(conversation, turn, event, body) {
|
|
@@ -523,7 +634,7 @@ export class Conversations {
|
|
|
523
634
|
return delivered;
|
|
524
635
|
}
|
|
525
636
|
write(conversation, turn, operation) {
|
|
526
|
-
if (
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
527
638
|
return;
|
|
528
639
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
529
640
|
}
|
|
@@ -534,12 +645,7 @@ export class Conversations {
|
|
|
534
645
|
begin(conversation) {
|
|
535
646
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
536
647
|
conversation.open++;
|
|
537
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
538
|
-
.then(() => {
|
|
539
|
-
turn.admitted = true;
|
|
540
|
-
this.replay(conversation, turn);
|
|
541
|
-
})
|
|
542
|
-
.catch(logged);
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
543
649
|
conversation.turn = turn;
|
|
544
650
|
if (conversation.context !== null)
|
|
545
651
|
turn.parts.measure(conversation.context);
|
|
@@ -572,12 +678,14 @@ export class Conversations {
|
|
|
572
678
|
this.next(conversation);
|
|
573
679
|
this.kit.changed();
|
|
574
680
|
}
|
|
575
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
576
682
|
const running = conversation.running;
|
|
577
683
|
const turn = this.begin(conversation);
|
|
578
684
|
turn.prompted = true;
|
|
685
|
+
turn.taken = taken;
|
|
579
686
|
conversation.prompted = running.sessionId;
|
|
580
687
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
688
|
+
this.took(turn);
|
|
581
689
|
const notice = answeredFailure(answer);
|
|
582
690
|
if (notice !== null) {
|
|
583
691
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -590,8 +698,9 @@ export class Conversations {
|
|
|
590
698
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
591
699
|
}
|
|
592
700
|
else {
|
|
701
|
+
this.took(turn);
|
|
593
702
|
if (conversation.open === 1 && conversation.running === running) {
|
|
594
|
-
running.
|
|
703
|
+
running.adapter.retire();
|
|
595
704
|
this.detach(conversation, running);
|
|
596
705
|
}
|
|
597
706
|
this.end(conversation, turn, answeredWith(error));
|
|
@@ -599,7 +708,9 @@ export class Conversations {
|
|
|
599
708
|
});
|
|
600
709
|
}
|
|
601
710
|
started(conversation) {
|
|
602
|
-
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
713
|
+
return turn;
|
|
603
714
|
}
|
|
604
715
|
ended(conversation) {
|
|
605
716
|
const turn = conversation.turn;
|
|
@@ -694,6 +805,11 @@ export class Conversations {
|
|
|
694
805
|
}
|
|
695
806
|
this.apply(conversation, this.started(conversation), event);
|
|
696
807
|
}
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
812
|
+
}
|
|
697
813
|
apply(conversation, turn, event) {
|
|
698
814
|
switch (event.kind) {
|
|
699
815
|
case 'text':
|
package/dist/copies.js
CHANGED
|
@@ -58,6 +58,9 @@ function expiredAuthority(error) {
|
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
function grouped(items, size) {
|
|
62
|
+
return items.flatMap((_, index) => (index % size === 0 ? [items.slice(index, index + size)] : []));
|
|
63
|
+
}
|
|
61
64
|
function covers(path, prefix) {
|
|
62
65
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
63
66
|
}
|
|
@@ -219,26 +222,35 @@ export class LocalCopies {
|
|
|
219
222
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
220
223
|
.sort();
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
connected() {
|
|
226
|
+
return this.selected();
|
|
227
|
+
}
|
|
228
|
+
async changed(rooms) {
|
|
229
|
+
if (rooms.length > 0)
|
|
230
|
+
await this.selected(rooms);
|
|
226
231
|
}
|
|
227
232
|
fetch() {
|
|
228
233
|
return this.answered(() => this.fetched(true));
|
|
229
234
|
}
|
|
235
|
+
async selected(rooms) {
|
|
236
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
237
|
+
if (copied.refused)
|
|
238
|
+
logged(copied.text);
|
|
239
|
+
}
|
|
230
240
|
answered(work) {
|
|
231
241
|
const answered = this.answering.then(work);
|
|
232
242
|
this.answering = answered.catch(() => undefined);
|
|
233
243
|
return answered;
|
|
234
244
|
}
|
|
235
|
-
async fetched(refreshing) {
|
|
236
|
-
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
245
|
+
async fetched(refreshing, rooms) {
|
|
246
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
237
247
|
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
238
|
-
|
|
248
|
+
const named = rooms === undefined ? null : new Set(rooms.map((room) => `rooms/${room}`));
|
|
249
|
+
const held = () => [...this.copies.values()].filter((copy) => named === null || named.has(copy.key));
|
|
250
|
+
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
239
251
|
const said = [];
|
|
240
252
|
let refused = false;
|
|
241
|
-
for (const copy of
|
|
253
|
+
for (const copy of held()) {
|
|
242
254
|
const returned = synced.has(copy.key);
|
|
243
255
|
if (returned && copy.erasing === undefined)
|
|
244
256
|
continue;
|
|
@@ -269,13 +281,12 @@ export class LocalCopies {
|
|
|
269
281
|
}
|
|
270
282
|
}
|
|
271
283
|
if (refreshing && current.length > 0) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
catch (error) {
|
|
284
|
+
const failed = await this.refreshed(current).then(() => null, houseCause);
|
|
285
|
+
for (const copy of current)
|
|
286
|
+
said.push(...moved(copy, this.copies.get(copy.key)));
|
|
287
|
+
if (failed !== null) {
|
|
277
288
|
refused = true;
|
|
278
|
-
said.push(
|
|
289
|
+
said.push(failed);
|
|
279
290
|
}
|
|
280
291
|
}
|
|
281
292
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -450,19 +461,24 @@ export class LocalCopies {
|
|
|
450
461
|
return next;
|
|
451
462
|
}
|
|
452
463
|
async refreshed(copies) {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
});
|
|
456
|
-
const
|
|
464
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map((copy) => rootOf(copy.room_handle)) });
|
|
465
|
+
const [first, ...rest] = copies;
|
|
466
|
+
const { bounds, authorities } = await discover([first]);
|
|
467
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
468
|
+
authorities.push(...(await discover(group)).authorities);
|
|
469
|
+
const unchanged = [];
|
|
457
470
|
for (const copy of copies) {
|
|
458
|
-
const authority =
|
|
471
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
459
472
|
if (authority !== undefined &&
|
|
460
473
|
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
461
|
-
|
|
474
|
+
await this.save(await this.received(copy));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
unchanged.push(copy);
|
|
462
478
|
}
|
|
463
479
|
}
|
|
464
|
-
const
|
|
465
|
-
|
|
480
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
481
|
+
await this.caughtUp(group, false);
|
|
466
482
|
}
|
|
467
483
|
async caughtUp(copies, whole) {
|
|
468
484
|
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|