@agentshouse/kit 0.1.0-alpha.9 → 0.2.1-alpha.1

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.
Files changed (93) hide show
  1. package/README.md +14 -1740
  2. package/dist/acp.js +120 -0
  3. package/dist/agents.js +90 -0
  4. package/dist/api.js +45 -0
  5. package/dist/blocks.js +51 -0
  6. package/dist/bridge.js +124 -0
  7. package/dist/clis.js +110 -0
  8. package/dist/conversations.js +538 -0
  9. package/dist/files.js +21 -0
  10. package/dist/home.js +24 -0
  11. package/dist/house-main.js +92 -0
  12. package/dist/instructions.js +28 -0
  13. package/dist/kit-main.js +16 -0
  14. package/dist/login.js +112 -0
  15. package/dist/resident.js +66 -0
  16. package/dist/secret-input.js +16 -0
  17. package/dist/sign-in.js +83 -0
  18. package/dist/stream.js +47 -0
  19. package/package.json +34 -10
  20. package/bin/connect-linux.sh +0 -134
  21. package/bin/kit.js +0 -1023
  22. package/bootstrap.json +0 -17
  23. package/src/acp.js +0 -99
  24. package/src/adapter-mcp.js +0 -44
  25. package/src/adapter.js +0 -252
  26. package/src/agent-logins.js +0 -65
  27. package/src/agents.js +0 -504
  28. package/src/apply.js +0 -29
  29. package/src/attach.js +0 -52
  30. package/src/binding.js +0 -90
  31. package/src/blocks.js +0 -156
  32. package/src/bridge.js +0 -449
  33. package/src/bytes.js +0 -89
  34. package/src/channel.js +0 -37
  35. package/src/command.js +0 -109
  36. package/src/configuration.js +0 -465
  37. package/src/connector-login.js +0 -91
  38. package/src/connector.js +0 -107
  39. package/src/continuation.js +0 -61
  40. package/src/contract.js +0 -363
  41. package/src/conversation-originals.js +0 -143
  42. package/src/custom/authorization.js +0 -63
  43. package/src/declaration.js +0 -35
  44. package/src/delivery.js +0 -191
  45. package/src/directory.js +0 -102
  46. package/src/draft.js +0 -89
  47. package/src/google/calendar.js +0 -177
  48. package/src/google/declared.js +0 -146
  49. package/src/google/drive.js +0 -435
  50. package/src/google/gateway.js +0 -166
  51. package/src/google/gmail.js +0 -249
  52. package/src/google/id.js +0 -1
  53. package/src/google/oauth.js +0 -273
  54. package/src/google/source.js +0 -25
  55. package/src/hook.js +0 -376
  56. package/src/host.js +0 -121
  57. package/src/house-main.js +0 -79
  58. package/src/house.js +0 -1090
  59. package/src/inbound.js +0 -175
  60. package/src/instance/google.js +0 -1
  61. package/src/kit-work-stream.js +0 -84
  62. package/src/launcher-main.js +0 -19
  63. package/src/launcher.js +0 -276
  64. package/src/local-files.js +0 -113
  65. package/src/login.js +0 -274
  66. package/src/mcp.js +0 -71
  67. package/src/migrator.js +0 -248
  68. package/src/mount.js +0 -582
  69. package/src/operation.js +0 -20
  70. package/src/original.js +0 -631
  71. package/src/peer.js +0 -124
  72. package/src/plan.js +0 -57
  73. package/src/question.js +0 -316
  74. package/src/refusal.js +0 -79
  75. package/src/repository.js +0 -521
  76. package/src/session-bin/house +0 -2
  77. package/src/settlement.js +0 -57
  78. package/src/setup.js +0 -190
  79. package/src/source-originals.js +0 -116
  80. package/src/source.js +0 -286
  81. package/src/starter-set/grilling/LICENSE +0 -21
  82. package/src/starter-set/grilling/SKILL.md +0 -30
  83. package/src/starter-set/grilling/agents/openai.yaml +0 -3
  84. package/src/starter-set.js +0 -17
  85. package/src/state.js +0 -157
  86. package/src/sync-library.js +0 -293
  87. package/src/sync-state.js +0 -120
  88. package/src/sync.js +0 -482
  89. package/src/telegram.js +0 -1045
  90. package/src/terminal.js +0 -57
  91. package/src/tmux.js +0 -119
  92. package/src/worker.js +0 -925
  93. package/src/workspace.js +0 -500
package/src/peer.js DELETED
@@ -1,124 +0,0 @@
1
- import { connect } from "node:net";
2
- import { setTimeout as delay } from "node:timers/promises";
3
- import { readFrames, writeFrame } from "./channel.js";
4
- import { BRIDGE_RECONNECT_MAX_MS, BRIDGE_RECONNECT_MS } from "./contract.js";
5
- import { KitRefusal } from "./refusal.js";
6
-
7
- function connected(path) {
8
- return new Promise((resolve, reject) => {
9
- const socket = connect(path);
10
- socket.once("connect", () => resolve(socket));
11
- socket.once("error", reject);
12
- });
13
- }
14
-
15
- export function bridgePeer(options) {
16
- const outbound = [];
17
- const drains = [];
18
- let socket = null;
19
- let ready = false;
20
- let ended = false;
21
- let greeting = null;
22
- let refuse;
23
- const refused = new Promise((_, reject) => {
24
- refuse = reject;
25
- });
26
-
27
- function flush() {
28
- while (ready && socket !== null && outbound.length > 0) {
29
- if (socket.destroyed || socket.writableEnded) {
30
- socket = null;
31
- return;
32
- }
33
- options.onSend?.(outbound[0]);
34
- writeFrame(socket, outbound[0]);
35
- outbound.shift();
36
- }
37
- if (outbound.length === 0) for (const settle of drains.splice(0)) settle();
38
- }
39
-
40
- function received(frame) {
41
- if (greeting === null) {
42
- options.onFrame?.(frame);
43
- return;
44
- }
45
- const settle = greeting;
46
- greeting = null;
47
- settle(frame);
48
- }
49
-
50
- async function attach(first) {
51
- let backoff = BRIDGE_RECONNECT_MS;
52
- const retry = async () => {
53
- if (first) throw new KitRefusal("kit_bridge_unavailable", options.socket);
54
- await delay(backoff);
55
- backoff = Math.min(backoff * 2, BRIDGE_RECONNECT_MAX_MS);
56
- };
57
- while (!ended) {
58
- let opened;
59
- try {
60
- opened = await connected(options.socket);
61
- } catch {
62
- await retry();
63
- continue;
64
- }
65
- socket = opened;
66
- ready = false;
67
- opened.on("error", () => opened.destroy());
68
- const answer = new Promise((resolve) => {
69
- greeting = resolve;
70
- });
71
- const departure = new Promise((resolve) => opened.once("close", () => resolve(null)));
72
- readFrames(opened, { onFrame: received, onRefusal: () => opened.destroy() });
73
- writeFrame(opened, {
74
- type: "hello",
75
- role: options.role,
76
- nonce: options.nonce,
77
- ...options.hello(),
78
- });
79
- const greeted = await Promise.race([answer, departure]);
80
- if (greeted === null) {
81
- greeting = null;
82
- socket = null;
83
- await retry();
84
- continue;
85
- }
86
- if (!options.greeted(greeted)) {
87
- ended = true;
88
- socket = null;
89
- opened.destroy();
90
- throw new KitRefusal("kit_bridge_refused", typeof greeted.code === "string" ? greeted.code : null);
91
- }
92
- ready = true;
93
- flush();
94
- void keep(opened).catch(refuse);
95
- return greeted;
96
- }
97
- return null;
98
- }
99
-
100
- async function keep(current) {
101
- if (!current.destroyed) await new Promise((resolve) => current.once("close", resolve));
102
- if (socket === current) socket = null;
103
- ready = false;
104
- if (ended) return;
105
- await attach(false);
106
- }
107
-
108
- return {
109
- refused,
110
- open: () => attach(options.retryInitial !== true),
111
- send(frame) {
112
- outbound.push(frame);
113
- flush();
114
- },
115
- drained: () => outbound.length === 0 ? Promise.resolve() : new Promise((resolve) => drains.push(resolve)),
116
- end() {
117
- ended = true;
118
- const current = socket;
119
- socket = null;
120
- if (current === null || current.destroyed || current.writableEnded) return Promise.resolve();
121
- return new Promise((resolve) => current.end(resolve));
122
- },
123
- };
124
- }
package/src/plan.js DELETED
@@ -1,57 +0,0 @@
1
- const PLAN_INTERVAL_MS = 500;
2
-
3
- function steps(entries) {
4
- if (!Array.isArray(entries) || entries.length === 0) return null;
5
- return entries.map((entry) => ({ label: entry.content, status: entry.status }));
6
- }
7
-
8
- export function observedPlans(options) {
9
- const intervalMs = options.intervalMs ?? PLAN_INTERVAL_MS;
10
- const now = options.now ?? (() => Date.now());
11
- let timer = null;
12
- let sentAt = null;
13
- let sent = null;
14
- let latest = null;
15
- let completed = false;
16
-
17
- function write(terminal) {
18
- if (latest === null) return;
19
- const serialized = JSON.stringify(latest);
20
- if (!terminal && serialized === sent) return;
21
- sent = serialized;
22
- sentAt = now();
23
- options.send({ steps: latest, terminal });
24
- }
25
-
26
- function cancel() {
27
- if (timer === null) return;
28
- clearTimeout(timer);
29
- timer = null;
30
- }
31
-
32
- return {
33
- observe(entries) {
34
- if (completed) return;
35
- const observed = steps(entries);
36
- if (observed === null) return;
37
- latest = observed;
38
- if (timer !== null) return;
39
- const wait = sentAt === null ? 0 : sentAt + intervalMs - now();
40
- if (wait <= 0) {
41
- write(false);
42
- return;
43
- }
44
- timer = setTimeout(() => {
45
- timer = null;
46
- write(false);
47
- }, wait);
48
- timer.unref?.();
49
- },
50
- complete() {
51
- if (completed) return;
52
- completed = true;
53
- cancel();
54
- write(true);
55
- },
56
- };
57
- }
package/src/question.js DELETED
@@ -1,316 +0,0 @@
1
- import {
2
- ELICITATION_FORM_MODE,
3
- ELICITATION_FREE_TEXT_META,
4
- ELICITATION_SECRET_META,
5
- ELICITATION_URL_MODE,
6
- SECRET_CEREMONY_STEPS_MAX,
7
- SECRET_STEP_LABEL_MAX,
8
- SECRET_STEP_TEXT_MAX,
9
- NATIVE_CODEX_META,
10
- NATIVE_CUSTOM_ANSWER_META,
11
- NATIVE_OTHER_OPTION,
12
- NATIVE_USER_NOTE_ROLE,
13
- INTERACTION_OPTIONS_MAX,
14
- INTERACTION_PROPERTY_MAX,
15
- } from "./contract.js";
16
-
17
- function declared(value) {
18
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
19
- }
20
-
21
- function withoutNativeMeta(value) {
22
- if (Array.isArray(value)) return value.map(withoutNativeMeta);
23
- const plain = declared(value);
24
- if (plain === null) return value;
25
- return Object.fromEntries(
26
- Object.entries(plain)
27
- .filter(([key]) => key !== "_meta")
28
- .map(([key, nested]) => [key, withoutNativeMeta(nested)]),
29
- );
30
- }
31
-
32
- function nativeCompanion(declaration) {
33
- const meta = declared(declaration?._meta);
34
- if (meta === null) return null;
35
- const custom = declared(meta[NATIVE_CUSTOM_ANSWER_META]);
36
- if (typeof custom?.questionId === "string") {
37
- return { question: custom.questionId, other: null };
38
- }
39
- const codex = declared(meta[NATIVE_CODEX_META]);
40
- if (codex?.role === NATIVE_USER_NOTE_ROLE && typeof codex.questionId === "string") {
41
- return { question: codex.questionId, other: NATIVE_OTHER_OPTION };
42
- }
43
- return null;
44
- }
45
-
46
- function nativeSecret(declaration) {
47
- return declared(declaration?._meta)?.[NATIVE_CODEX_META]?.isSecret === true;
48
- }
49
-
50
- function choiceHolder(declaration) {
51
- return declaration?.type === "array" ? declared(declaration.items) : declared(declaration);
52
- }
53
-
54
- function choiceValues(declaration) {
55
- const holder = choiceHolder(declaration);
56
- const titled = holder?.oneOf ?? holder?.anyOf;
57
- if (Array.isArray(titled)) {
58
- return titled.map((option) => option?.const).filter((value) => typeof value === "string");
59
- }
60
- if (Array.isArray(holder?.enum)) {
61
- return holder.enum.filter((value) => typeof value === "string");
62
- }
63
- return [];
64
- }
65
-
66
- function withoutChoice(holder, dropped) {
67
- if (Array.isArray(holder.oneOf)) {
68
- return { ...holder, oneOf: holder.oneOf.filter((option) => option?.const !== dropped) };
69
- }
70
- if (Array.isArray(holder.anyOf)) {
71
- return { ...holder, anyOf: holder.anyOf.filter((option) => option?.const !== dropped) };
72
- }
73
- if (Array.isArray(holder.enum)) {
74
- return { ...holder, enum: holder.enum.filter((value) => value !== dropped) };
75
- }
76
- return holder;
77
- }
78
-
79
- function withoutOtherChoice(declaration, dropped) {
80
- const holder = choiceHolder(declaration);
81
- if (dropped === null || holder === null) return declaration;
82
- if (declaration.type === "array") {
83
- return { ...declaration, items: withoutChoice(holder, dropped) };
84
- }
85
- return withoutChoice(holder, dropped);
86
- }
87
-
88
- function forwarded(declaration, name, marked) {
89
- const plain = declared(declaration);
90
- if (plain === null) return declaration;
91
- const titled =
92
- typeof plain.title === "string" && plain.title !== "" ? plain : { ...plain, title: name };
93
- return marked ? { ...titled, _meta: { [ELICITATION_FREE_TEXT_META]: true } } : titled;
94
- }
95
-
96
- function foldedConventions(properties) {
97
- const companions = new Map();
98
- const conventions = new Map();
99
- for (const [name, declaration] of Object.entries(properties)) {
100
- const companion = nativeCompanion(declaration);
101
- if (companion === null || !Object.hasOwn(properties, companion.question)) continue;
102
- companions.set(name, companion.question);
103
- conventions.set(companion.question, {
104
- companion: name,
105
- other: companion.other,
106
- options: choiceValues(withoutOtherChoice(properties[companion.question], companion.other)),
107
- });
108
- }
109
- return { companions, conventions };
110
- }
111
-
112
- function bounded(text, limit, fallback) {
113
- const said = typeof text === "string" ? text.trim() : "";
114
- return said === "" ? fallback : said.slice(0, limit);
115
- }
116
-
117
- const VISIT_LABEL = "Open the provider flow";
118
- const COLLECT_LABEL = "Paste what it gives you";
119
-
120
- export function elicitedVisit(params, correlation) {
121
- if (params?.mode !== ELICITATION_URL_MODE || typeof params.url !== "string") return null;
122
- let opened;
123
- try {
124
- opened = new URL(params.url);
125
- } catch {
126
- return null;
127
- }
128
- if (opened.protocol !== "https:") return null;
129
- return {
130
- form: {
131
- correlation: correlation.correlation,
132
- provider_turn: correlation.providerTurn,
133
- message: bounded(params.message, SECRET_STEP_TEXT_MAX, VISIT_LABEL),
134
- requested_schema: {
135
- type: "object",
136
- properties: {
137
- visited: { type: "boolean", title: bounded(params.message, 200, VISIT_LABEL) },
138
- },
139
- },
140
- _meta: { [ELICITATION_SECRET_META]: true },
141
- },
142
- ceremony: {
143
- steps: [
144
- {
145
- kind: "visit",
146
- label: bounded(params.message, SECRET_STEP_LABEL_MAX, VISIT_LABEL),
147
- url: params.url,
148
- },
149
- ],
150
- },
151
- convention: {},
152
- };
153
- }
154
-
155
- function secretCeremony(message, properties) {
156
- const steps = [];
157
- const shown = bounded(message, SECRET_STEP_TEXT_MAX, "");
158
- if (shown !== "") steps.push({ kind: "show", label: "What the Agent is asking", text: shown });
159
- for (const [name, declaration] of Object.entries(properties)) {
160
- if (steps.length >= SECRET_CEREMONY_STEPS_MAX) break;
161
- const property = declared(declaration);
162
- steps.push({
163
- kind: "collect",
164
- label: bounded(property?.title, SECRET_STEP_LABEL_MAX, COLLECT_LABEL),
165
- name,
166
- ...(typeof property?.description === "string" && property.description !== ""
167
- ? { description: property.description.slice(0, SECRET_STEP_TEXT_MAX) }
168
- : {}),
169
- });
170
- }
171
- return steps.length === 0 ? null : { steps };
172
- }
173
-
174
- export function elicitedForm(params, correlation) {
175
- if (params?.mode !== ELICITATION_FORM_MODE) return null;
176
- const schema = declared(params.requestedSchema);
177
- if (schema === null) return null;
178
- const properties = declared(schema.properties) ?? {};
179
- const { companions, conventions } = foldedConventions(properties);
180
- const carried = {};
181
- for (const [name, declaration] of Object.entries(properties)) {
182
- if (companions.has(name)) continue;
183
- const convention = conventions.get(name);
184
- const listed =
185
- convention === undefined ? declaration : withoutOtherChoice(declaration, convention.other);
186
- carried[name] = forwarded(withoutNativeMeta(listed), name, convention !== undefined);
187
- }
188
- const { _meta: _schemaMeta, properties: _properties, required, ...rest } = schema;
189
- const secret = Object.values(properties).some(nativeSecret);
190
- const ceremony = secret ? secretCeremony(params.message, carried) : null;
191
- if (secret && ceremony === null) return null;
192
- return {
193
- ...(ceremony === null ? {} : { ceremony }),
194
- form: {
195
- correlation: correlation.correlation,
196
- provider_turn: correlation.providerTurn,
197
- message: typeof params.message === "string" ? params.message : "",
198
- requested_schema: {
199
- ...withoutNativeMeta(rest),
200
- properties: carried,
201
- ...(Array.isArray(required)
202
- ? { required: required.filter((name) => !companions.has(name)) }
203
- : {}),
204
- },
205
- ...(secret ? { _meta: { [ELICITATION_SECRET_META]: true } } : {}),
206
- },
207
- convention: Object.fromEntries(conventions),
208
- };
209
- }
210
-
211
- function nativeAnswer(content, name, convention, value) {
212
- if (Array.isArray(value)) {
213
- content[name] = value.filter((item) => convention.options.includes(item));
214
- const typed = value.filter((item) => !convention.options.includes(item));
215
- if (typed.length > 0) content[convention.companion] = typed.join(", ");
216
- return;
217
- }
218
- if (typeof value !== "string" || convention.options.includes(value)) {
219
- content[name] = value;
220
- return;
221
- }
222
- content[convention.companion] = value;
223
- if (convention.other !== null) content[name] = convention.other;
224
- }
225
-
226
- export function elicitationResponse(convention, response) {
227
- if (response?.action !== "accept") return { action: "decline" };
228
- const content = {};
229
- for (const [name, value] of Object.entries(response.content)) {
230
- const native = convention[name];
231
- if (native === undefined) content[name] = value;
232
- else nativeAnswer(content, name, native, value);
233
- }
234
- return { action: "accept", content };
235
- }
236
-
237
- function grokOption(option) {
238
- if (typeof option?.label !== "string" || option.label.length === 0) return null;
239
- return {
240
- const: option.label,
241
- title: option.label,
242
- ...(typeof option.description === "string" && option.description.length > 0
243
- ? { description: option.description }
244
- : {}),
245
- };
246
- }
247
-
248
- export function grokForm(params, correlation) {
249
- if (params?.mode !== "default" && params?.mode !== "plan") return null;
250
- if (
251
- !Array.isArray(params.questions) ||
252
- params.questions.length === 0 ||
253
- params.questions.length > INTERACTION_PROPERTY_MAX
254
- ) {
255
- return null;
256
- }
257
- const properties = {};
258
- const questions = {};
259
- const named = new Set();
260
- for (const [position, question] of params.questions.entries()) {
261
- if (
262
- typeof question?.question !== "string" ||
263
- question.question.length === 0 ||
264
- named.has(question.question) ||
265
- !Array.isArray(question.options) ||
266
- question.options.length > INTERACTION_OPTIONS_MAX
267
- ) {
268
- return null;
269
- }
270
- const options = question.options.map(grokOption);
271
- if (options.some((option) => option === null)) return null;
272
- const labels = options.map((option) => option.const);
273
- if (new Set(labels).size !== labels.length) return null;
274
- const name = `question_${position}`;
275
- const choices = options.length === 0 ? {} : { oneOf: options };
276
- properties[name] =
277
- question.multiSelect === true
278
- ? {
279
- type: "array",
280
- title: question.question,
281
- items: { type: "string", ...choices },
282
- uniqueItems: true,
283
- }
284
- : { type: "string", title: question.question, ...choices };
285
- questions[name] = question.question;
286
- named.add(question.question);
287
- }
288
- return {
289
- form: {
290
- correlation: correlation.correlation,
291
- provider_turn: correlation.providerTurn,
292
- message: `Grok Build needs your input before it continues${params.mode === "plan" ? " its plan" : ""}.`,
293
- requested_schema: {
294
- type: "object",
295
- properties,
296
- required: Object.keys(properties),
297
- },
298
- },
299
- convention: questions,
300
- };
301
- }
302
-
303
- export function grokResponse(convention, response) {
304
- if (response?.action !== "accept") return { outcome: "cancelled" };
305
- const answers = {};
306
- for (const [name, question] of Object.entries(convention)) {
307
- const value = response.content?.[name];
308
- if (typeof value === "string") answers[question] = [value];
309
- else if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
310
- answers[question] = value;
311
- } else {
312
- return { outcome: "cancelled" };
313
- }
314
- }
315
- return { outcome: "accepted", answers };
316
- }
package/src/refusal.js DELETED
@@ -1,79 +0,0 @@
1
- export class KitRefusal extends Error {
2
- constructor(code, detail = null) {
3
- super(detail === null ? code : `${code}: ${detail}`);
4
- this.code = code;
5
- }
6
- }
7
-
8
- export class EpochReportRequired extends Error {
9
- constructor(epoch) {
10
- super(`epoch_report_required: ${epoch}`);
11
- this.code = "epoch_report_required";
12
- }
13
- }
14
-
15
- export class HousePending extends Error {
16
- constructor(pending) {
17
- super("pending");
18
- this.pending = pending;
19
- this.retryAfterSeconds = pending.retry_after;
20
- }
21
- }
22
-
23
- function typedCode(answer) {
24
- const code = answer?.error?.code;
25
- return typeof code === "string" && code !== "" ? code : null;
26
- }
27
-
28
- export function houseRefusal(status, answer, path = null) {
29
- const code = typedCode(answer);
30
- if (status >= 500 && code === null) return new KitRefusal("kit_house_unavailable", path);
31
- if (status === 409 && code === "epoch_report_required") {
32
- return new EpochReportRequired(answer.error.log_epoch);
33
- }
34
- const refusal = new KitRefusal(code ?? `house_http_${status}`, answer?.error?.declaration ?? null);
35
- refusal.status = status;
36
- refusal.payload = answer;
37
- return refusal;
38
- }
39
-
40
- export function housePending(answer) {
41
- const pending = answer?.pending;
42
- if (typeof pending !== "object" || pending === null || Array.isArray(pending)) return null;
43
- if (!Number.isSafeInteger(pending.retry_after) || pending.retry_after < 1) return null;
44
- return new HousePending(pending);
45
- }
46
-
47
- export function throwHouseAnswer(status, answer, path = null) {
48
- if (status === 202) {
49
- const pending = housePending(answer);
50
- if (pending !== null) throw pending;
51
- throw new KitRefusal("kit_house_unavailable", path);
52
- }
53
- if (status < 200 || status >= 300) throw houseRefusal(status, answer, path);
54
- }
55
-
56
- export function houseAnswered(error) {
57
- return error instanceof KitRefusal && error.status !== undefined;
58
- }
59
-
60
- export function houseAnsweredFailure(error) {
61
- return houseAnswered(error) && error.status >= 500;
62
- }
63
-
64
- export function houseFailed(error) {
65
- return (
66
- error instanceof HousePending ||
67
- (houseAnsweredFailure(error) && error.payload.error.retryable === true) ||
68
- (error instanceof KitRefusal && error.code === "kit_house_unavailable")
69
- );
70
- }
71
-
72
- export function formatCommandError(error) {
73
- if (!(error instanceof Error)) return "kit_command_failed";
74
- if (error instanceof HousePending) return JSON.stringify({ pending: error.pending });
75
- if (error instanceof KitRefusal && error.payload !== undefined) {
76
- return `${error.message}\n${JSON.stringify(error.payload)}`;
77
- }
78
- return error.message;
79
- }