@agentshouse/kit 0.1.0-alpha.3 → 0.1.0-alpha.31

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 (142) hide show
  1. package/README.md +722 -263
  2. package/bin/connect-linux.sh +200 -25
  3. package/bin/kit.js +179 -157
  4. package/bootstrap.json +17 -0
  5. package/package.json +7 -2
  6. package/src/adapter-mcp.js +76 -12
  7. package/src/agents.js +63 -8
  8. package/src/binding.js +52 -0
  9. package/src/blocks.js +2 -26
  10. package/src/bridge.js +28 -14
  11. package/src/bytes.js +19 -8
  12. package/src/configuration.js +13 -209
  13. package/src/connector-login.js +3 -84
  14. package/src/contract.js +36 -11
  15. package/src/delivery.js +4 -7
  16. package/src/directory.js +6 -26
  17. package/src/draft.js +4 -82
  18. package/src/git-main.js +138 -0
  19. package/src/git.js +62 -0
  20. package/src/google/calendar.js +39 -45
  21. package/src/google/drive.js +61 -186
  22. package/src/google/entries.js +346 -0
  23. package/src/google/gateway.js +3 -6
  24. package/src/google/gmail.js +33 -23
  25. package/src/google/oauth.js +106 -66
  26. package/src/google/source.js +21 -23
  27. package/src/host.js +6 -2
  28. package/src/house-main.js +65 -8
  29. package/src/house.js +446 -127
  30. package/src/how-we-work.md +25 -0
  31. package/src/inbound.js +2 -18
  32. package/src/kit-work-stream.js +118 -0
  33. package/src/launcher.js +46 -10
  34. package/src/local-files.js +3 -11
  35. package/src/login.js +23 -11
  36. package/src/mcp.js +18 -0
  37. package/src/mount.js +13 -21
  38. package/src/original.js +4 -4
  39. package/src/pacing.js +41 -0
  40. package/src/plan.js +37 -45
  41. package/src/repository.js +3 -61
  42. package/src/session-bin/git +2 -0
  43. package/src/setup.js +14 -15
  44. package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
  45. package/src/skill-set/ask-matt/SKILL.md +91 -0
  46. package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
  47. package/src/skill-set/code-review/LICENSE +21 -0
  48. package/src/skill-set/code-review/SKILL.md +89 -0
  49. package/src/skill-set/code-review/agents/openai.yaml +3 -0
  50. package/src/skill-set/codebase-design/DEEPENING.md +37 -0
  51. package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
  52. package/src/skill-set/codebase-design/LICENSE +21 -0
  53. package/src/skill-set/codebase-design/SKILL.md +116 -0
  54. package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
  55. package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
  56. package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
  57. package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
  58. package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  59. package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
  60. package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
  61. package/src/skill-set/domain-modeling/LICENSE +21 -0
  62. package/src/skill-set/domain-modeling/SKILL.md +76 -0
  63. package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
  64. package/src/skill-set/grill-me/LICENSE +21 -0
  65. package/src/skill-set/grill-me/SKILL.md +8 -0
  66. package/src/skill-set/grill-me/agents/openai.yaml +4 -0
  67. package/src/skill-set/grill-with-docs/LICENSE +21 -0
  68. package/src/skill-set/grill-with-docs/SKILL.md +8 -0
  69. package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
  70. package/src/skill-set/grilling/LICENSE +21 -0
  71. package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
  72. package/src/skill-set/handoff/LICENSE +21 -0
  73. package/src/skill-set/handoff/SKILL.md +17 -0
  74. package/src/skill-set/handoff/agents/openai.yaml +4 -0
  75. package/src/skill-set/implement/LICENSE +21 -0
  76. package/src/skill-set/implement/SKILL.md +16 -0
  77. package/src/skill-set/implement/agents/openai.yaml +4 -0
  78. package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
  79. package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
  80. package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
  81. package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
  82. package/src/skill-set/prototype/LICENSE +21 -0
  83. package/src/skill-set/prototype/LOGIC.md +67 -0
  84. package/src/skill-set/prototype/SKILL.md +28 -0
  85. package/src/skill-set/prototype/UI.md +112 -0
  86. package/src/skill-set/prototype/agents/openai.yaml +3 -0
  87. package/src/skill-set/research/LICENSE +21 -0
  88. package/src/skill-set/research/SKILL.md +14 -0
  89. package/src/skill-set/research/agents/openai.yaml +3 -0
  90. package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
  91. package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
  92. package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
  93. package/src/skill-set/tdd/LICENSE +21 -0
  94. package/src/skill-set/tdd/SKILL.md +40 -0
  95. package/src/skill-set/tdd/agents/openai.yaml +3 -0
  96. package/src/skill-set/tdd/mocking.md +59 -0
  97. package/src/skill-set/tdd/tests.md +77 -0
  98. package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
  99. package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
  100. package/src/skill-set/teach/LICENSE +21 -0
  101. package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
  102. package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
  103. package/src/skill-set/teach/SKILL.md +141 -0
  104. package/src/skill-set/teach/agents/openai.yaml +4 -0
  105. package/src/skill-set/to-questionnaire/LICENSE +21 -0
  106. package/src/skill-set/to-questionnaire/SKILL.md +55 -0
  107. package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
  108. package/src/skill-set/to-spec/LICENSE +21 -0
  109. package/src/skill-set/to-spec/SKILL.md +76 -0
  110. package/src/skill-set/to-spec/agents/openai.yaml +4 -0
  111. package/src/skill-set/to-tickets/LICENSE +21 -0
  112. package/src/skill-set/to-tickets/SKILL.md +106 -0
  113. package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
  114. package/src/skill-set/wait-what/LICENSE +21 -0
  115. package/src/skill-set/wait-what/SKILL.md +8 -0
  116. package/src/skill-set/wait-what/agents/openai.yaml +4 -0
  117. package/src/skill-set/wayfinder/LICENSE +21 -0
  118. package/src/skill-set/wayfinder/SKILL.md +129 -0
  119. package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
  120. package/src/skill-set/writing-for-agents/LICENSE +21 -0
  121. package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
  122. package/src/skill-set/writing-for-agents/SKILL.md +83 -0
  123. package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
  124. package/src/skill-set.js +85 -0
  125. package/src/source.js +93 -54
  126. package/src/state.js +19 -1
  127. package/src/sync-library.js +7 -11
  128. package/src/sync-state.js +16 -3
  129. package/src/sync.js +129 -41
  130. package/src/tasks.js +75 -0
  131. package/src/telegram-view.js +214 -0
  132. package/src/telegram.js +161 -66
  133. package/src/turn-frames.js +69 -0
  134. package/src/worker.js +685 -184
  135. package/src/working-copy.js +1221 -0
  136. package/src/workspace.js +40 -32
  137. package/src/google/declared.js +0 -146
  138. package/src/google/id.js +0 -1
  139. package/src/instance/google.js +0 -1
  140. package/src/starter-set.js +0 -17
  141. /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
  142. /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
@@ -7,18 +7,10 @@ import {
7
7
  MIGRATOR_TIMEOUT_MS,
8
8
  } from "./contract.js";
9
9
  import { declarationReader } from "./declaration.js";
10
- import { GOOGLE_DRIVE_ID } from "./google/id.js";
11
10
  import { KitRefusal } from "./refusal.js";
12
11
 
13
- const PRESETS = new Set(["custom", "google"]);
14
12
  const AGENT_CLIENTS = new Set(["claude-code"]);
15
- const GOOGLE_CURATED_SERVICES = new Set(["gmail", "calendar", "drive"]);
16
13
  const ENVIRONMENT_VALUE_MAX = 4096;
17
- const GOOGLE_API_HOST = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.googleapis\.com$/;
18
- const GOOGLE_API_PATH = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]+$/;
19
- const GOOGLE_API_SCOPE = /^https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+$/;
20
- const EXTRACTION_PATH = /^(?:[A-Za-z_][A-Za-z0-9_-]*|\d+)(?:\.(?:[A-Za-z_][A-Za-z0-9_-]*|\d+))*$/;
21
- const QUERY_NAME = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
22
14
 
23
15
  const { refuse, plain, closed, text, positiveCount, filled } = declarationReader(
24
16
  "kit_configuration_invalid",
@@ -116,225 +108,33 @@ function backfillOf(value, declaration) {
116
108
  return { kind: named[0], bound: positiveCount(backfill[named[0]], declaration) };
117
109
  }
118
110
 
119
- function textList(value, declaration) {
120
- return filled(value, declaration).map((entry, position) =>
121
- text(entry, `${declaration}[${position}]`, 512),
122
- );
123
- }
124
-
125
- function textArray(value, declaration) {
126
- if (!Array.isArray(value)) refuse(declaration);
127
- return value.map((entry, position) => text(entry, `${declaration}[${position}]`, 512));
128
- }
129
-
130
- function googleSelectionOf(value, declaration, service) {
131
- if (service === "gmail") {
132
- const selection = closed(value, ["labels"], declaration);
133
- return { labels: textList(selection.labels, `${declaration}.labels`) };
134
- }
135
- if (service === "calendar") {
136
- const selection = closed(value, ["calendars"], declaration);
137
- return { calendars: textList(selection.calendars, `${declaration}.calendars`) };
138
- }
139
- if (service === "drive") {
140
- const selection = closed(value, ["files", "folders"], declaration);
141
- const files =
142
- selection.files === undefined ? [] : textArray(selection.files, `${declaration}.files`);
143
- const folders =
144
- selection.folders === undefined ? [] : textArray(selection.folders, `${declaration}.folders`);
145
- if (files.length + folders.length === 0) refuse(declaration);
146
- files.forEach((id, position) => {
147
- if (!GOOGLE_DRIVE_ID.test(id)) refuse(`${declaration}.files[${position}]`);
148
- });
149
- folders.forEach((id, position) => {
150
- if (!GOOGLE_DRIVE_ID.test(id)) refuse(`${declaration}.folders[${position}]`);
151
- });
152
- return { files, folders };
153
- }
154
- refuse(declaration);
155
- }
156
-
157
- function extractionPath(value, declaration) {
158
- const path = text(value, declaration, 512);
159
- if (!EXTRACTION_PATH.test(path)) refuse(declaration);
160
- return path;
161
- }
162
-
163
- function queryOf(value, declaration) {
164
- const query = plain(value, declaration);
165
- const parsed = {};
166
- for (const [name, entry] of Object.entries(query)) {
167
- if (!QUERY_NAME.test(name) || typeof entry !== "string" || entry.length > 2048) {
168
- refuse(`${declaration}.${name}`);
169
- }
170
- parsed[name] = entry;
171
- }
172
- return parsed;
173
- }
174
-
175
- function declaredServiceOf(value, declaration) {
176
- const declared = closed(
177
- value,
178
- ["host", "path", "query", "page_size", "page_token", "items", "fields", "scopes"],
179
- declaration,
180
- );
181
- const host = text(declared.host, `${declaration}.host`, 253);
182
- if (!GOOGLE_API_HOST.test(host)) refuse(`${declaration}.host`);
183
- const path = text(declared.path, `${declaration}.path`, 2048);
184
- if (!GOOGLE_API_PATH.test(path) || path.includes("..") || path.includes("//")) {
185
- refuse(`${declaration}.path`);
186
- }
187
- const tokens = closed(
188
- declared.page_token,
189
- ["request", "response", "checkpoint_request", "checkpoint_response"],
190
- `${declaration}.page_token`,
191
- );
192
- const pageToken = {
193
- request: text(tokens.request, `${declaration}.page_token.request`, 128),
194
- response: extractionPath(tokens.response, `${declaration}.page_token.response`),
195
- checkpointRequest: text(
196
- tokens.checkpoint_request,
197
- `${declaration}.page_token.checkpoint_request`,
198
- 128,
199
- ),
200
- checkpointResponse: extractionPath(
201
- tokens.checkpoint_response,
202
- `${declaration}.page_token.checkpoint_response`,
203
- ),
204
- };
205
- if (
206
- !QUERY_NAME.test(pageToken.request) ||
207
- !QUERY_NAME.test(pageToken.checkpointRequest) ||
208
- pageToken.request === pageToken.checkpointRequest
209
- ) {
210
- refuse(`${declaration}.page_token`);
211
- }
212
- const fields = closed(
213
- declared.fields,
214
- [
215
- "native_id",
216
- "native_version",
217
- "provider_modified_at",
218
- "observed_at",
219
- "body",
220
- "container",
221
- "author",
222
- "url",
223
- ],
224
- `${declaration}.fields`,
225
- );
226
- const pageSize = text(declared.page_size, `${declaration}.page_size`, 128);
227
- if (!QUERY_NAME.test(pageSize)) refuse(`${declaration}.page_size`);
228
- const scopes = textList(declared.scopes, `${declaration}.scopes`).map((scope, position) => {
229
- if (!GOOGLE_API_SCOPE.test(scope)) refuse(`${declaration}.scopes[${position}]`);
230
- return scope;
231
- });
232
- return {
233
- host,
234
- path,
235
- query: queryOf(declared.query, `${declaration}.query`),
236
- pageSize,
237
- scopes: [...new Set(scopes)],
238
- pageToken,
239
- items: extractionPath(declared.items, `${declaration}.items`),
240
- fields: {
241
- nativeId: extractionPath(fields.native_id, `${declaration}.fields.native_id`),
242
- nativeVersion:
243
- fields.native_version === undefined
244
- ? null
245
- : extractionPath(fields.native_version, `${declaration}.fields.native_version`),
246
- providerModifiedAt:
247
- fields.provider_modified_at === undefined
248
- ? null
249
- : extractionPath(
250
- fields.provider_modified_at,
251
- `${declaration}.fields.provider_modified_at`,
252
- ),
253
- observedAt: extractionPath(fields.observed_at, `${declaration}.fields.observed_at`),
254
- body: textList(fields.body, `${declaration}.fields.body`).map((path, position) =>
255
- extractionPath(path, `${declaration}.fields.body[${position}]`),
256
- ),
257
- container: extractionPath(fields.container, `${declaration}.fields.container`),
258
- author:
259
- fields.author === undefined
260
- ? null
261
- : extractionPath(fields.author, `${declaration}.fields.author`),
262
- url:
263
- fields.url === undefined ? null : extractionPath(fields.url, `${declaration}.fields.url`),
264
- },
265
- };
266
- }
267
-
268
- function serviceOf(value, declaration, preset) {
111
+ function serviceOf(value, declaration) {
269
112
  const service = closed(
270
113
  value,
271
- ["service", "source_ref", "rooms", "backfill", "connector", "selection", "declaration"],
114
+ ["service", "source_ref", "rooms", "backfill", "connector"],
272
115
  declaration,
273
116
  );
274
- const name = declaredId(service.service, `${declaration}.service`);
275
- if (preset === "custom" && service.connector === undefined) refuse(`${declaration}.connector`);
276
- if (preset === "custom" && service.selection !== undefined) refuse(`${declaration}.selection`);
277
- if (preset === "custom" && service.declaration !== undefined) refuse(`${declaration}.declaration`);
278
- if (preset === "google" && service.connector !== undefined) refuse(`${declaration}.connector`);
279
- if (preset === "google" && GOOGLE_CURATED_SERVICES.has(name) && service.declaration !== undefined) {
280
- refuse(`${declaration}.declaration`);
281
- }
282
- if (preset === "google" && !GOOGLE_CURATED_SERVICES.has(name)) {
283
- if (service.selection !== undefined) refuse(`${declaration}.selection`);
284
- if (service.declaration === undefined) refuse(`${declaration}.declaration`);
285
- }
286
117
  return {
287
- service: name,
118
+ service: declaredId(service.service, `${declaration}.service`),
288
119
  sourceRef: text(service.source_ref, `${declaration}.source_ref`, 512),
289
120
  rooms: filled(service.rooms, `${declaration}.rooms`).map((room, position) =>
290
121
  text(room, `${declaration}.rooms[${position}]`, 512),
291
122
  ),
292
123
  backfill: backfillOf(service.backfill, `${declaration}.backfill`),
293
- selection:
294
- preset === "google" && GOOGLE_CURATED_SERVICES.has(name)
295
- ? googleSelectionOf(service.selection, `${declaration}.selection`, name)
296
- : null,
297
- connector: preset === "custom" ? connectorOf(service.connector, `${declaration}.connector`) : null,
298
- declaration:
299
- preset === "google" && !GOOGLE_CURATED_SERVICES.has(name)
300
- ? declaredServiceOf(service.declaration, `${declaration}.declaration`)
301
- : null,
124
+ connector: connectorOf(service.connector, `${declaration}.connector`),
302
125
  };
303
126
  }
304
127
 
305
128
  function instanceOf(value, declaration) {
306
- const instance = closed(value, ["id", "preset", "services", "scopes"], declaration);
307
- const preset = declaredId(instance.preset, `${declaration}.preset`);
308
- if (!PRESETS.has(preset)) refuse(`${declaration}.preset`);
309
- if (preset !== "google" && instance.scopes !== undefined) refuse(`${declaration}.scopes`);
310
- const scopes =
311
- instance.scopes === undefined
312
- ? []
313
- : textList(instance.scopes, `${declaration}.scopes`).map((scope, position) => {
314
- if (!GOOGLE_API_SCOPE.test(scope)) refuse(`${declaration}.scopes[${position}]`);
315
- return scope;
316
- });
129
+ const instance = closed(value, ["id", "preset", "services"], declaration);
317
130
  const services = filled(instance.services, `${declaration}.services`).map((service, position) =>
318
- serviceOf(service, `${declaration}.services[${position}]`, preset),
131
+ serviceOf(service, `${declaration}.services[${position}]`),
319
132
  );
320
133
  services.forEach((service, position) => {
321
134
  const first = services.findIndex((candidate) => candidate.service === service.service);
322
135
  if (first !== position) refuse(`${declaration}.services[${position}].service`);
323
136
  });
324
- services.forEach((service, position) => {
325
- if (
326
- service.declaration !== null &&
327
- service.declaration.scopes.some((scope) => !scopes.includes(scope))
328
- ) {
329
- refuse(`${declaration}.services[${position}].declaration.scopes`);
330
- }
331
- });
332
- return {
333
- id: declaredId(instance.id, `${declaration}.id`),
334
- preset,
335
- services,
336
- ...(preset === "google" ? { scopes: [...new Set(scopes)] } : {}),
337
- };
137
+ return { id: declaredId(instance.id, `${declaration}.id`), preset: instance.preset, services };
338
138
  }
339
139
 
340
140
  function absolutePath(value, declaration) {
@@ -388,6 +188,7 @@ export function parseConfiguration(document) {
388
188
  "migrators",
389
189
  "clients",
390
190
  "host",
191
+ "skills",
391
192
  ],
392
193
  "configuration",
393
194
  );
@@ -397,10 +198,12 @@ export function parseConfiguration(document) {
397
198
  if (!EXECUTION_AUTHORITIES.includes(declared.execution_authority)) {
398
199
  refuse("execution_authority");
399
200
  }
201
+ const skills = declared.skills === undefined ? true : declared.skills;
202
+ if (typeof skills !== "boolean") refuse("skills");
400
203
  if (!Array.isArray(declared.instances)) refuse("instances");
401
204
  if (!Array.isArray(declared.migrators)) refuse("migrators");
402
- const instances = declared.instances.map((instance, position) =>
403
- instanceOf(instance, `instances[${position}]`),
205
+ const instances = declared.instances.flatMap((instance, position) =>
206
+ instance?.preset === "custom" ? [instanceOf(instance, `instances[${position}]`)] : [],
404
207
  );
405
208
  instances.forEach((instance, position) => {
406
209
  const first = instances.findIndex((candidate) => candidate.id === instance.id);
@@ -428,6 +231,7 @@ export function parseConfiguration(document) {
428
231
  migrators,
429
232
  clients,
430
233
  host: hostOf(declared.host, "host"),
234
+ skills,
431
235
  };
432
236
  }
433
237
 
@@ -1,91 +1,10 @@
1
1
  import { readConfiguration, sourceInstance } from "./configuration.js";
2
2
  import { authorizeCustom, customAuthorizationStatus } from "./custom/authorization.js";
3
- import { googleAuthorization, authorizeGoogle, GOOGLE_SCOPES } from "./google/oauth.js";
4
- import { KitRefusal } from "./refusal.js";
5
3
 
6
- function googleServiceScopes(service) {
7
- const curated = GOOGLE_SCOPES[service.service];
8
- return curated === undefined ? service.declaration.scopes : [curated];
9
- }
10
-
11
- function googleServiceAuthorized(authorization, scopes) {
12
- return authorization.grants.some((grant) =>
13
- scopes.every((scope) => grant.scopes.includes(scope)),
14
- );
15
- }
16
-
17
- function googleConsent(service, context) {
18
- return async ({ url }) => {
19
- const collected = await context.present(
20
- {
21
- steps: [
22
- { kind: "visit", label: `Sign in to Google and allow access for ${service.service}`, url },
23
- {
24
- kind: "collect",
25
- label: "The address your browser ended on",
26
- name: "address",
27
- description:
28
- "After you allow access, Google sends your browser to a 127.0.0.1 address that does not open. Copy that whole address from the address bar and paste it here.",
29
- },
30
- ],
31
- },
32
- context.signal,
33
- );
34
- return collected.address;
35
- };
36
- }
37
-
38
- const CONNECTOR_LOGINS = {
39
- custom: {
40
- status: customAuthorizationStatus,
41
- login: authorizeCustom,
42
- },
43
- google: {
44
- status: async (instance, context) => {
45
- const authorization = await googleAuthorization(context.directory, instance.id);
46
- if (authorization === null) return false;
47
- return instance.services.every((service) =>
48
- googleServiceAuthorized(authorization, googleServiceScopes(service)),
49
- );
50
- },
51
- guided: async (instance, context) => {
52
- for (const service of instance.services) {
53
- try {
54
- await authorizeGoogle(
55
- {
56
- directory: context.directory,
57
- instance: instance.id,
58
- client: context.dependencies.googleOAuthClient(),
59
- scopes: googleServiceScopes(service),
60
- approve: googleConsent(service, context),
61
- },
62
- context.dependencies.googleDependencies ?? {},
63
- );
64
- } catch (error) {
65
- if (context.signal.aborted) return "cancelled";
66
- if (error instanceof KitRefusal && error.code === "google_authorization_declined") {
67
- return "cancelled";
68
- }
69
- return "failed";
70
- }
71
- }
72
- return "ready";
73
- },
74
- },
75
- };
76
-
77
- export function connectorLogin(instance, dependencies = {}) {
78
- return { ...CONNECTOR_LOGINS, ...(dependencies.connectorLogins ?? {}) }[instance.preset];
79
- }
80
-
81
- export async function signInConnector(options, dependencies = {}) {
4
+ export async function signInConnector(options) {
82
5
  const configuration = await readConfiguration(options.configuration);
83
6
  const instance = sourceInstance(configuration, options.instance);
84
- const gateway = connectorLogin(instance, dependencies);
85
- if (gateway.login === undefined) {
86
- throw new KitRefusal("kit_connector_login_guided", `${instance.id} signs in through House setup`);
87
- }
88
7
  const context = { directory: options.directory, signal: options.signal };
89
- if (await gateway.status(instance, context)) return "skipped";
90
- return (await gateway.login(instance, context)) ? "ready" : "needs_attention";
8
+ if (await customAuthorizationStatus(instance, context)) return "skipped";
9
+ return (await authorizeCustom(instance, context)) ? "ready" : "needs_attention";
91
10
  }
package/src/contract.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CLAUDE_PASTED_CODE, CODEX_DEVICE_CODE, GROK_DEVICE_CODE } from "./agent-logins.js";
2
2
 
3
- export const KIT_CONTRACT_VERSION = "2026-09-26-house-agent-kinds";
3
+ export const KIT_CONTRACT_VERSION = "3";
4
4
  export const KIT_CAPTURE_BATCH_MAX = 100;
5
5
  export const COMMAND_INPUT_MAX_BYTES = 4 * 1024;
6
6
  export const AUTHORIZATION_OUTPUT_MAX_BYTES = 4 * 1024;
@@ -21,10 +21,11 @@ export const COMMAND_ADAPTER_SESSION_TIMEOUT_MS = 30_000;
21
21
  export const WORKER_EXIT_GRACE_MS = 5_000;
22
22
  export const WORKER_EXIT_POLL_MS = 100;
23
23
  export const WORKER_SUPERVISION_POLL_MS = 2_000;
24
- export const WORKER_COMMAND_POLL_MS = 2_000;
25
- export const WORKER_HEARTBEAT_MS = 30_000;
26
24
  export const WORKER_COMMAND_BATCH_MAX = 10;
27
25
  export const WORKER_RUN_TOKEN_MARGIN_MS = 60_000;
26
+ export const KIT_STREAM_RECONNECT_MS = 1_000;
27
+ export const KIT_STREAM_RECONNECT_MAX_MS = 30_000;
28
+ export const KIT_STREAM_REJOIN_MS = 2_000;
28
29
  export const TELEGRAM_IMAGE_UPLOAD_CEILING_BYTES = 10 * 1024 * 1024;
29
30
  export const CONVERSATION_FILE_TRANSFER_TIMEOUT_MS = 30_000;
30
31
  export const WORKER_INHERITED_ENVIRONMENT = ["PATH", "HOME"];
@@ -40,17 +41,15 @@ export function runOutcomePath(run) {
40
41
  return `/kit/runs/${encodeURIComponent(run)}/outcome`;
41
42
  }
42
43
 
44
+ export function runRequestPath(run) {
45
+ return `/kit/runs/${encodeURIComponent(run)}/request`;
46
+ }
47
+
43
48
  export const KIT_SESSIONS_REPORT_PATH = "/kit/sessions/report";
44
49
  export const KIT_SESSIONS_REPORT_MAX = 256;
45
50
 
46
- export const ASSISTANT_TEXT_MAX_BYTES = 4096;
47
- export const ASSISTANT_BLOCKS_MAX = 500;
48
51
  export const ASSISTANT_TABLE_COLUMNS_MAX = 20;
49
52
 
50
- export function assistantDraftsPath(workerSession) {
51
- return `/kit/sessions/${encodeURIComponent(workerSession)}/drafts`;
52
- }
53
-
54
53
  export function assistantTextPath(workerSession) {
55
54
  return `/kit/sessions/${encodeURIComponent(workerSession)}/assistant-messages`;
56
55
  }
@@ -61,6 +60,8 @@ export const TELEGRAM_DELIVERY_BATCH_MAX = 10;
61
60
  export const TELEGRAM_DOWNLOAD_CEILING_BYTES = 20 * 1024 * 1024;
62
61
  export const TELEGRAM_BOT_API_TIMEOUT_MS = 30_000;
63
62
  export const TELEGRAM_GATEWAY_POLL_MS = 2_000;
63
+ export const TELEGRAM_RICH_MESSAGE_CHARACTERS_MAX = 32_768;
64
+ export const TELEGRAM_RICH_MESSAGE_BLOCKS_MAX = 500;
64
65
  export const TELEGRAM_CONTROL_KEYBOARD_COMMANDS = ["/stop", "/kill"];
65
66
  export const TELEGRAM_MEDIA_KINDS = [
66
67
  "document",
@@ -114,6 +115,10 @@ export const BRIDGE_NONCE_ENVIRONMENT = "HOUSE_KIT_BRIDGE_NONCE";
114
115
  export const BRIDGE_RECONNECT_MS = 200;
115
116
  export const BRIDGE_RECONNECT_MAX_MS = 5_000;
116
117
 
118
+ export const HOUSE_CLI_LINE = "The agents.house house CLI is available: run house --help.";
119
+ export const HOW_WE_WORK_PATH = "/private/library/how-we-work.md";
120
+ export const SHELL_RENDER = "shell/1";
121
+
117
122
  export const ACP_PROTOCOL = 1;
118
123
  export const ACP_AUTH_REQUIRED = -32000;
119
124
  export const ADAPTER_EXIT_GRACE_MS = 2_000;
@@ -195,7 +200,7 @@ export const ADAPTER_CONTRACTS = Object.freeze({
195
200
  guidedLogin: CLAUDE_PASTED_CODE,
196
201
  identity: Object.freeze({ agentInfo: "@agentclientprotocol/claude-agent-acp" }),
197
202
  nativeQuestion: null,
198
- sessionLoad: false,
203
+ sessionLoad: true,
199
204
  sessionConfig: Object.freeze({ model: "model", effort: "effort" }),
200
205
  capabilities: Object.freeze({
201
206
  field_kinds: ELICITATION_FIELD_KINDS,
@@ -251,6 +256,10 @@ export const KIT_LOGIN_REPORT_PATH = "/kit/logins/report";
251
256
  export const GUIDED_LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
252
257
  export const GUIDED_LOGIN_OUTPUT_MAX_BYTES = 64 * 1024;
253
258
 
259
+ export const KIT_SOURCES_DESIRED_PATH = "/kit/sources/desired";
260
+ export const KIT_SOURCES_REPORT_PATH = "/kit/sources/report";
261
+ export const SOURCE_CAPTURE_INTERVAL_MS = 15 * 60 * 1000;
262
+
254
263
  export const EXECUTION_AUTHORITIES = ["allow", "refuse"];
255
264
 
256
265
  export const PERMISSION_OPTION_KINDS = Object.freeze({
@@ -288,9 +297,25 @@ export const KIT_DOOR_DISCOVER_PATH = "/kit/door/discover";
288
297
  export const KIT_DOOR_BOOTSTRAP_PATH = "/kit/door/bootstrap";
289
298
  export const KIT_DOOR_ENUMERATE_PATH = "/kit/door/enumerate";
290
299
  export const KIT_DOOR_BATCH_PATH = "/kit/door/batch";
291
- export const EXPLICIT_SYNC_VERSION = "explicit-sync/1";
300
+ export const KIT_DOOR_REVISIONS_PATH = "/kit/door/revisions";
301
+ export const EXPLICIT_SYNC_VERSION = "explicit-sync/2";
292
302
  export const KIT_DOOR_UNMOUNT_PATH = "/kit/door/unmount";
293
303
 
304
+ export const KIT_WORKING_COPY_SELECTION_PATH = "/kit/working-copy/selection";
305
+ export const WORKING_COPY_VERSION = "working-copy-state/3";
306
+ export const WORKING_COPIES_HOME = "working-copies";
307
+ export const WORKING_COPY_ROOM_HOMES = ["capture", "library", "collaboration"];
308
+ export const WORKING_COPY_PRIVATE_HOMES = ["library", "collaboration", "rooms", "agents", "house"];
309
+ export const WORKING_COPY_RECEIVED_REF = "refs/house/received";
310
+ export const WORKING_COPY_BRANCH = "main";
311
+ export const ROOM_REFERENCE = /^r_[0-9a-z]+\.[0-9a-z]$/;
312
+ export const WORKING_COPY_PUSH_METHOD = "house/git/push";
313
+ export const WORKING_COPY_INDEX_METHOD = "house/git/index";
314
+ export const WORKING_COPY_PUSH_HELP =
315
+ "Submit a Room commit to House; default HEAD. Use --owner only outside an Agent Session. Native git push is not a House remote.";
316
+ export const WORKING_COPY_UPLOAD_COMMAND = "prepare_edit_upload";
317
+ export const MCP_REQUEST_BODY_MAX_BYTES = 4 * 1024 * 1024;
318
+
294
319
  export const MIGRATOR_CONTRACT = "kit-migrator/1";
295
320
  export const MIGRATOR_CATEGORY = "migrator";
296
321
  export const MIGRATOR_MANIFEST = "migrator.json";
package/src/delivery.js CHANGED
@@ -54,10 +54,8 @@ const FAILURE_CATEGORIES = {
54
54
  google_provider_unavailable: "provider",
55
55
  google_provider_response_invalid: "provider",
56
56
  google_checkpoint_invalid: "provider",
57
- google_service_unsupported: "config",
58
- google_oauth_client_unconfigured: "config",
59
- google_drive_selection_exceeded: "config",
60
57
  google_drive_item_missing: "provider",
58
+ api_disabled: "config",
61
59
  };
62
60
 
63
61
  export function fileDescriptor(file) {
@@ -184,8 +182,7 @@ export function stallOf(code, houseRetry = null) {
184
182
 
185
183
  export function healthOfRun(stall) {
186
184
  if (stall === null) return { state: "ok", category: null };
187
- return {
188
- state: stall.disposition === "retry" ? "retrying" : "failed",
189
- category: stall.category ?? FAILURE_CATEGORIES[stall.code] ?? "internal",
190
- };
185
+ const category = stall.category ?? FAILURE_CATEGORIES[stall.code] ?? "internal";
186
+ if (stall.disposition === "retry") return { state: "retrying", category };
187
+ return { state: "failed", category, cause: stall.code };
191
188
  }
package/src/directory.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import { constants } from "node:fs";
2
- import { access, mkdir, realpath, rename, rm, stat } from "node:fs/promises";
3
- import { randomUUID } from "node:crypto";
4
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
2
+ import { access, mkdir, realpath, stat } from "node:fs/promises";
3
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
5
4
  import { LAUNCH_DIRECTORY_BASE } from "./contract.js";
6
5
  import { KitRefusal } from "./refusal.js";
7
- import { seedStarterSet } from "./starter-set.js";
8
6
 
9
7
  export function launchBase(dependencies = {}) {
10
8
  return dependencies.launchBase ?? LAUNCH_DIRECTORY_BASE;
@@ -39,25 +37,9 @@ function unavailable(path, cause) {
39
37
  return new KitRefusal("kit_launch_directory_unavailable", `${path} ${cause}`);
40
38
  }
41
39
 
42
- async function isDirectory(path) {
43
- try {
44
- return (await stat(path)).isDirectory();
45
- } catch {
46
- return false;
47
- }
48
- }
49
-
50
- async function seedDefaultDirectory(selected, base, path) {
51
- const staging = join(base, `.${randomUUID()}.staging`);
52
- try {
53
- await seedStarterSet(staging);
54
- await rename(staging, path);
55
- } catch (error) {
56
- await rm(staging, { recursive: true, force: true }).catch(() => undefined);
57
- if (!(await isDirectory(path))) {
58
- throw unavailable(selected, `could not be prepared (${error.code ?? "unknown"})`);
59
- }
60
- }
40
+ async function createDefaultDirectory(selected, path) {
41
+ const refused = await made(path);
42
+ if (refused !== null) throw unavailable(selected, `could not be created (${refused})`);
61
43
  }
62
44
 
63
45
  function within(base, selected) {
@@ -73,9 +55,7 @@ export async function readyLaunchDirectory(selected, base, agentId = null, optio
73
55
  if (options.container && !within(resolve(base), path)) {
74
56
  throw unavailable(selected, `resolves outside ${base}`);
75
57
  }
76
- if (path === defaultDirectory(base, agentId) && !(await isDirectory(path))) {
77
- await seedDefaultDirectory(selected, base, path);
78
- }
58
+ if (path === defaultDirectory(base, agentId)) await createDefaultDirectory(selected, path);
79
59
  let described;
80
60
  try {
81
61
  described = await stat(path);
package/src/draft.js CHANGED
@@ -1,89 +1,11 @@
1
- import { assistantBlocks, blocksText, withinAssistantBound } from "./blocks.js";
2
-
3
- const DRAFT_INTERVAL_MS = 500;
1
+ import { pacedLatest } from "./pacing.js";
4
2
 
5
3
  export function observedAssistantDrafts(options) {
6
- const intervalMs = options.intervalMs ?? DRAFT_INTERVAL_MS;
7
- const now = options.now ?? (() => Date.now());
8
- let timer = null;
9
- let sentAt = null;
10
- let sent = null;
11
- let latest = null;
12
-
13
- function write() {
14
- timer = null;
15
- if (latest === null || latest === sent) return;
16
- sent = latest;
17
- sentAt = now();
18
- options.send(sent);
19
- }
20
-
21
- function cancel() {
22
- if (timer === null) return;
23
- clearTimeout(timer);
24
- timer = null;
25
- }
26
-
4
+ const paced = pacedLatest(options);
27
5
  return {
28
6
  observe(view) {
29
- if (typeof view !== "string" || view === "") return;
30
- latest = view;
31
- if (timer !== null) return;
32
- const wait = sentAt === null ? 0 : sentAt + intervalMs - now();
33
- if (wait <= 0) {
34
- write();
35
- return;
36
- }
37
- timer = setTimeout(write, wait);
38
- timer.unref?.();
39
- },
40
- complete(view) {
41
- if (typeof view !== "string" || view === "") return;
42
- latest = view;
43
- cancel();
44
- write();
45
- },
46
- close: cancel,
47
- };
48
- }
49
-
50
- export function openAssistantDrafts(options) {
51
- let pending = null;
52
- let sending = null;
53
- let sent = null;
54
- let closed = false;
55
-
56
- function pump() {
57
- if (sending !== null || pending === null) return;
58
- const view = pending;
59
- pending = null;
60
- sent = view.text;
61
- let started;
62
- try {
63
- started = Promise.resolve(options.send(view.blocks));
64
- } catch {
65
- started = Promise.resolve();
66
- }
67
- sending = started
68
- .catch(() => undefined)
69
- .finally(() => {
70
- sending = null;
71
- pump();
72
- });
73
- }
74
-
75
- return {
76
- update(text) {
77
- if (closed || typeof text !== "string" || text === "") return;
78
- if (text === sent || text === pending?.text) return;
79
- const blocks = assistantBlocks(text);
80
- if (blocks === null || !withinAssistantBound(blocksText(blocks))) return;
81
- pending = { text, blocks };
82
- pump();
83
- },
84
- close() {
85
- closed = true;
86
- pump();
7
+ if (typeof view === "string" && view !== "") paced.observe(view);
87
8
  },
9
+ close: paced.close,
88
10
  };
89
11
  }