@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-ce3d2bb67177c
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 +40 -39
- package/dist/acp.js +48 -105
- package/dist/agents.js +153 -43
- package/dist/api.js +34 -2
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +36 -57
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +127 -70
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +125 -0
- package/dist/conversations.js +620 -323
- package/dist/copies.js +188 -128
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +123 -17
- package/dist/git.js +11 -9
- package/dist/home.js +96 -7
- package/dist/house-cli.js +327 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +117 -104
- package/dist/logout.js +14 -13
- package/dist/mcp.js +4 -2
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +106 -50
- package/dist/scope.js +206 -0
- package/dist/secret-input.js +3 -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 +37 -31
- package/dist/skills.js +84 -15
- package/dist/transcription.js +100 -21
- package/dist/translate.js +2 -8
- package/dist/update.js +71 -50
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/how-we-work.md +13 -3
- package/package.json +4 -3
- 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/dist/blocks.js +0 -51
- package/dist/stream.js +0 -47
- 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/events.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
const COMMAND = { shown: 'command' };
|
|
2
|
+
const HIDDEN = { shown: 'hidden' };
|
|
3
|
+
const FINISHED = new Set(['completed', 'failed']);
|
|
4
|
+
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
|
+
const MESSAGE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request', send_mail: 'request' };
|
|
6
|
+
function words(line) {
|
|
7
|
+
const found = [];
|
|
8
|
+
let word = null;
|
|
9
|
+
let quote = null;
|
|
10
|
+
for (let index = 0; index < line.length; index++) {
|
|
11
|
+
const character = line[index];
|
|
12
|
+
if (quote !== null) {
|
|
13
|
+
if (character === quote)
|
|
14
|
+
quote = null;
|
|
15
|
+
else if (character === '\\' && quote === '"')
|
|
16
|
+
word += line[++index] ?? '';
|
|
17
|
+
else
|
|
18
|
+
word += character;
|
|
19
|
+
}
|
|
20
|
+
else if (character === "'" || character === '"') {
|
|
21
|
+
quote = character;
|
|
22
|
+
word ??= '';
|
|
23
|
+
}
|
|
24
|
+
else if (character === '\\') {
|
|
25
|
+
word = (word ?? '') + (line[++index] ?? '');
|
|
26
|
+
}
|
|
27
|
+
else if (/\s/.test(character)) {
|
|
28
|
+
if (word !== null)
|
|
29
|
+
found.push(word);
|
|
30
|
+
word = null;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
word = (word ?? '') + character;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (word !== null)
|
|
37
|
+
found.push(word);
|
|
38
|
+
return found;
|
|
39
|
+
}
|
|
40
|
+
export function houseMessage(line) {
|
|
41
|
+
const [program, verb, json] = words(line.trim());
|
|
42
|
+
if (program !== 'house' || verb !== 'run_command' || json === undefined)
|
|
43
|
+
return null;
|
|
44
|
+
let call;
|
|
45
|
+
try {
|
|
46
|
+
call = JSON.parse(json);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const argument = MESSAGE_TARGETS[call.command];
|
|
52
|
+
if (argument === undefined)
|
|
53
|
+
return null;
|
|
54
|
+
const target = call.arguments?.[argument];
|
|
55
|
+
return { shown: 'message', command: call.command, target: typeof target === 'string' ? target : null };
|
|
56
|
+
}
|
|
57
|
+
function decidedByLine(command, final) {
|
|
58
|
+
const line = typeof command === 'string' ? command : Array.isArray(command) ? command.at(-1) : undefined;
|
|
59
|
+
if (typeof line !== 'string')
|
|
60
|
+
return final ? COMMAND : null;
|
|
61
|
+
return houseMessage(line) ?? COMMAND;
|
|
62
|
+
}
|
|
63
|
+
function contentText(content) {
|
|
64
|
+
const text = (content ?? [])
|
|
65
|
+
.map((entry) => (entry.type === 'content' && entry.content?.type === 'text' ? entry.content.text : ''))
|
|
66
|
+
.filter((part) => part !== '')
|
|
67
|
+
.join('\n');
|
|
68
|
+
return text === '' ? null : text;
|
|
69
|
+
}
|
|
70
|
+
function plan(entries) {
|
|
71
|
+
return entries.map((entry) => ({ content: entry.content, status: entry.status }));
|
|
72
|
+
}
|
|
73
|
+
function merged(into, from) {
|
|
74
|
+
if (from === null || from === undefined)
|
|
75
|
+
return into;
|
|
76
|
+
const result = { ...into };
|
|
77
|
+
for (const [key, value] of Object.entries(from)) {
|
|
78
|
+
result[key] =
|
|
79
|
+
typeof value === 'object' && value !== null && !Array.isArray(value) && typeof into[key] === 'object'
|
|
80
|
+
? merged(into[key], value)
|
|
81
|
+
: value;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
function names(input, ids) {
|
|
86
|
+
const values = JSON.stringify(input ?? {});
|
|
87
|
+
for (const id of ids)
|
|
88
|
+
if (values.includes(id))
|
|
89
|
+
return true;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
class Reader {
|
|
93
|
+
session = null;
|
|
94
|
+
calls = new Map();
|
|
95
|
+
subagents = new Set();
|
|
96
|
+
done = new Set();
|
|
97
|
+
seenOptions = new Map();
|
|
98
|
+
requestedOptions = new Map();
|
|
99
|
+
compactions = new Set();
|
|
100
|
+
jobs = new Map();
|
|
101
|
+
read(message) {
|
|
102
|
+
if (message.method === undefined)
|
|
103
|
+
return this.answered(message.result);
|
|
104
|
+
const session = message.params?.sessionId;
|
|
105
|
+
if (session !== undefined && this.session !== null && session !== this.session) {
|
|
106
|
+
return this.elsewhere(session, message);
|
|
107
|
+
}
|
|
108
|
+
return this.own(message);
|
|
109
|
+
}
|
|
110
|
+
requested(option, value) {
|
|
111
|
+
this.requestedOptions.set(option, value);
|
|
112
|
+
}
|
|
113
|
+
refused(option) {
|
|
114
|
+
this.requestedOptions.delete(option);
|
|
115
|
+
}
|
|
116
|
+
closeTurn() {
|
|
117
|
+
const events = [];
|
|
118
|
+
for (const [id, call] of this.calls) {
|
|
119
|
+
if (call.decided?.shown === 'subagent')
|
|
120
|
+
continue;
|
|
121
|
+
if (call.decided === null)
|
|
122
|
+
events.push(...this.shown(call, this.decide(call, true)));
|
|
123
|
+
this.calls.delete(id);
|
|
124
|
+
}
|
|
125
|
+
return events;
|
|
126
|
+
}
|
|
127
|
+
answered(result) {
|
|
128
|
+
if (typeof result?.sessionId === 'string' && this.session === null)
|
|
129
|
+
this.session = result.sessionId;
|
|
130
|
+
for (const option of (result?.configOptions ?? [])) {
|
|
131
|
+
this.seenOptions.set(option.id, option.currentValue);
|
|
132
|
+
this.requestedOptions.delete(option.id);
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
elsewhere(_session, _message) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
own(message) {
|
|
140
|
+
if (message.method !== 'session/update')
|
|
141
|
+
return this.extension(message);
|
|
142
|
+
return this.update(message.params.update);
|
|
143
|
+
}
|
|
144
|
+
extension(_message) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
update(update) {
|
|
148
|
+
switch (update.sessionUpdate) {
|
|
149
|
+
case 'agent_message_chunk':
|
|
150
|
+
if (update.content.type === 'image')
|
|
151
|
+
return [{ kind: 'image', data: update.content.data, mediaType: update.content.mimeType }];
|
|
152
|
+
return update.content.type === 'text' ? [{ kind: 'text', id: update.messageId ?? null, text: update.content.text }] : [];
|
|
153
|
+
case 'agent_thought_chunk':
|
|
154
|
+
return update.content.type === 'text' ? [{ kind: 'reasoning', id: update.messageId ?? null, text: update.content.text }] : [];
|
|
155
|
+
case 'tool_call':
|
|
156
|
+
case 'tool_call_update':
|
|
157
|
+
return this.tool(update);
|
|
158
|
+
case 'plan':
|
|
159
|
+
return [{ kind: 'plan', entries: plan(update.entries) }];
|
|
160
|
+
case 'usage_update':
|
|
161
|
+
return [{ kind: 'context', used: update.used, window: update.size }];
|
|
162
|
+
case 'current_mode_update':
|
|
163
|
+
return [{ kind: 'marker', marker: 'mode', text: update.currentModeId }];
|
|
164
|
+
case 'available_commands_update':
|
|
165
|
+
return [{ kind: 'commands', commands: update.availableCommands }];
|
|
166
|
+
case 'config_option_update':
|
|
167
|
+
return [{ kind: 'options', options: update.configOptions }, ...this.optionsChanged(update.configOptions)];
|
|
168
|
+
case 'compaction_update':
|
|
169
|
+
if (update.status !== 'completed' || this.compactions.has(update.compactionId))
|
|
170
|
+
return [];
|
|
171
|
+
this.compactions.add(update.compactionId);
|
|
172
|
+
return [{ kind: 'marker', marker: 'compaction', text: '' }];
|
|
173
|
+
case 'async_task_spawned':
|
|
174
|
+
return this.job(update.asyncTaskId, update.name ?? update.description ?? '', 'running');
|
|
175
|
+
case 'async_task_state_update':
|
|
176
|
+
return RUNNING_JOB.has(update.state) ? [] : this.job(update.asyncTaskId, null, update.state);
|
|
177
|
+
default:
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
job(id, name, state) {
|
|
182
|
+
if (name !== null)
|
|
183
|
+
this.jobs.set(id, name);
|
|
184
|
+
const label = this.jobs.get(id) ?? '';
|
|
185
|
+
if (state !== 'running')
|
|
186
|
+
this.jobs.delete(id);
|
|
187
|
+
return [{ kind: 'marker', marker: 'job', text: `${label}: ${state}` }];
|
|
188
|
+
}
|
|
189
|
+
optionsChanged(options) {
|
|
190
|
+
const events = [];
|
|
191
|
+
for (const option of options) {
|
|
192
|
+
const marker = this.watchedOptions[option.id];
|
|
193
|
+
const before = this.seenOptions.get(option.id);
|
|
194
|
+
this.seenOptions.set(option.id, option.currentValue);
|
|
195
|
+
if (marker === undefined || before === undefined || before === option.currentValue)
|
|
196
|
+
continue;
|
|
197
|
+
if (this.requestedOptions.get(option.id) === option.currentValue) {
|
|
198
|
+
this.requestedOptions.delete(option.id);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
events.push({ kind: 'marker', marker, text: String(option.currentValue) });
|
|
202
|
+
}
|
|
203
|
+
return events;
|
|
204
|
+
}
|
|
205
|
+
tool(update) {
|
|
206
|
+
let call = this.calls.get(update.toolCallId);
|
|
207
|
+
if (call === undefined) {
|
|
208
|
+
if (update.sessionUpdate !== 'tool_call')
|
|
209
|
+
return [];
|
|
210
|
+
call = { id: update.toolCallId, decided: null, meta: {}, imaged: false, status: 'in_progress' };
|
|
211
|
+
this.calls.set(call.id, call);
|
|
212
|
+
}
|
|
213
|
+
if (update.kind !== undefined)
|
|
214
|
+
call.kind = update.kind;
|
|
215
|
+
if (update.title !== undefined)
|
|
216
|
+
call.title = update.title;
|
|
217
|
+
if (update.status !== undefined)
|
|
218
|
+
call.status = update.status;
|
|
219
|
+
if (update.rawInput !== undefined)
|
|
220
|
+
call.rawInput = merged(call.rawInput ?? {}, update.rawInput);
|
|
221
|
+
if (update.content !== undefined)
|
|
222
|
+
call.content = update.content;
|
|
223
|
+
call.meta = merged(call.meta, update._meta);
|
|
224
|
+
const events = [];
|
|
225
|
+
const finished = FINISHED.has(call.status);
|
|
226
|
+
if (call.decided === null) {
|
|
227
|
+
const decided = this.decide(call, finished);
|
|
228
|
+
if (decided !== null)
|
|
229
|
+
events.push(...this.shown(call, decided));
|
|
230
|
+
}
|
|
231
|
+
events.push(...this.produced(call));
|
|
232
|
+
if (call.decided?.shown === 'subagent' && finished)
|
|
233
|
+
events.push(...this.subagentFinished(call));
|
|
234
|
+
else if (finished)
|
|
235
|
+
this.calls.delete(call.id);
|
|
236
|
+
return events;
|
|
237
|
+
}
|
|
238
|
+
shown(call, decided) {
|
|
239
|
+
call.decided = decided;
|
|
240
|
+
switch (decided.shown) {
|
|
241
|
+
case 'command':
|
|
242
|
+
return [{ kind: 'command' }];
|
|
243
|
+
case 'hidden':
|
|
244
|
+
return [];
|
|
245
|
+
case 'message':
|
|
246
|
+
return [{ kind: 'message', command: decided.command, target: decided.target }];
|
|
247
|
+
case 'marker':
|
|
248
|
+
return [{ kind: 'marker', marker: decided.marker, text: decided.text }];
|
|
249
|
+
case 'subagent':
|
|
250
|
+
this.subagents.add(call.id);
|
|
251
|
+
return [{ kind: 'subagent', id: call.id, description: decided.description }];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
produced(_call) {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
subagentFinished(call) {
|
|
258
|
+
this.calls.delete(call.id);
|
|
259
|
+
return this.finish(call.id, contentText(call.content));
|
|
260
|
+
}
|
|
261
|
+
finish(id, result) {
|
|
262
|
+
if (!this.subagents.has(id) || this.done.has(id))
|
|
263
|
+
return [];
|
|
264
|
+
this.done.add(id);
|
|
265
|
+
this.calls.delete(id);
|
|
266
|
+
return [{ kind: 'subagent-done', id, result }];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
class ClaudeReader extends Reader {
|
|
270
|
+
watchedOptions = { model: 'model' };
|
|
271
|
+
tasks = new Map();
|
|
272
|
+
own(message) {
|
|
273
|
+
if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
|
|
274
|
+
return [];
|
|
275
|
+
return super.own(message);
|
|
276
|
+
}
|
|
277
|
+
update(update) {
|
|
278
|
+
if (update.sessionUpdate === 'usage_update' && update._meta?.['_claude/origin'] !== undefined) {
|
|
279
|
+
return [...super.update(update), { kind: 'ended' }];
|
|
280
|
+
}
|
|
281
|
+
if (update.sessionUpdate === 'session_info_update' && update._meta?.jetbrains?.air?.sessionFailure !== undefined) {
|
|
282
|
+
return [{ kind: 'marker', marker: 'retry', text: update._meta.jetbrains.air.sessionFailure.title }];
|
|
283
|
+
}
|
|
284
|
+
return super.update(update);
|
|
285
|
+
}
|
|
286
|
+
extension(message) {
|
|
287
|
+
if (message.method !== '_claude/sdkMessage')
|
|
288
|
+
return [];
|
|
289
|
+
const sdk = message.params.message;
|
|
290
|
+
switch (sdk.subtype) {
|
|
291
|
+
case 'task_started':
|
|
292
|
+
if (sdk.tool_use_id !== undefined)
|
|
293
|
+
this.tasks.set(sdk.task_id, sdk.tool_use_id);
|
|
294
|
+
return [];
|
|
295
|
+
case 'task_notification':
|
|
296
|
+
return sdk.tool_use_id === undefined ? [] : this.finish(sdk.tool_use_id, sdk.summary ?? null);
|
|
297
|
+
default:
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
decide(call, final) {
|
|
302
|
+
const tool = call.meta.claudeCode?.toolName;
|
|
303
|
+
if (tool === 'Agent' || tool === 'Task' || call.meta.jetbrains?.air?.subagent === true) {
|
|
304
|
+
const description = call.rawInput?.description;
|
|
305
|
+
if (description === undefined && !final)
|
|
306
|
+
return null;
|
|
307
|
+
return { shown: 'subagent', description: description ?? call.title ?? '' };
|
|
308
|
+
}
|
|
309
|
+
if (tool === 'TodoWrite' || tool === 'CronCreate')
|
|
310
|
+
return HIDDEN;
|
|
311
|
+
if (tool === 'ScheduleWakeup') {
|
|
312
|
+
if (call.rawInput === undefined)
|
|
313
|
+
return final ? COMMAND : null;
|
|
314
|
+
return call.rawInput.stop === true ? COMMAND : HIDDEN;
|
|
315
|
+
}
|
|
316
|
+
if (call.kind === 'switch_mode')
|
|
317
|
+
return { shown: 'marker', marker: 'mode', text: call.title ?? '' };
|
|
318
|
+
if (tool === 'Bash')
|
|
319
|
+
return decidedByLine(call.rawInput?.command, final);
|
|
320
|
+
if (tool === 'SendMessage' || tool === 'TaskStop') {
|
|
321
|
+
if (call.rawInput === undefined)
|
|
322
|
+
return final ? COMMAND : null;
|
|
323
|
+
return names(call.rawInput, this.tasks.keys()) ? HIDDEN : COMMAND;
|
|
324
|
+
}
|
|
325
|
+
return COMMAND;
|
|
326
|
+
}
|
|
327
|
+
produced(call) {
|
|
328
|
+
const tool = call.meta.claudeCode?.toolName;
|
|
329
|
+
if (call.status !== 'completed')
|
|
330
|
+
return [];
|
|
331
|
+
if (tool === 'CronCreate' || (tool === 'ScheduleWakeup' && call.rawInput?.stop !== true)) {
|
|
332
|
+
return [{ kind: 'marker', marker: 'job', text: `${call.rawInput?.prompt ?? call.title ?? ''}: scheduled` }];
|
|
333
|
+
}
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
subagentFinished(call) {
|
|
337
|
+
if (call.meta.claudeCode?.toolResponse?.isAsync === true)
|
|
338
|
+
return [];
|
|
339
|
+
return super.subagentFinished(call);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const COLLABORATION = new Set(['spawnAgent', 'sendInput', 'resumeAgent', 'wait', 'closeAgent', 'followupTask']);
|
|
343
|
+
class CodexReader extends Reader {
|
|
344
|
+
watchedOptions = { mode: 'mode', collaboration_mode: 'mode' };
|
|
345
|
+
children = new Map();
|
|
346
|
+
own(message) {
|
|
347
|
+
if (message.method === 'session/request_permission' && message.params.toolCall?.kind === 'switch_mode') {
|
|
348
|
+
return [{ kind: 'started' }, { kind: 'marker', marker: 'mode', text: message.params.toolCall.title ?? '' }];
|
|
349
|
+
}
|
|
350
|
+
return super.own(message);
|
|
351
|
+
}
|
|
352
|
+
update(update) {
|
|
353
|
+
switch (update.sessionUpdate) {
|
|
354
|
+
case 'session_info_update': {
|
|
355
|
+
const codex = update._meta?.codex;
|
|
356
|
+
if (codex?.threadStatus?.type === 'active')
|
|
357
|
+
return [{ kind: 'started' }];
|
|
358
|
+
if (codex?.threadStatus?.type === 'idle')
|
|
359
|
+
return [{ kind: 'ended' }];
|
|
360
|
+
if (codex?.error !== undefined)
|
|
361
|
+
return [{ kind: 'marker', marker: 'retry', text: codex.error.message ?? '' }];
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
case 'notice':
|
|
365
|
+
return update.severity === 'info' ? [{ kind: 'marker', marker: 'model', text: update.description ?? update.title }] : [];
|
|
366
|
+
case 'subagent_spawned':
|
|
367
|
+
this.children.set(update.subagentSessionId, { id: null, text: '' });
|
|
368
|
+
this.subagents.add(update.subagentSessionId);
|
|
369
|
+
return [{ kind: 'subagent', id: update.subagentSessionId, description: update.name }];
|
|
370
|
+
case 'subagent_state_update':
|
|
371
|
+
return this.finish(update.subagentSessionId, this.children.get(update.subagentSessionId)?.text || null);
|
|
372
|
+
default:
|
|
373
|
+
return super.update(update);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
elsewhere(session, message) {
|
|
377
|
+
const child = this.children.get(session);
|
|
378
|
+
const update = message.params?.update;
|
|
379
|
+
if (child === undefined || update === undefined)
|
|
380
|
+
return [];
|
|
381
|
+
if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {
|
|
382
|
+
const id = update.messageId ?? null;
|
|
383
|
+
if (child.id !== id)
|
|
384
|
+
child.text = '';
|
|
385
|
+
child.id = id;
|
|
386
|
+
child.text += update.content.text;
|
|
387
|
+
}
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
decide(call) {
|
|
391
|
+
const air = call.meta.jetbrains?.air;
|
|
392
|
+
if (COLLABORATION.has(call.title ?? ''))
|
|
393
|
+
return HIDDEN;
|
|
394
|
+
if (air?.subagent !== undefined)
|
|
395
|
+
return { shown: 'subagent', description: call.title ?? '' };
|
|
396
|
+
if (call.kind === 'switch_mode')
|
|
397
|
+
return { shown: 'marker', marker: 'mode', text: call.title ?? '' };
|
|
398
|
+
if (call.kind === 'execute')
|
|
399
|
+
return decidedByLine(call.rawInput?.command, true);
|
|
400
|
+
if (call.title === 'Image generation')
|
|
401
|
+
return HIDDEN;
|
|
402
|
+
return COMMAND;
|
|
403
|
+
}
|
|
404
|
+
produced(call) {
|
|
405
|
+
if (call.imaged || call.kind === 'read')
|
|
406
|
+
return [];
|
|
407
|
+
const image = (call.content ?? []).find((entry) => entry.type === 'content' && entry.content?.type === 'image');
|
|
408
|
+
if (image === undefined)
|
|
409
|
+
return [];
|
|
410
|
+
call.imaged = true;
|
|
411
|
+
return [{ kind: 'image', data: image.content.data, mediaType: image.content.mimeType }];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
class GrokReader extends Reader {
|
|
415
|
+
watchedOptions = {};
|
|
416
|
+
children = new Map();
|
|
417
|
+
models = [];
|
|
418
|
+
model = null;
|
|
419
|
+
answered(result) {
|
|
420
|
+
if (result?.models !== undefined)
|
|
421
|
+
this.offered(result.models);
|
|
422
|
+
return super.answered(result);
|
|
423
|
+
}
|
|
424
|
+
read(message) {
|
|
425
|
+
const session = message.params?.sessionId;
|
|
426
|
+
if (this.session !== null && session !== undefined && session !== this.session && !this.children.has(session))
|
|
427
|
+
return [];
|
|
428
|
+
if (message.method === '_x.ai/queue/changed') {
|
|
429
|
+
return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
|
|
430
|
+
}
|
|
431
|
+
if (message.params?.update?.sessionUpdate === 'turn_completed') {
|
|
432
|
+
return session === this.session ? [{ kind: 'ended' }] : [];
|
|
433
|
+
}
|
|
434
|
+
if (message.method === '_x.ai/models/update') {
|
|
435
|
+
this.offered(message.params);
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
return super.read(message);
|
|
439
|
+
}
|
|
440
|
+
offered(models) {
|
|
441
|
+
this.models = models.availableModels ?? this.models;
|
|
442
|
+
this.model = models.currentModelId ?? this.model;
|
|
443
|
+
}
|
|
444
|
+
window() {
|
|
445
|
+
return this.models.find((model) => model.modelId === this.model)?._meta?.totalContextTokens ?? null;
|
|
446
|
+
}
|
|
447
|
+
extension(message) {
|
|
448
|
+
const update = message.params?.update;
|
|
449
|
+
switch (message.method) {
|
|
450
|
+
case '_x.ai/task_backgrounded':
|
|
451
|
+
return this.job(update.task_id, update.description ?? update.command ?? '', 'running');
|
|
452
|
+
case '_x.ai/task_completed':
|
|
453
|
+
return this.job(update.task_snapshot.task_id, null, update.task_snapshot.exit_code === 0 ? 'completed' : 'failed');
|
|
454
|
+
case '_x.ai/scheduled_task_created':
|
|
455
|
+
return [{ kind: 'marker', marker: 'job', text: `${update.prompt}: next run ${update.next_fire_at}` }];
|
|
456
|
+
case '_x.ai/session_notification':
|
|
457
|
+
return this.notification(update);
|
|
458
|
+
default:
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
notification(update) {
|
|
463
|
+
switch (update.sessionUpdate) {
|
|
464
|
+
case 'response_completed': {
|
|
465
|
+
const usage = update.usage;
|
|
466
|
+
const window = this.window();
|
|
467
|
+
if (window === null)
|
|
468
|
+
return [];
|
|
469
|
+
const used = usage.input_tokens + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + usage.output_tokens;
|
|
470
|
+
return [{ kind: 'context', used, window }];
|
|
471
|
+
}
|
|
472
|
+
case 'subagent_spawned': {
|
|
473
|
+
const call = [...this.calls.values()].find((candidate) => candidate.decided?.shown === 'subagent' &&
|
|
474
|
+
![...this.children.values()].includes(candidate.id) &&
|
|
475
|
+
candidate.decided.description === update.description) ??
|
|
476
|
+
[...this.calls.values()].find((candidate) => candidate.decided?.shown === 'subagent' && ![...this.children.values()].includes(candidate.id));
|
|
477
|
+
this.children.set(update.child_session_id, call?.id ?? null);
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
case 'subagent_finished': {
|
|
481
|
+
const call = this.children.get(update.child_session_id);
|
|
482
|
+
return typeof call === 'string' ? this.finish(call, update.output ?? null) : [];
|
|
483
|
+
}
|
|
484
|
+
case 'auto_compact_completed':
|
|
485
|
+
return [{ kind: 'marker', marker: 'compaction', text: '' }];
|
|
486
|
+
case 'model_auto_switched':
|
|
487
|
+
this.model = update.new_model_id;
|
|
488
|
+
return [{ kind: 'marker', marker: 'model', text: update.new_model_id }];
|
|
489
|
+
case 'retry_state':
|
|
490
|
+
return [{ kind: 'marker', marker: 'retry', text: update.message ?? update.reason ?? update.error_type ?? '' }];
|
|
491
|
+
default:
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
decide(call) {
|
|
496
|
+
const tool = (call.meta['x.ai/tool'] ?? {});
|
|
497
|
+
if (tool.name === 'spawn_subagent' || tool.kind === 'task') {
|
|
498
|
+
return { shown: 'subagent', description: call.rawInput?.description ?? call.title ?? '' };
|
|
499
|
+
}
|
|
500
|
+
if (tool.name === 'todo_write' || tool.name === 'scheduler_create')
|
|
501
|
+
return HIDDEN;
|
|
502
|
+
if (tool.name === 'enter_plan_mode' || tool.name === 'exit_plan_mode') {
|
|
503
|
+
return { shown: 'marker', marker: 'mode', text: tool.name };
|
|
504
|
+
}
|
|
505
|
+
if (tool.name === 'kill_command_or_subagent')
|
|
506
|
+
return names(call.rawInput, this.children.keys()) ? HIDDEN : COMMAND;
|
|
507
|
+
if (tool.name === 'run_terminal_command')
|
|
508
|
+
return decidedByLine(call.rawInput?.command, true);
|
|
509
|
+
return COMMAND;
|
|
510
|
+
}
|
|
511
|
+
subagentFinished(_call) {
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
export function answeredFailure(answer) {
|
|
516
|
+
const failure = answer._meta?.jetbrains?.air?.sessionFailure;
|
|
517
|
+
return typeof failure?.title === 'string' ? { title: failure.title, limit: failure.category === 'limit' } : null;
|
|
518
|
+
}
|
|
519
|
+
export function readerFor(kind) {
|
|
520
|
+
if (kind === 'claude-agent-acp')
|
|
521
|
+
return new ClaudeReader();
|
|
522
|
+
if (kind === 'grok-build')
|
|
523
|
+
return new GrokReader();
|
|
524
|
+
return new CodexReader();
|
|
525
|
+
}
|
package/dist/files.js
CHANGED
|
@@ -1,22 +1,128 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir, rename, rm } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
|
+
import { Readable, Transform } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { parseDocument } from 'yaml';
|
|
8
|
+
import { retryDelay } from './api.js';
|
|
9
|
+
import { Unsettled } from './mcp.js';
|
|
3
10
|
import { relayed } from './relay.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const STALLED_ROUNDS = 8;
|
|
12
|
+
const LINK = /\]\((\/rooms\/[^/)\s]+\/[^)\s]+)\)/gu;
|
|
13
|
+
export function linkedPaths(text) {
|
|
14
|
+
return [...new Set(Array.from(text.matchAll(LINK), (link) => link[1]))];
|
|
15
|
+
}
|
|
16
|
+
export function inspectedFile(answer) {
|
|
17
|
+
const document = parseDocument(answer);
|
|
18
|
+
if (document.errors.length > 0)
|
|
19
|
+
return null;
|
|
20
|
+
const inspected = document.toJS();
|
|
21
|
+
if (typeof inspected?.download !== 'string')
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
download: inspected.download,
|
|
25
|
+
sha256: String(inspected.sha256),
|
|
26
|
+
media_type: typeof inspected.media_type === 'string' ? inspected.media_type : null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function paused(milliseconds) {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
31
|
+
}
|
|
32
|
+
export function storeRefusal(act, status) {
|
|
33
|
+
return new Error(`the ${act} answered ${status}; ${act} the file again`);
|
|
34
|
+
}
|
|
35
|
+
export async function uploaded(declared, uploading) {
|
|
36
|
+
let upload = declared;
|
|
37
|
+
let idle = 0;
|
|
38
|
+
let stalled = 0;
|
|
39
|
+
for (;;) {
|
|
40
|
+
let sent = 0;
|
|
41
|
+
let failure = null;
|
|
42
|
+
for (const { part, url } of upload.parts) {
|
|
43
|
+
const start = (part - 1) * upload.part_bytes;
|
|
44
|
+
try {
|
|
45
|
+
await uploading.send(url, start, Math.min(start + upload.part_bytes, uploading.bytes));
|
|
46
|
+
sent++;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
failure = error;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (sent > 0) {
|
|
54
|
+
idle = 0;
|
|
55
|
+
stalled = 0;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (failure !== null && ++stalled >= STALLED_ROUNDS)
|
|
59
|
+
throw failure;
|
|
60
|
+
// A round that sent no part, its store unreachable or not yet started, is followed by the next finishing call on the jittered curve.
|
|
61
|
+
await paused(retryDelay(idle++));
|
|
62
|
+
}
|
|
63
|
+
let answer = await finishing(uploading, upload.id);
|
|
64
|
+
while ('checking' in answer) {
|
|
65
|
+
// A finishing call that finds House still checking the stored bytes is sent again after five seconds, the cadence at which House takes up a check it handed over.
|
|
66
|
+
await paused(5_000);
|
|
67
|
+
answer = await finishing(uploading, upload.id);
|
|
68
|
+
}
|
|
69
|
+
if (!('upload' in answer))
|
|
70
|
+
return answer;
|
|
71
|
+
upload = answer.upload;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function finishing(uploading, upload) {
|
|
75
|
+
for (let unsettled = 1;; unsettled++) {
|
|
76
|
+
try {
|
|
77
|
+
return await uploading.finish(upload);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!(error instanceof Unsettled) || unsettled === STALLED_ROUNDS)
|
|
14
81
|
throw error;
|
|
82
|
+
}
|
|
83
|
+
// A finishing call House did not settle, unreached or refused short of final, is sent again on the jittered curve as a conversation resends it, so a dropped connection or a House restart resumes the upload instead of ending it.
|
|
84
|
+
await paused(retryDelay(unsettled - 1));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function drained(body) {
|
|
88
|
+
for await (const _chunk of body)
|
|
89
|
+
;
|
|
90
|
+
}
|
|
91
|
+
export async function downloaded(address, fetching, destination, name) {
|
|
92
|
+
for (let asked = 1;; asked++) {
|
|
93
|
+
const { download, sha256 } = await address();
|
|
94
|
+
const answer = await fetching(download);
|
|
95
|
+
if (answer.status === 403 && asked === 1) {
|
|
96
|
+
await drained(answer.body);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (answer.status !== 200) {
|
|
100
|
+
await drained(answer.body);
|
|
101
|
+
throw storeRefusal('download', answer.status);
|
|
102
|
+
}
|
|
103
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
104
|
+
const partial = join(dirname(destination), `.${basename(destination)}.${randomUUID()}`);
|
|
105
|
+
const digest = createHash('sha256');
|
|
106
|
+
const hashed = new Transform({
|
|
107
|
+
transform(chunk, _encoding, done) {
|
|
108
|
+
digest.update(chunk);
|
|
109
|
+
done(null, chunk);
|
|
110
|
+
},
|
|
15
111
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
112
|
+
try {
|
|
113
|
+
await pipeline(answer.body, hashed, createWriteStream(partial, { flags: 'wx' }));
|
|
114
|
+
if (digest.digest('hex') !== sha256)
|
|
115
|
+
throw new Error(`${name} did not arrive whole; download it again`);
|
|
116
|
+
await rename(partial, destination);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
await rm(partial, { force: true });
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
20
123
|
}
|
|
21
|
-
|
|
124
|
+
}
|
|
125
|
+
export async function fetched(url, signal) {
|
|
126
|
+
const answer = await fetch(relayed(url), { signal });
|
|
127
|
+
return { status: answer.status, body: answer.body ?? Readable.from([]) };
|
|
22
128
|
}
|