@agentshouse/kit 0.2.1-cbdf76b9a90a3 → 0.2.1-cbff58027f078

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 +28 -28
  2. package/dist/acp.js +58 -7
  3. package/dist/afk.js +17 -0
  4. package/dist/agents.js +55 -32
  5. package/dist/api.js +1 -1
  6. package/dist/authority-main.js +2 -2
  7. package/dist/bridge.js +1 -1
  8. package/dist/building.js +50 -0
  9. package/dist/calls.js +71 -0
  10. package/dist/chunks.js +49 -0
  11. package/dist/clis.js +45 -40
  12. package/dist/{stream.js → connection.js} +28 -46
  13. package/dist/conversations.js +330 -125
  14. package/dist/copies.js +286 -149
  15. package/dist/end.js +11 -5
  16. package/dist/events.js +66 -8
  17. package/dist/files.js +139 -37
  18. package/dist/git.js +47 -13
  19. package/dist/home.js +0 -7
  20. package/dist/house-cli.js +95 -87
  21. package/dist/house-main.js +3 -3
  22. package/dist/instructions.js +29 -9
  23. package/dist/kit-cli.js +4 -4
  24. package/dist/kit-main.js +3 -3
  25. package/dist/login.js +4 -4
  26. package/dist/mcp.js +6 -2
  27. package/dist/parts.js +10 -0
  28. package/dist/placement.js +10 -18
  29. package/dist/relay.js +21 -0
  30. package/dist/resident.js +49 -38
  31. package/dist/scope.js +12 -4
  32. package/dist/scripts.js +255 -0
  33. package/dist/service.js +1 -1
  34. package/dist/sign-in.js +22 -21
  35. package/dist/silence.js +28 -0
  36. package/dist/skills.js +42 -97
  37. package/dist/transcription.js +83 -43
  38. package/dist/update.js +113 -124
  39. package/dist/wire.js +23 -5
  40. package/package.json +20 -21
  41. package/dist/bootstrap.json +0 -24
  42. package/dist/clis-main.js +0 -23
  43. package/dist/inputs.js +0 -63
  44. package/how-we-work.md +0 -33
  45. package/skills/ask-matt/LICENSE +0 -21
  46. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  47. package/skills/ask-matt/SKILL.md +0 -97
  48. package/skills/ask-matt/agents/openai.yaml +0 -5
  49. package/skills/code-review/LICENSE +0 -21
  50. package/skills/code-review/SKILL.md +0 -89
  51. package/skills/code-review/agents/openai.yaml +0 -3
  52. package/skills/codebase-design/DEEPENING.md +0 -37
  53. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  54. package/skills/codebase-design/LICENSE +0 -21
  55. package/skills/codebase-design/SKILL.md +0 -114
  56. package/skills/codebase-design/agents/openai.yaml +0 -3
  57. package/skills/diagnosing-bugs/LICENSE +0 -21
  58. package/skills/diagnosing-bugs/SKILL.md +0 -138
  59. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  60. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  61. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  62. package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
  63. package/skills/domain-modeling/LICENSE +0 -21
  64. package/skills/domain-modeling/SKILL.md +0 -74
  65. package/skills/domain-modeling/agents/openai.yaml +0 -3
  66. package/skills/grill-me/LICENSE +0 -21
  67. package/skills/grill-me/SKILL.md +0 -7
  68. package/skills/grill-me/agents/openai.yaml +0 -5
  69. package/skills/grill-with-docs/LICENSE +0 -21
  70. package/skills/grill-with-docs/SKILL.md +0 -7
  71. package/skills/grill-with-docs/agents/openai.yaml +0 -5
  72. package/skills/grilling/LICENSE +0 -21
  73. package/skills/grilling/SKILL.md +0 -30
  74. package/skills/grilling/agents/openai.yaml +0 -3
  75. package/skills/handoff/LICENSE +0 -21
  76. package/skills/handoff/SKILL.md +0 -16
  77. package/skills/handoff/agents/openai.yaml +0 -5
  78. package/skills/implement/LICENSE +0 -21
  79. package/skills/implement/SKILL.md +0 -17
  80. package/skills/implement/agents/openai.yaml +0 -5
  81. package/skills/implement-spec/LICENSE +0 -21
  82. package/skills/implement-spec/SKILL.md +0 -40
  83. package/skills/implement-spec/agents/openai.yaml +0 -5
  84. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  85. package/skills/improve-codebase-architecture/LICENSE +0 -21
  86. package/skills/improve-codebase-architecture/SKILL.md +0 -71
  87. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  88. package/skills/pr/CREDITS.md +0 -3
  89. package/skills/pr/LICENSE +0 -21
  90. package/skills/pr/SKILL.md +0 -170
  91. package/skills/pr/agents/openai.yaml +0 -3
  92. package/skills/prototype/LICENSE +0 -21
  93. package/skills/prototype/LOGIC.md +0 -67
  94. package/skills/prototype/SKILL.md +0 -26
  95. package/skills/prototype/UI.md +0 -112
  96. package/skills/prototype/agents/openai.yaml +0 -3
  97. package/skills/research/LICENSE +0 -21
  98. package/skills/research/SKILL.md +0 -12
  99. package/skills/research/agents/openai.yaml +0 -3
  100. package/skills/retro/LICENSE +0 -21
  101. package/skills/retro/SKILL.md +0 -44
  102. package/skills/retro/agents/openai.yaml +0 -5
  103. package/skills/tdd/LICENSE +0 -21
  104. package/skills/tdd/SKILL.md +0 -38
  105. package/skills/tdd/agents/openai.yaml +0 -3
  106. package/skills/tdd/mocking.md +0 -59
  107. package/skills/tdd/tests.md +0 -77
  108. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  109. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  110. package/skills/teach/LICENSE +0 -21
  111. package/skills/teach/MISSION-FORMAT.md +0 -31
  112. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  113. package/skills/teach/SKILL.md +0 -140
  114. package/skills/teach/agents/openai.yaml +0 -5
  115. package/skills/to-questionnaire/LICENSE +0 -21
  116. package/skills/to-questionnaire/SKILL.md +0 -54
  117. package/skills/to-questionnaire/agents/openai.yaml +0 -5
  118. package/skills/to-spec/LICENSE +0 -21
  119. package/skills/to-spec/SKILL.md +0 -75
  120. package/skills/to-spec/agents/openai.yaml +0 -5
  121. package/skills/to-tickets/LICENSE +0 -21
  122. package/skills/to-tickets/SKILL.md +0 -105
  123. package/skills/to-tickets/agents/openai.yaml +0 -5
  124. package/skills/triage/AGENT-BRIEF.md +0 -207
  125. package/skills/triage/LICENSE +0 -21
  126. package/skills/triage/OUT-OF-SCOPE.md +0 -105
  127. package/skills/triage/SKILL.md +0 -112
  128. package/skills/triage/agents/openai.yaml +0 -5
  129. package/skills/wait-what/LICENSE +0 -21
  130. package/skills/wait-what/SKILL.md +0 -7
  131. package/skills/wait-what/agents/openai.yaml +0 -5
  132. package/skills/wayfinder/LICENSE +0 -21
  133. package/skills/wayfinder/SKILL.md +0 -128
  134. package/skills/wayfinder/agents/openai.yaml +0 -5
  135. package/skills/wizard/LICENSE +0 -21
  136. package/skills/wizard/SKILL.md +0 -44
  137. package/skills/wizard/agents/openai.yaml +0 -3
  138. package/skills/wizard/template.sh +0 -223
  139. package/skills/writing-for-agents/LICENSE +0 -21
  140. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  141. package/skills/writing-for-agents/SKILL.md +0 -81
  142. package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/events.js CHANGED
@@ -1,8 +1,17 @@
1
+ const AUTONOMOUS_ORIGINS = new Set(['task-notification', 'peer', 'coordinator', 'observer', 'observer-activity']);
1
2
  const COMMAND = { shown: 'command' };
2
3
  const HIDDEN = { shown: 'hidden' };
3
4
  const FINISHED = new Set(['completed', 'failed']);
4
5
  export const RUNNING_JOB = new Set(['running', 'paused']);
5
- const ROUTINE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request' };
6
+ const MESSAGE_TARGETS = new Map([
7
+ ['send_agent_function_request', ['agent_function', 'agent']],
8
+ ['cancel_agent_function_request', ['request']],
9
+ ['send_mail', ['conversation', 'request']],
10
+ ]);
11
+ function counted(reports) {
12
+ const whole = reports.every((tokens) => Object.values(tokens).every((count) => Number.isSafeInteger(count) && count >= 0));
13
+ return whole ? reports.map((tokens) => ({ kind: 'tokens', tokens })) : [];
14
+ }
6
15
  function words(line) {
7
16
  const found = [];
8
17
  let word = null;
@@ -48,11 +57,13 @@ export function houseMessage(line) {
48
57
  catch {
49
58
  return null;
50
59
  }
51
- const argument = ROUTINE_TARGETS[call.command];
52
- if (argument === undefined)
60
+ if (call === null)
61
+ return null;
62
+ const targets = MESSAGE_TARGETS.get(call.command);
63
+ if (targets === undefined)
53
64
  return null;
54
- const target = call.arguments?.[argument];
55
- return { shown: 'message', command: call.command, target: typeof target === 'string' ? target : null };
65
+ const target = targets.map((argument) => call.arguments?.[argument]).find((value) => typeof value === 'string');
66
+ return { shown: 'message', command: call.command, target: target ?? null };
56
67
  }
57
68
  function decidedByLine(command, final) {
58
69
  const line = typeof command === 'string' ? command : Array.isArray(command) ? command.at(-1) : undefined;
@@ -269,6 +280,17 @@ class Reader {
269
280
  class ClaudeReader extends Reader {
270
281
  watchedOptions = { model: 'model' };
271
282
  tasks = new Map();
283
+ stop = null;
284
+ answered(result) {
285
+ const models = (result?._meta?.quota?.model_usage ?? []);
286
+ const spent = counted(models.map(({ token_count: count }) => ({
287
+ input: count?.inputTokens,
288
+ output: count?.outputTokens,
289
+ cache_read: count?.cachedInputTokens,
290
+ cache_write: count?.cachedWriteTokens,
291
+ })));
292
+ return [...spent, ...super.answered(result)];
293
+ }
272
294
  own(message) {
273
295
  if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
274
296
  return [];
@@ -276,7 +298,9 @@ class ClaudeReader extends Reader {
276
298
  }
277
299
  update(update) {
278
300
  if (update.sessionUpdate === 'usage_update' && update._meta?.['_claude/origin'] !== undefined) {
279
- return [...super.update(update), { kind: 'ended' }];
301
+ const stop = this.stop;
302
+ this.stop = null;
303
+ return [...super.update(update), { kind: 'ended', stop }];
280
304
  }
281
305
  if (update.sessionUpdate === 'session_info_update' && update._meta?.jetbrains?.air?.sessionFailure !== undefined) {
282
306
  return [{ kind: 'marker', marker: 'retry', text: update._meta.jetbrains.air.sessionFailure.title }];
@@ -287,6 +311,13 @@ class ClaudeReader extends Reader {
287
311
  if (message.method !== '_claude/sdkMessage')
288
312
  return [];
289
313
  const sdk = message.params.message;
314
+ if (sdk.type === 'result') {
315
+ if (AUTONOMOUS_ORIGINS.has(sdk.origin?.kind))
316
+ this.stop = null;
317
+ else
318
+ this.stop = sdk.stop_reason === 'refusal' ? 'refusal' : sdk.is_error === true ? null : 'end_turn';
319
+ return [];
320
+ }
290
321
  switch (sdk.subtype) {
291
322
  case 'task_started':
292
323
  if (sdk.tool_use_id !== undefined)
@@ -294,6 +325,10 @@ class ClaudeReader extends Reader {
294
325
  return [];
295
326
  case 'task_notification':
296
327
  return sdk.tool_use_id === undefined ? [] : this.finish(sdk.tool_use_id, sdk.summary ?? null);
328
+ case 'task_progress':
329
+ if (!this.subagents.has(sdk.tool_use_id) || this.done.has(sdk.tool_use_id) || typeof sdk.description !== 'string')
330
+ return [];
331
+ return [{ kind: 'subagent-activity', id: sdk.tool_use_id, activity: sdk.description }];
297
332
  default:
298
333
  return [];
299
334
  }
@@ -343,6 +378,20 @@ const COLLABORATION = new Set(['spawnAgent', 'sendInput', 'resumeAgent', 'wait',
343
378
  class CodexReader extends Reader {
344
379
  watchedOptions = { mode: 'mode', collaboration_mode: 'mode' };
345
380
  children = new Map();
381
+ answered(result) {
382
+ const usage = result?.usage;
383
+ const spent = usage === null || usage === undefined
384
+ ? []
385
+ : counted([
386
+ {
387
+ input: usage.inputTokens,
388
+ output: usage.outputTokens,
389
+ cache_read: usage.cachedReadTokens,
390
+ cache_write: usage.cachedWriteTokens,
391
+ },
392
+ ]);
393
+ return [...spent, ...super.answered(result)];
394
+ }
346
395
  own(message) {
347
396
  if (message.method === 'session/request_permission' && message.params.toolCall?.kind === 'switch_mode') {
348
397
  return [{ kind: 'started' }, { kind: 'marker', marker: 'mode', text: message.params.toolCall.title ?? '' }];
@@ -356,7 +405,7 @@ class CodexReader extends Reader {
356
405
  if (codex?.threadStatus?.type === 'active')
357
406
  return [{ kind: 'started' }];
358
407
  if (codex?.threadStatus?.type === 'idle')
359
- return [{ kind: 'ended' }];
408
+ return [{ kind: 'ended', stop: null }];
360
409
  if (codex?.error !== undefined)
361
410
  return [{ kind: 'marker', marker: 'retry', text: codex.error.message ?? '' }];
362
411
  return [];
@@ -429,7 +478,9 @@ class GrokReader extends Reader {
429
478
  return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
430
479
  }
431
480
  if (message.params?.update?.sessionUpdate === 'turn_completed') {
432
- return session === this.session ? [{ kind: 'ended' }] : [];
481
+ if (session !== this.session)
482
+ return [];
483
+ return [...this.spent(message.params.update.usage), { kind: 'ended', stop: null }];
433
484
  }
434
485
  if (message.method === '_x.ai/models/update') {
435
486
  this.offered(message.params);
@@ -437,6 +488,13 @@ class GrokReader extends Reader {
437
488
  }
438
489
  return super.read(message);
439
490
  }
491
+ spent(usage) {
492
+ if (usage === null || usage === undefined)
493
+ return [];
494
+ const { inputTokens, outputTokens, cachedReadTokens, cacheCreationTokens } = usage;
495
+ const input = inputTokens - cachedReadTokens - cacheCreationTokens;
496
+ return counted([{ input, output: outputTokens, cache_read: cachedReadTokens, cache_write: cacheCreationTokens }]);
497
+ }
440
498
  offered(models) {
441
499
  this.models = models.availableModels ?? this.models;
442
500
  this.model = models.currentModelId ?? this.model;
package/dist/files.js CHANGED
@@ -1,42 +1,144 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { HouseRefusal, retrying } from './api.js';
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';
5
10
  import { relayed } from './relay.js';
6
- export function verified(bytes, sha256, name) {
7
- if (createHash('sha256').update(bytes).digest('hex') !== sha256)
8
- throw new Error(`${name} did not arrive whole; download it again`);
9
- return bytes;
10
- }
11
- export async function placeFiles(house, directory, files, signal) {
12
- const paths = [];
13
- for (const file of files) {
14
- const original = await retrying(() => house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal), signal);
15
- const answer = await fetch(relayed(original.download.url), { signal });
16
- if (!answer.ok)
17
- throw new Error(`${file.name} did not download: ${answer.status}`);
18
- const folder = join(directory, '.house', 'files', original.message, file.version);
19
- await mkdir(join(directory, '.house')).catch((error) => {
20
- if (error.code !== 'EEXIST')
11
+ const STALLED_ROUNDS = 8;
12
+ const PART_CHECKSUM = 'x-amz-checksum-sha256';
13
+ const LINK = /\]\((\/rooms\/[^/)\s]+\/[^)\s]+)\)/gu;
14
+ export function linkedPaths(text) {
15
+ return [...new Set(Array.from(text.matchAll(LINK), (link) => link[1]))];
16
+ }
17
+ export function inspectedFile(answer) {
18
+ const document = parseDocument(answer);
19
+ if (document.errors.length > 0)
20
+ return null;
21
+ const inspected = document.toJS();
22
+ if (typeof inspected?.download !== 'string')
23
+ return null;
24
+ return {
25
+ download: inspected.download,
26
+ sha256: String(inspected.sha256),
27
+ media_type: typeof inspected.media_type === 'string' ? inspected.media_type : null,
28
+ };
29
+ }
30
+ export function paused(milliseconds) {
31
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
32
+ }
33
+ export function storeRefusal(act, status) {
34
+ return new Error(`the ${act} answered ${status}; ${act} the file again`);
35
+ }
36
+ async function partDigests(uploading, partBytes) {
37
+ const whole = createHash('sha256');
38
+ const digests = [];
39
+ for (let start = 0; digests.length === 0 || start < uploading.bytes; start += partBytes) {
40
+ const end = Math.min(start + partBytes, uploading.bytes);
41
+ const digest = createHash('sha256');
42
+ for await (const chunk of uploading.read(start, end)) {
43
+ digest.update(chunk);
44
+ whole.update(chunk);
45
+ }
46
+ digests.push(digest.digest());
47
+ }
48
+ if (whole.digest('hex') !== uploading.sha256)
49
+ throw new Error(`${uploading.name} changed while it was uploaded; upload it again`);
50
+ return digests;
51
+ }
52
+ export async function uploaded(declared, uploading) {
53
+ const partBytes = declared.part_bytes;
54
+ const digests = await partDigests(uploading, partBytes);
55
+ const partSha256 = digests.map((digest) => digest.toString('hex'));
56
+ let answer = await finishing(uploading, declared.id, partSha256);
57
+ let idle = 0;
58
+ let stalled = 0;
59
+ while ('upload' in answer) {
60
+ const { id, parts } = answer.upload;
61
+ let sent = 0;
62
+ let failure = null;
63
+ for (const { part, url } of parts ?? []) {
64
+ const start = (part - 1) * partBytes;
65
+ try {
66
+ await uploading.send(url, start, Math.min(start + partBytes, uploading.bytes), {
67
+ [PART_CHECKSUM]: digests[part - 1].toString('base64'),
68
+ });
69
+ sent++;
70
+ }
71
+ catch (error) {
72
+ failure = error;
73
+ break;
74
+ }
75
+ }
76
+ if (sent > 0) {
77
+ idle = 0;
78
+ stalled = 0;
79
+ }
80
+ else {
81
+ if (failure !== null && ++stalled >= STALLED_ROUNDS)
82
+ throw failure;
83
+ // A round that sent no part, its store unreachable, is followed by the next finishing call on the jittered curve.
84
+ await paused(retryDelay(idle++));
85
+ }
86
+ answer = await finishing(uploading, id, partSha256);
87
+ }
88
+ return answer;
89
+ }
90
+ async function finishing(uploading, upload, partSha256) {
91
+ for (let unsettled = 1;; unsettled++) {
92
+ try {
93
+ return await uploading.finish(upload, partSha256);
94
+ }
95
+ catch (error) {
96
+ if (!(error instanceof Unsettled) || unsettled === STALLED_ROUNDS)
21
97
  throw error;
22
- });
23
- await mkdir(folder, { recursive: true });
24
- const path = join(folder, file.name);
25
- await writeFile(path, verified(Buffer.from(await answer.arrayBuffer()), file.sha256, file.name));
26
- paths.push(path);
98
+ }
99
+ // 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.
100
+ await paused(retryDelay(unsettled - 1));
27
101
  }
28
- return paths;
29
- }
30
- export function uploadBytes(upload, bytes) {
31
- return retrying(async () => {
32
- const answer = await fetch(relayed(upload.url), {
33
- method: 'POST',
34
- headers: { 'content-type': 'application/octet-stream', 'x-house-byte-operation': upload.operation },
35
- body: bytes,
102
+ }
103
+ async function drained(body) {
104
+ for await (const _chunk of body)
105
+ ;
106
+ }
107
+ export async function downloaded(address, fetching, destination, name) {
108
+ for (let asked = 1;; asked++) {
109
+ const { download, sha256 } = await address();
110
+ const answer = await fetching(download);
111
+ if (answer.status === 403 && asked === 1) {
112
+ await drained(answer.body);
113
+ continue;
114
+ }
115
+ if (answer.status !== 200) {
116
+ await drained(answer.body);
117
+ throw storeRefusal('download', answer.status);
118
+ }
119
+ await mkdir(dirname(destination), { recursive: true });
120
+ const partial = join(dirname(destination), `.${basename(destination)}.${randomUUID()}`);
121
+ const digest = createHash('sha256');
122
+ const hashed = new Transform({
123
+ transform(chunk, _encoding, done) {
124
+ digest.update(chunk);
125
+ done(null, chunk);
126
+ },
36
127
  });
37
- const text = await answer.text();
38
- if (!answer.ok)
39
- throw new HouseRefusal(upload.url, answer.status, text);
40
- return JSON.parse(text);
41
- });
128
+ try {
129
+ await pipeline(answer.body, hashed, createWriteStream(partial, { flags: 'wx' }));
130
+ if (digest.digest('hex') !== sha256)
131
+ throw new Error(`${name} did not arrive whole; download it again`);
132
+ await rename(partial, destination);
133
+ }
134
+ catch (error) {
135
+ await rm(partial, { force: true });
136
+ throw error;
137
+ }
138
+ return;
139
+ }
140
+ }
141
+ export async function fetched(url, signal) {
142
+ const answer = await fetch(relayed(url), { signal });
143
+ return { status: answer.status, body: answer.body === null ? Readable.from([]) : Readable.fromWeb(answer.body) };
42
144
  }
package/dist/git.js CHANGED
@@ -2,16 +2,16 @@ import { spawn } from 'node:child_process';
2
2
  import { isUtf8 } from 'node:buffer';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
- import { withoutProxy } from './clis.js';
6
5
  import { workspaceRoot } from './home.js';
6
+ import { childEnvironment } from './relay.js';
7
7
  export function localCopies() {
8
8
  return join(workspaceRoot(), 'local-copies');
9
9
  }
10
10
  export const HOUSE_REF = 'refs/house/received';
11
- const COMMITTER = 'House Kit <house-kit@localhost>';
11
+ const COMMITTER = 'Kit <house-kit@localhost>';
12
12
  export function run(cwd, args, input, env = {}) {
13
13
  return new Promise((resolve, reject) => {
14
- const child = spawn('git', args, { cwd, env: { ...withoutProxy(), ...env } });
14
+ const child = spawn('git', args, { cwd, env: { ...childEnvironment(), ...env } });
15
15
  const stdout = [];
16
16
  let stderr = '';
17
17
  child.stdout.on('data', (chunk) => stdout.push(chunk));
@@ -70,18 +70,52 @@ function data(text) {
70
70
  const bytes = Buffer.from(text, 'utf8');
71
71
  return [Buffer.from(`data ${bytes.length}\n`), bytes, Buffer.from('\n')];
72
72
  }
73
- export async function importCommits(repository, commits) {
74
- const stream = [];
73
+ function written(stream, bytes) {
74
+ return new Promise((resolve, reject) => {
75
+ stream.write(bytes, (error) => (error ? reject(error) : resolve()));
76
+ });
77
+ }
78
+ async function imported(stream, commits) {
79
+ await written(stream, Buffer.from('feature done\n'));
75
80
  for (const [index, commit] of commits.entries()) {
76
- stream.push(Buffer.from(`commit ${HOUSE_REF}\nmark :${index + 1}\ncommitter ${COMMITTER} ${Math.floor(Date.now() / 1000)} +0000\n`), ...data(commit.message), ...(commit.from === null ? [] : [Buffer.from(`from ${commit.from}\n`)]), ...(commit.merge === undefined ? [] : [Buffer.from(`merge ${commit.merge}\n`)]), ...(commit.replaces === true ? [Buffer.from('deleteall\n')] : []));
77
- for (const [path, content] of commit.files) {
78
- if (content === null)
79
- stream.push(Buffer.from(`D ${quoted(path)}\n`));
80
- else
81
- stream.push(Buffer.from(`M 100644 inline ${quoted(path)}\n`), ...data(content));
81
+ await written(stream, Buffer.concat([
82
+ Buffer.from(`commit ${HOUSE_REF}\nmark :${index + 1}\ncommitter ${COMMITTER} ${Math.floor(Date.now() / 1000)} +0000\n`),
83
+ ...data(commit.message),
84
+ ...(commit.from === null ? [] : [Buffer.from(`from ${commit.from}\n`)]),
85
+ ...(commit.merge === undefined ? [] : [Buffer.from(`merge ${commit.merge}\n`)]),
86
+ ...(commit.replaces === true ? [Buffer.from('deleteall\n')] : []),
87
+ ]));
88
+ for await (const [path, content] of commit.files) {
89
+ await written(stream, content === null
90
+ ? Buffer.from(`D ${quoted(path)}\n`)
91
+ : Buffer.concat([Buffer.from(`M 100644 inline ${quoted(path)}\n`), ...data(content)]));
82
92
  }
83
- stream.push(Buffer.from('\n'));
93
+ await written(stream, Buffer.from('\n'));
94
+ }
95
+ await written(stream, Buffer.from('done\n'));
96
+ }
97
+ export async function importCommits(repository, commits) {
98
+ const child = spawn('git', ['fast-import', '--quiet', '--force'], { cwd: repository, env: childEnvironment() });
99
+ let stderr = '';
100
+ child.stdout.resume();
101
+ child.stderr.on('data', (chunk) => {
102
+ stderr += chunk.toString('utf8');
103
+ });
104
+ child.stdin.on('error', () => undefined);
105
+ const closed = new Promise((resolve, reject) => {
106
+ child.on('error', reject);
107
+ child.on('close', resolve);
108
+ });
109
+ try {
110
+ await imported(child.stdin, commits);
111
+ child.stdin.end();
112
+ }
113
+ catch (error) {
114
+ child.stdin.destroy();
115
+ await closed;
116
+ throw error.code === 'EPIPE' ? new Error(`git fast-import failed: ${stderr.trim()}`) : error;
84
117
  }
85
- await git(repository, ['fast-import', '--quiet', '--force'], Buffer.concat(stream));
118
+ if ((await closed) !== 0)
119
+ throw new Error(`git fast-import failed: ${stderr.trim()}`);
86
120
  return revision(repository, HOUSE_REF);
87
121
  }
package/dist/home.js CHANGED
@@ -8,7 +8,6 @@ const ENROLMENT = 'credential.json';
8
8
  const DISCONNECTED = 'disconnected.json';
9
9
  const CONFIGURATION = 'kit.json';
10
10
  const OWN_AGENT = 'own-agent.json';
11
- const RESTART = 'restart';
12
11
  let defaulted;
13
12
  function writable(directory) {
14
13
  try {
@@ -132,12 +131,6 @@ export async function forgetRejected(rejected) {
132
131
  export function enrolled() {
133
132
  return existsSync(join(kitHome(), ENROLMENT));
134
133
  }
135
- export function restartAsked() {
136
- return existsSync(join(kitHome(), RESTART));
137
- }
138
- export function forgetRestart() {
139
- return rm(join(kitHome(), RESTART), { force: true });
140
- }
141
134
  export function readOwnAgent() {
142
135
  return readHome(OWN_AGENT);
143
136
  }