@agentshouse/kit 0.2.1-cf110d15f69c8 → 0.2.1-cf6326dfa5c3e
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 +20 -19
- package/dist/acp.js +34 -14
- package/dist/agents.js +108 -21
- package/dist/api.js +5 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +19 -3
- package/dist/conversations.js +209 -77
- package/dist/copies.js +98 -47
- package/dist/end.js +5 -0
- package/dist/events.js +4 -0
- package/dist/files.js +8 -2
- package/dist/git.js +10 -8
- package/dist/home.js +56 -5
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -206
- package/dist/inputs.js +63 -0
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +46 -28
- package/dist/logout.js +0 -2
- package/dist/placement.js +30 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +65 -16
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/skills.js +81 -12
- package/dist/stream.js +66 -17
- package/dist/transcription.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +48 -29
- package/dist/wire.js +6 -0
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/clis.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { constants, readFileSync } from 'node:fs';
|
|
3
4
|
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { delimiter, isAbsolute, join } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
7
|
import { kitHome } from './home.js';
|
|
7
8
|
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
8
9
|
function houseRule(houses) {
|
|
@@ -54,11 +55,14 @@ export async function allowHouseInCodex(path) {
|
|
|
54
55
|
houses.push(house);
|
|
55
56
|
}
|
|
56
57
|
await mkdir(rules, { recursive: true });
|
|
57
|
-
const temporary = `${file}.${
|
|
58
|
+
const temporary = `${file}.${randomUUID()}`;
|
|
58
59
|
await writeFile(temporary, houseRule(houses));
|
|
59
60
|
await rename(temporary, file);
|
|
60
61
|
}
|
|
61
62
|
async function nothing() { }
|
|
63
|
+
function answered(error) {
|
|
64
|
+
return typeof error === 'object' && error !== null ? error : {};
|
|
65
|
+
}
|
|
62
66
|
function bridgeInClaudeSandbox(bridge) {
|
|
63
67
|
return process.platform === 'darwin' ? { allowUnixSockets: [bridge] } : { allowAllUnixSockets: true };
|
|
64
68
|
}
|
|
@@ -76,6 +80,10 @@ export const CLIS = {
|
|
|
76
80
|
args: [],
|
|
77
81
|
fullAccess: 'agent-full-access',
|
|
78
82
|
sessionMeta: () => ({}),
|
|
83
|
+
unknownSession: (error) => {
|
|
84
|
+
const { code, data } = answered(error);
|
|
85
|
+
return code === -32603 && typeof data?.details === 'string' && data.details.startsWith('no rollout found');
|
|
86
|
+
},
|
|
79
87
|
renames: true,
|
|
80
88
|
allowHouse: allowHouseInCodex,
|
|
81
89
|
signedIn: { command: ['login', 'status'] },
|
|
@@ -105,6 +113,7 @@ export const CLIS = {
|
|
|
105
113
|
emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
|
|
106
114
|
},
|
|
107
115
|
}),
|
|
116
|
+
unknownSession: (error) => answered(error).code === -32002,
|
|
108
117
|
renames: false,
|
|
109
118
|
allowHouse: nothing,
|
|
110
119
|
signedIn: { command: ['auth', 'status'] },
|
|
@@ -119,6 +128,7 @@ export const CLIS = {
|
|
|
119
128
|
args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
|
|
120
129
|
fullAccess: null,
|
|
121
130
|
sessionMeta: () => ({}),
|
|
131
|
+
unknownSession: (error) => answered(error).data?.code === 'FS_NOT_FOUND',
|
|
122
132
|
renames: false,
|
|
123
133
|
allowHouse: nothing,
|
|
124
134
|
signedIn: { initializeMeta: 'defaultAuthMethodId' },
|
|
@@ -199,7 +209,7 @@ export async function installAdapter(kind, signal) {
|
|
|
199
209
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
200
210
|
if ((await installedAdapter(kind)) === release)
|
|
201
211
|
return null;
|
|
202
|
-
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
212
|
+
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--cache', join(kitHome(), 'npm-cache'), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
203
213
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
204
214
|
}
|
|
205
215
|
export async function installCli(kind, signal) {
|
|
@@ -224,6 +234,12 @@ export async function loginPath() {
|
|
|
224
234
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
225
235
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
226
236
|
}
|
|
237
|
+
export async function writeCodexRule() {
|
|
238
|
+
const shell = await loginPath().catch(() => '');
|
|
239
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
240
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
227
243
|
export async function versionOf(path, signal) {
|
|
228
244
|
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
229
245
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -1,41 +1,100 @@
|
|
|
1
|
-
import { client, } from '@agentclientprotocol/sdk';
|
|
1
|
+
import { client, RequestError, } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
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
|
-
import { readerFor } from './events.js';
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
10
11
|
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';
|
|
17
19
|
import { transcribe } from './transcription.js';
|
|
18
20
|
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
19
22
|
const KILLED = 'the conversation was killed';
|
|
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
|
+
class NotStarted extends Error {
|
|
37
|
+
}
|
|
38
|
+
async function step(reason, work) {
|
|
39
|
+
try {
|
|
40
|
+
return await work;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
44
|
+
}
|
|
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
|
+
}
|
|
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_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
20
66
|
function causeOf(error) {
|
|
21
67
|
return error instanceof Error ? error.message : String(error);
|
|
22
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
74
|
+
}
|
|
23
75
|
function answerMessage(request, response) {
|
|
24
76
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
25
77
|
}
|
|
26
78
|
function logged(error) {
|
|
27
79
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
28
80
|
}
|
|
81
|
+
function refusal(conversation, error) {
|
|
82
|
+
if (conversation.kills > 0)
|
|
83
|
+
return KILLED;
|
|
84
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
85
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
86
|
+
return causeOf(error);
|
|
87
|
+
}
|
|
29
88
|
function marker(bridge) {
|
|
30
89
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
90
|
}
|
|
32
91
|
class Turn {
|
|
33
92
|
id = randomUUID();
|
|
34
93
|
parts;
|
|
35
|
-
admitted = false;
|
|
36
94
|
questions = 0;
|
|
37
95
|
secrets = 0;
|
|
38
96
|
prompted = false;
|
|
97
|
+
taken = null;
|
|
39
98
|
ended = false;
|
|
40
99
|
images = 0;
|
|
41
100
|
uploads = [];
|
|
@@ -56,6 +115,7 @@ class Conversation {
|
|
|
56
115
|
subagents = new Map();
|
|
57
116
|
held = [];
|
|
58
117
|
opening = null;
|
|
118
|
+
prompted = null;
|
|
59
119
|
kills = 0;
|
|
60
120
|
killed = new AbortController();
|
|
61
121
|
interrupts = 0;
|
|
@@ -63,6 +123,11 @@ class Conversation {
|
|
|
63
123
|
open = 0;
|
|
64
124
|
queue = Promise.resolve();
|
|
65
125
|
reports = Promise.resolve();
|
|
126
|
+
starting = null;
|
|
127
|
+
preparing = null;
|
|
128
|
+
told = null;
|
|
129
|
+
untold = false;
|
|
130
|
+
shown = null;
|
|
66
131
|
constructor(id) {
|
|
67
132
|
this.id = id;
|
|
68
133
|
}
|
|
@@ -78,9 +143,7 @@ function asksSecret(request) {
|
|
|
78
143
|
export class Conversations {
|
|
79
144
|
kit;
|
|
80
145
|
conversations = new Map();
|
|
81
|
-
acks = new Map();
|
|
82
146
|
questions = new Map();
|
|
83
|
-
carrying = 0;
|
|
84
147
|
reporting = 0;
|
|
85
148
|
watched = false;
|
|
86
149
|
constructor(kit) {
|
|
@@ -89,37 +152,26 @@ export class Conversations {
|
|
|
89
152
|
setInterval(() => this.look(), 2_000);
|
|
90
153
|
}
|
|
91
154
|
input(input) {
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
conversation.queue = handled.catch(() => undefined);
|
|
109
|
-
const work = handled.then(({ ack }) => ack);
|
|
110
|
-
this.acks.set(input.input_id, work);
|
|
111
|
-
return work;
|
|
112
|
-
})();
|
|
113
|
-
carried
|
|
114
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
115
|
-
.catch(logged)
|
|
116
|
-
.finally(() => {
|
|
117
|
-
this.carrying--;
|
|
118
|
-
this.kit.changed();
|
|
155
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
156
|
+
const conversation = this.conversation(input.conversation_id);
|
|
157
|
+
if (input.kind === 'kill') {
|
|
158
|
+
conversation.kills++;
|
|
159
|
+
conversation.killed.abort(new Error(KILLED));
|
|
160
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
161
|
+
waiting.settle({ refused: KILLED });
|
|
162
|
+
this.halt(conversation);
|
|
163
|
+
}
|
|
164
|
+
if (input.kind === 'interrupt') {
|
|
165
|
+
conversation.interrupts++;
|
|
166
|
+
conversation.interrupted.abort();
|
|
167
|
+
}
|
|
168
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
169
|
+
conversation.queue = handled.catch(() => undefined);
|
|
170
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
119
171
|
});
|
|
120
172
|
}
|
|
121
173
|
idle() {
|
|
122
|
-
if (this.
|
|
174
|
+
if (this.reporting > 0)
|
|
123
175
|
return false;
|
|
124
176
|
for (const conversation of this.conversations.values()) {
|
|
125
177
|
const turn = conversation.turn;
|
|
@@ -148,6 +200,10 @@ export class Conversations {
|
|
|
148
200
|
}
|
|
149
201
|
opened() {
|
|
150
202
|
this.watched = false;
|
|
203
|
+
for (const conversation of this.conversations.values()) {
|
|
204
|
+
if (conversation.untold || conversation.told !== null)
|
|
205
|
+
this.retell(conversation);
|
|
206
|
+
}
|
|
151
207
|
}
|
|
152
208
|
watching(watched) {
|
|
153
209
|
const resend = watched && !this.watched;
|
|
@@ -198,11 +254,15 @@ export class Conversations {
|
|
|
198
254
|
async open(conversation, input) {
|
|
199
255
|
if (conversation.running !== null)
|
|
200
256
|
return {};
|
|
257
|
+
this.prepares(conversation, 'session');
|
|
201
258
|
try {
|
|
202
|
-
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
259
|
+
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null, true) };
|
|
203
260
|
}
|
|
204
261
|
catch (error) {
|
|
205
|
-
return { refused:
|
|
262
|
+
return { refused: refusal(conversation, error) };
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
this.prepares(conversation, null);
|
|
206
266
|
}
|
|
207
267
|
}
|
|
208
268
|
async message(conversation, input) {
|
|
@@ -211,6 +271,8 @@ export class Conversations {
|
|
|
211
271
|
try {
|
|
212
272
|
route = await this.route(conversation, input.agent_id);
|
|
213
273
|
const files = input.files;
|
|
274
|
+
if (files.length > 0)
|
|
275
|
+
this.prepares(conversation, 'files');
|
|
214
276
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
215
277
|
const lines = await this.transcribed(conversation, files, paths);
|
|
216
278
|
if (lines.length > 0)
|
|
@@ -219,8 +281,10 @@ export class Conversations {
|
|
|
219
281
|
throw new Error(KILLED);
|
|
220
282
|
}
|
|
221
283
|
catch (error) {
|
|
222
|
-
|
|
284
|
+
this.prepares(conversation, null);
|
|
285
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
223
286
|
}
|
|
287
|
+
conversation.preparing = null;
|
|
224
288
|
return {
|
|
225
289
|
ack: new Promise((settle) => {
|
|
226
290
|
conversation.waiting.push({ input, route, content, settle });
|
|
@@ -232,8 +296,11 @@ export class Conversations {
|
|
|
232
296
|
if (conversation.sending !== null || conversation.open > 0)
|
|
233
297
|
return;
|
|
234
298
|
const waiting = conversation.waiting.shift();
|
|
235
|
-
if (waiting === undefined)
|
|
299
|
+
if (waiting === undefined) {
|
|
300
|
+
if (conversation.starting !== conversation.preparing)
|
|
301
|
+
this.starts(conversation, conversation.preparing);
|
|
236
302
|
return;
|
|
303
|
+
}
|
|
237
304
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
238
305
|
conversation.sending = null;
|
|
239
306
|
this.next(conversation);
|
|
@@ -242,13 +309,18 @@ export class Conversations {
|
|
|
242
309
|
async send(conversation, waiting) {
|
|
243
310
|
const { input } = waiting;
|
|
244
311
|
let ack = {};
|
|
312
|
+
let started = false;
|
|
245
313
|
const prompt = [];
|
|
246
314
|
try {
|
|
247
315
|
if (conversation.running === null) {
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
316
|
+
this.starts(conversation, 'session');
|
|
317
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
318
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
319
|
+
started = (await this.start(conversation, waiting.route, stored, empty)) !== stored;
|
|
320
|
+
}
|
|
321
|
+
const session = conversation.running.sessionId;
|
|
322
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
323
|
+
ack = { provider_session_id: session };
|
|
252
324
|
}
|
|
253
325
|
if (input.first === true) {
|
|
254
326
|
prompt.push({
|
|
@@ -260,22 +332,53 @@ export class Conversations {
|
|
|
260
332
|
throw new Error(KILLED);
|
|
261
333
|
}
|
|
262
334
|
catch (error) {
|
|
263
|
-
|
|
335
|
+
this.starts(conversation, null);
|
|
336
|
+
if (started) {
|
|
264
337
|
conversation.session = null;
|
|
265
338
|
await this.kill(conversation);
|
|
266
339
|
}
|
|
267
|
-
waiting.settle({ refused: conversation
|
|
340
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
268
341
|
return;
|
|
269
342
|
}
|
|
270
|
-
this.
|
|
343
|
+
this.starts(conversation, null);
|
|
344
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
271
345
|
waiting.settle(ack);
|
|
272
346
|
}
|
|
347
|
+
starts(conversation, step) {
|
|
348
|
+
conversation.starting = step;
|
|
349
|
+
if (conversation.shown !== null)
|
|
350
|
+
clearTimeout(conversation.shown);
|
|
351
|
+
conversation.shown = null;
|
|
352
|
+
if (step === null || conversation.told !== null) {
|
|
353
|
+
this.tell(conversation, step);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
conversation.shown = setTimeout(() => {
|
|
357
|
+
conversation.shown = null;
|
|
358
|
+
if (conversation.starting === step)
|
|
359
|
+
this.tell(conversation, step);
|
|
360
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
361
|
+
}
|
|
362
|
+
prepares(conversation, step) {
|
|
363
|
+
conversation.preparing = step;
|
|
364
|
+
this.next(conversation);
|
|
365
|
+
}
|
|
366
|
+
tell(conversation, step) {
|
|
367
|
+
if (conversation.told === step && !conversation.untold)
|
|
368
|
+
return;
|
|
369
|
+
conversation.told = step;
|
|
370
|
+
this.retell(conversation);
|
|
371
|
+
}
|
|
372
|
+
retell(conversation) {
|
|
373
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
374
|
+
}
|
|
273
375
|
async transcribed(conversation, files, paths) {
|
|
274
376
|
const lines = [];
|
|
275
377
|
for (const [index, path] of paths.entries()) {
|
|
276
378
|
lines.push(path);
|
|
277
379
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
278
380
|
continue;
|
|
381
|
+
this.prepares(conversation, 'transcription');
|
|
279
382
|
try {
|
|
280
383
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
281
384
|
if (text !== null && text !== '')
|
|
@@ -342,14 +445,15 @@ export class Conversations {
|
|
|
342
445
|
}
|
|
343
446
|
return route;
|
|
344
447
|
}
|
|
345
|
-
async start(conversation, route, sessionId) {
|
|
448
|
+
async start(conversation, route, sessionId, empty) {
|
|
346
449
|
const reader = readerFor(route.kind);
|
|
347
450
|
reader.session = sessionId;
|
|
348
451
|
const app = client({ name: '@agentshouse/kit' })
|
|
349
452
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
350
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
453
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
454
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
351
455
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
352
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
456
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
353
457
|
if (conversation.kills > 0) {
|
|
354
458
|
bridge.close();
|
|
355
459
|
throw new Error(KILLED);
|
|
@@ -357,27 +461,28 @@ export class Conversations {
|
|
|
357
461
|
conversation.opening = bridge;
|
|
358
462
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
359
463
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
464
|
+
let adapter = null;
|
|
360
465
|
try {
|
|
361
466
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
362
467
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
363
|
-
const
|
|
468
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
469
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
364
470
|
work.read(message);
|
|
365
471
|
this.read(conversation, reader, message);
|
|
366
|
-
}, { ...bridge.env, ...
|
|
472
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
367
473
|
void adapter.exited.then(() => bridge.close());
|
|
368
474
|
const agent = adapter.connection.agent;
|
|
369
475
|
const cwd = route.working_directory;
|
|
370
476
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
477
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
478
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
374
479
|
reader.session = opened.sessionId;
|
|
375
|
-
if (sessionId
|
|
480
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
376
481
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
377
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
482
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
378
483
|
}
|
|
379
484
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
380
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
485
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
381
486
|
void work.settled().then(() => scope.record());
|
|
382
487
|
conversation.running = running;
|
|
383
488
|
conversation.session = running.sessionId;
|
|
@@ -385,11 +490,11 @@ export class Conversations {
|
|
|
385
490
|
if (conversation.commands !== null)
|
|
386
491
|
this.commands(conversation, conversation.commands);
|
|
387
492
|
this.options(conversation, options);
|
|
388
|
-
void adapter.
|
|
493
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
389
494
|
return running.sessionId;
|
|
390
495
|
}
|
|
391
496
|
catch (error) {
|
|
392
|
-
|
|
497
|
+
adapter?.retire();
|
|
393
498
|
work.close();
|
|
394
499
|
conversation.leftovers.add(scope);
|
|
395
500
|
bridge.close();
|
|
@@ -399,6 +504,18 @@ export class Conversations {
|
|
|
399
504
|
conversation.opening = null;
|
|
400
505
|
}
|
|
401
506
|
}
|
|
507
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
508
|
+
try {
|
|
509
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
513
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
514
|
+
}
|
|
515
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
402
519
|
async launchSettings(running, route, offered) {
|
|
403
520
|
let options = offered;
|
|
404
521
|
const settings = [
|
|
@@ -420,13 +537,10 @@ export class Conversations {
|
|
|
420
537
|
}
|
|
421
538
|
return options;
|
|
422
539
|
}
|
|
423
|
-
exited(conversation,
|
|
424
|
-
const running = conversation.running;
|
|
425
|
-
if (running?.adapter !== adapter)
|
|
426
|
-
return;
|
|
540
|
+
exited(conversation, running, cause) {
|
|
427
541
|
this.detach(conversation, running);
|
|
428
542
|
if (conversation.turn !== null) {
|
|
429
|
-
this.end(conversation, conversation.turn,
|
|
543
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
430
544
|
}
|
|
431
545
|
this.next(conversation);
|
|
432
546
|
this.kit.changed();
|
|
@@ -465,7 +579,7 @@ export class Conversations {
|
|
|
465
579
|
return delivered;
|
|
466
580
|
}
|
|
467
581
|
write(conversation, turn, operation) {
|
|
468
|
-
if (
|
|
582
|
+
if (turn.ended || !this.watched)
|
|
469
583
|
return;
|
|
470
584
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
471
585
|
}
|
|
@@ -476,12 +590,7 @@ export class Conversations {
|
|
|
476
590
|
begin(conversation) {
|
|
477
591
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
478
592
|
conversation.open++;
|
|
479
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
480
|
-
.then(() => {
|
|
481
|
-
turn.admitted = true;
|
|
482
|
-
this.replay(conversation, turn);
|
|
483
|
-
})
|
|
484
|
-
.catch(logged);
|
|
593
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
485
594
|
conversation.turn = turn;
|
|
486
595
|
if (conversation.context !== null)
|
|
487
596
|
turn.parts.measure(conversation.context);
|
|
@@ -490,13 +599,17 @@ export class Conversations {
|
|
|
490
599
|
this.kit.changed();
|
|
491
600
|
return turn;
|
|
492
601
|
}
|
|
493
|
-
end(conversation, turn, failed) {
|
|
602
|
+
end(conversation, turn, failed, notice = null) {
|
|
494
603
|
if (turn.ended)
|
|
495
604
|
return;
|
|
605
|
+
if (failed !== null)
|
|
606
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
496
607
|
if (conversation.turn === turn) {
|
|
497
608
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
498
609
|
this.apply(conversation, turn, event);
|
|
499
610
|
}
|
|
611
|
+
if (notice !== null)
|
|
612
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
500
613
|
turn.ended = true;
|
|
501
614
|
conversation.open--;
|
|
502
615
|
if (conversation.turn === turn)
|
|
@@ -504,31 +617,45 @@ export class Conversations {
|
|
|
504
617
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
505
618
|
parts: turn.parts.saved(),
|
|
506
619
|
context: turn.parts.context,
|
|
507
|
-
...failed,
|
|
620
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
508
621
|
}));
|
|
509
622
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
510
623
|
this.next(conversation);
|
|
511
624
|
this.kit.changed();
|
|
512
625
|
}
|
|
513
|
-
prompt(conversation, prompt) {
|
|
626
|
+
prompt(conversation, prompt, taken) {
|
|
514
627
|
const running = conversation.running;
|
|
515
628
|
const turn = this.begin(conversation);
|
|
516
629
|
turn.prompted = true;
|
|
517
|
-
|
|
630
|
+
turn.taken = taken;
|
|
631
|
+
conversation.prompted = running.sessionId;
|
|
632
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
633
|
+
this.took(turn);
|
|
634
|
+
const notice = answeredFailure(answer);
|
|
635
|
+
if (notice !== null) {
|
|
636
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
640
|
+
}
|
|
641
|
+
}, async (error) => {
|
|
518
642
|
if (running.adapter.connection.signal.aborted) {
|
|
519
|
-
this.end(conversation, turn,
|
|
643
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
520
644
|
}
|
|
521
645
|
else {
|
|
646
|
+
this.took(turn);
|
|
522
647
|
if (conversation.open === 1 && conversation.running === running) {
|
|
523
|
-
running.
|
|
648
|
+
running.adapter.retire();
|
|
524
649
|
this.detach(conversation, running);
|
|
525
650
|
}
|
|
526
|
-
this.end(conversation, turn,
|
|
651
|
+
this.end(conversation, turn, answeredWith(error));
|
|
527
652
|
}
|
|
528
653
|
});
|
|
529
654
|
}
|
|
530
655
|
started(conversation) {
|
|
531
|
-
|
|
656
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
657
|
+
this.took(turn);
|
|
658
|
+
return turn;
|
|
532
659
|
}
|
|
533
660
|
ended(conversation) {
|
|
534
661
|
const turn = conversation.turn;
|
|
@@ -623,6 +750,11 @@ export class Conversations {
|
|
|
623
750
|
}
|
|
624
751
|
this.apply(conversation, this.started(conversation), event);
|
|
625
752
|
}
|
|
753
|
+
took(turn) {
|
|
754
|
+
const taken = turn.taken;
|
|
755
|
+
turn.taken = null;
|
|
756
|
+
taken?.();
|
|
757
|
+
}
|
|
626
758
|
apply(conversation, turn, event) {
|
|
627
759
|
switch (event.kind) {
|
|
628
760
|
case 'text':
|