@agentshouse/kit 0.2.1-cb345f4157685 → 0.2.1-cb66cb2fd0696

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 (140) hide show
  1. package/README.md +28 -28
  2. package/dist/acp.js +58 -7
  3. package/dist/agents.js +85 -40
  4. package/dist/api.js +5 -5
  5. package/dist/authority-main.js +2 -2
  6. package/dist/bridge.js +1 -1
  7. package/dist/building.js +50 -0
  8. package/dist/calls.js +71 -0
  9. package/dist/clis.js +34 -40
  10. package/dist/{stream.js → connection.js} +31 -49
  11. package/dist/conversations.js +423 -139
  12. package/dist/copies.js +36 -43
  13. package/dist/end.js +14 -4
  14. package/dist/events.js +29 -8
  15. package/dist/files.js +139 -37
  16. package/dist/git.js +3 -3
  17. package/dist/home.js +0 -7
  18. package/dist/house-cli.js +95 -87
  19. package/dist/house-main.js +3 -3
  20. package/dist/instructions.js +29 -9
  21. package/dist/kit-cli.js +4 -4
  22. package/dist/kit-main.js +3 -3
  23. package/dist/login.js +4 -4
  24. package/dist/mcp.js +6 -2
  25. package/dist/placement.js +21 -20
  26. package/dist/relay.js +21 -0
  27. package/dist/resident.js +45 -38
  28. package/dist/scope.js +15 -4
  29. package/dist/scripts.js +255 -0
  30. package/dist/service.js +1 -1
  31. package/dist/sign-in.js +22 -21
  32. package/dist/silence.js +28 -0
  33. package/dist/skills.js +42 -97
  34. package/dist/transcription.js +83 -43
  35. package/dist/translate.js +2 -8
  36. package/dist/update.js +91 -125
  37. package/dist/wire.js +19 -3
  38. package/package.json +20 -21
  39. package/dist/bootstrap.json +0 -24
  40. package/dist/clis-main.js +0 -23
  41. package/dist/inputs.js +0 -37
  42. package/how-we-work.md +0 -33
  43. package/skills/ask-matt/LICENSE +0 -21
  44. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  45. package/skills/ask-matt/SKILL.md +0 -97
  46. package/skills/ask-matt/agents/openai.yaml +0 -5
  47. package/skills/code-review/LICENSE +0 -21
  48. package/skills/code-review/SKILL.md +0 -89
  49. package/skills/code-review/agents/openai.yaml +0 -3
  50. package/skills/codebase-design/DEEPENING.md +0 -37
  51. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  52. package/skills/codebase-design/LICENSE +0 -21
  53. package/skills/codebase-design/SKILL.md +0 -114
  54. package/skills/codebase-design/agents/openai.yaml +0 -3
  55. package/skills/diagnosing-bugs/LICENSE +0 -21
  56. package/skills/diagnosing-bugs/SKILL.md +0 -138
  57. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  58. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  59. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  60. package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
  61. package/skills/domain-modeling/LICENSE +0 -21
  62. package/skills/domain-modeling/SKILL.md +0 -74
  63. package/skills/domain-modeling/agents/openai.yaml +0 -3
  64. package/skills/grill-me/LICENSE +0 -21
  65. package/skills/grill-me/SKILL.md +0 -7
  66. package/skills/grill-me/agents/openai.yaml +0 -5
  67. package/skills/grill-with-docs/LICENSE +0 -21
  68. package/skills/grill-with-docs/SKILL.md +0 -7
  69. package/skills/grill-with-docs/agents/openai.yaml +0 -5
  70. package/skills/grilling/LICENSE +0 -21
  71. package/skills/grilling/SKILL.md +0 -30
  72. package/skills/grilling/agents/openai.yaml +0 -3
  73. package/skills/handoff/LICENSE +0 -21
  74. package/skills/handoff/SKILL.md +0 -16
  75. package/skills/handoff/agents/openai.yaml +0 -5
  76. package/skills/implement/LICENSE +0 -21
  77. package/skills/implement/SKILL.md +0 -17
  78. package/skills/implement/agents/openai.yaml +0 -5
  79. package/skills/implement-spec/LICENSE +0 -21
  80. package/skills/implement-spec/SKILL.md +0 -40
  81. package/skills/implement-spec/agents/openai.yaml +0 -5
  82. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  83. package/skills/improve-codebase-architecture/LICENSE +0 -21
  84. package/skills/improve-codebase-architecture/SKILL.md +0 -71
  85. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  86. package/skills/pr/CREDITS.md +0 -3
  87. package/skills/pr/LICENSE +0 -21
  88. package/skills/pr/SKILL.md +0 -170
  89. package/skills/pr/agents/openai.yaml +0 -3
  90. package/skills/prototype/LICENSE +0 -21
  91. package/skills/prototype/LOGIC.md +0 -67
  92. package/skills/prototype/SKILL.md +0 -26
  93. package/skills/prototype/UI.md +0 -112
  94. package/skills/prototype/agents/openai.yaml +0 -3
  95. package/skills/research/LICENSE +0 -21
  96. package/skills/research/SKILL.md +0 -12
  97. package/skills/research/agents/openai.yaml +0 -3
  98. package/skills/retro/LICENSE +0 -21
  99. package/skills/retro/SKILL.md +0 -44
  100. package/skills/retro/agents/openai.yaml +0 -5
  101. package/skills/tdd/LICENSE +0 -21
  102. package/skills/tdd/SKILL.md +0 -38
  103. package/skills/tdd/agents/openai.yaml +0 -3
  104. package/skills/tdd/mocking.md +0 -59
  105. package/skills/tdd/tests.md +0 -77
  106. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  107. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  108. package/skills/teach/LICENSE +0 -21
  109. package/skills/teach/MISSION-FORMAT.md +0 -31
  110. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  111. package/skills/teach/SKILL.md +0 -140
  112. package/skills/teach/agents/openai.yaml +0 -5
  113. package/skills/to-questionnaire/LICENSE +0 -21
  114. package/skills/to-questionnaire/SKILL.md +0 -54
  115. package/skills/to-questionnaire/agents/openai.yaml +0 -5
  116. package/skills/to-spec/LICENSE +0 -21
  117. package/skills/to-spec/SKILL.md +0 -75
  118. package/skills/to-spec/agents/openai.yaml +0 -5
  119. package/skills/to-tickets/LICENSE +0 -21
  120. package/skills/to-tickets/SKILL.md +0 -105
  121. package/skills/to-tickets/agents/openai.yaml +0 -5
  122. package/skills/triage/AGENT-BRIEF.md +0 -207
  123. package/skills/triage/LICENSE +0 -21
  124. package/skills/triage/OUT-OF-SCOPE.md +0 -105
  125. package/skills/triage/SKILL.md +0 -112
  126. package/skills/triage/agents/openai.yaml +0 -5
  127. package/skills/wait-what/LICENSE +0 -21
  128. package/skills/wait-what/SKILL.md +0 -7
  129. package/skills/wait-what/agents/openai.yaml +0 -5
  130. package/skills/wayfinder/LICENSE +0 -21
  131. package/skills/wayfinder/SKILL.md +0 -128
  132. package/skills/wayfinder/agents/openai.yaml +0 -5
  133. package/skills/wizard/LICENSE +0 -21
  134. package/skills/wizard/SKILL.md +0 -44
  135. package/skills/wizard/agents/openai.yaml +0 -3
  136. package/skills/wizard/template.sh +0 -223
  137. package/skills/writing-for-agents/LICENSE +0 -21
  138. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  139. package/skills/writing-for-agents/SKILL.md +0 -81
  140. package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/calls.js ADDED
@@ -0,0 +1,71 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { kitHome } from './home.js';
4
+ function logged(error) {
5
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
6
+ }
7
+ export class Calls {
8
+ house;
9
+ changed;
10
+ carried = new Map();
11
+ path = join(kitHome(), 'call-results.json');
12
+ results;
13
+ returned = new Set();
14
+ released = new Set();
15
+ open = 0;
16
+ constructor(house, changed) {
17
+ this.house = house;
18
+ this.changed = changed;
19
+ this.results = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
20
+ }
21
+ keepResult(callId, result) {
22
+ if (this.returned.has(callId))
23
+ return;
24
+ this.results.set(callId, result);
25
+ this.save();
26
+ }
27
+ idle() {
28
+ return this.open === 0;
29
+ }
30
+ carry(callId, work) {
31
+ if (this.released.has(callId))
32
+ return Promise.resolve();
33
+ const known = this.carried.get(callId);
34
+ const result = this.results.get(callId);
35
+ const carried = known ?? (result === undefined ? work() : Promise.resolve({ result }));
36
+ if (known === undefined) {
37
+ this.carried.set(callId, carried);
38
+ carried.catch(() => this.carried.delete(callId));
39
+ }
40
+ this.open++;
41
+ this.changed();
42
+ return carried
43
+ .then(async ({ result, returned, release }) => {
44
+ try {
45
+ await this.house.deliver(`/kit/inputs/${callId}/ack`, result);
46
+ }
47
+ finally {
48
+ this.returned.add(callId);
49
+ if (this.results.delete(callId))
50
+ this.save();
51
+ }
52
+ if (known !== undefined)
53
+ return;
54
+ if (release) {
55
+ this.carried.delete(callId);
56
+ this.released.add(callId);
57
+ }
58
+ await returned?.();
59
+ })
60
+ .catch(logged)
61
+ .finally(() => {
62
+ this.open--;
63
+ this.changed();
64
+ });
65
+ }
66
+ save() {
67
+ const temporary = `${this.path}.${process.pid}`;
68
+ writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.results)), { mode: 0o600 });
69
+ renameSync(temporary, this.path);
70
+ }
71
+ }
package/dist/clis.js CHANGED
@@ -4,7 +4,8 @@ import { constants, readFileSync } from 'node:fs';
4
4
  import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
5
5
  import { homedir, userInfo } from 'node:os';
6
6
  import { delimiter, dirname, isAbsolute, join } from 'node:path';
7
- import { kitHome } from './home.js';
7
+ import { releaseDirectory } from './placement.js';
8
+ import { childEnvironment } from './relay.js';
8
9
  const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
9
10
  function houseRule(houses) {
10
11
  return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
@@ -14,6 +15,7 @@ const RAW_SUBTYPES = [
14
15
  'task_started',
15
16
  'task_updated',
16
17
  'task_notification',
18
+ 'task_progress',
17
19
  'session_state_changed',
18
20
  'hook_started',
19
21
  'hook_response',
@@ -69,8 +71,9 @@ function bridgeInClaudeSandbox(bridge) {
69
71
  export const CLIS = {
70
72
  'codex-acp': {
71
73
  bin: 'codex',
72
- minimum: '0.159.1',
74
+ minimum: '0.160.1',
73
75
  install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
76
+ update: 'codex update',
74
77
  adapter: {
75
78
  package: '@agentclientprotocol/codex-acp',
76
79
  bin: 'codex-acp',
@@ -92,8 +95,9 @@ export const CLIS = {
92
95
  },
93
96
  'claude-agent-acp': {
94
97
  bin: 'claude',
95
- minimum: '2.1.286',
98
+ minimum: '2.1.303',
96
99
  install: 'curl -fsSL https://claude.ai/install.sh | bash',
100
+ update: 'claude update',
97
101
  adapter: {
98
102
  package: '@agentclientprotocol/claude-agent-acp',
99
103
  bin: 'claude-agent-acp',
@@ -110,7 +114,7 @@ export const CLIS = {
110
114
  title,
111
115
  settings: { sandbox: { network: bridgeInClaudeSandbox(bridge) } },
112
116
  },
113
- emitRawSDKMessages: RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })),
117
+ emitRawSDKMessages: [...RAW_SUBTYPES.map((subtype) => ({ type: 'system', subtype })), { type: 'result' }],
114
118
  },
115
119
  }),
116
120
  unknownSession: (error) => answered(error).code === -32002,
@@ -124,6 +128,7 @@ export const CLIS = {
124
128
  bin: 'grok',
125
129
  minimum: '1.0.46',
126
130
  install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
131
+ update: 'grok update',
127
132
  adapter: null,
128
133
  args: ['--sandbox', 'off', 'agent', '--always-approve', '--no-leader', 'stdio'],
129
134
  fullAccess: null,
@@ -137,38 +142,22 @@ export const CLIS = {
137
142
  },
138
143
  };
139
144
  const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
140
- export const KIT_VERSION = KIT_PACKAGE.version;
141
- const PROXY = /^https?_proxy$/i;
145
+ export const KIT_RELEASE = KIT_PACKAGE.version;
142
146
  const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
143
- // An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
147
+ // An install or update still running after fifteen minutes is ended as failed, so a hung download can neither hold its CLI's report nor keep the Kit busy.
144
148
  const INSTALL_MS = 15 * 60_000;
145
149
  // A login shell or a version read is ended after half a minute, so a profile that waits for input cannot stall the report.
146
150
  const READ_MS = 30_000;
147
151
  const CAUSE_CHARACTERS = 4000;
148
- function prefix(kind) {
149
- return join(kitHome(), 'agents', kind);
150
- }
151
152
  export function adapterCommand(kind) {
152
- return join(prefix(kind), 'node_modules', '.bin', CLIS[kind].adapter.bin);
153
- }
154
- async function installedAdapter(kind) {
155
- try {
156
- const manifest = join(prefix(kind), 'node_modules', CLIS[kind].adapter.package, 'package.json');
157
- return JSON.parse(await readFile(manifest, 'utf8')).version;
158
- }
159
- catch {
160
- return null;
161
- }
162
- }
163
- export function withoutProxy() {
164
- return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
153
+ return join(releaseDirectory(KIT_RELEASE), 'adapters', kind, 'node_modules', '.bin', CLIS[kind].adapter.bin);
165
154
  }
166
155
  export function run(command, args, timeoutMs, detached = false, signal) {
167
156
  return new Promise((resolve) => {
168
157
  const child = spawn(command, args, {
169
158
  stdio: ['ignore', 'pipe', 'pipe'],
170
159
  timeout: timeoutMs,
171
- env: withoutProxy(),
160
+ env: childEnvironment(),
172
161
  detached,
173
162
  signal: detached ? undefined : signal,
174
163
  });
@@ -202,19 +191,18 @@ export function run(command, args, timeoutMs, detached = false, signal) {
202
191
  export function failureOf(ran, program) {
203
192
  return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
204
193
  }
205
- export async function installAdapter(kind, signal) {
206
- const adapter = CLIS[kind].adapter;
207
- if (adapter === null)
208
- return null;
209
- const release = KIT_PACKAGE.peerDependencies[adapter.package];
210
- if ((await installedAdapter(kind)) === release)
211
- return null;
212
- const ran = await run('npm', ['install', '--prefix', prefix(kind), '--cache', join(kitHome(), 'npm-cache'), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
213
- return ran.status === 0 ? null : failureOf(ran, 'npm');
194
+ export function adapterArchive(kind) {
195
+ return `${kind}-${KIT_PACKAGE.peerDependencies[CLIS[kind].adapter.package]}.tar.gz`;
196
+ }
197
+ async function bounded(program, work, signal) {
198
+ const deadline = AbortSignal.timeout(INSTALL_MS);
199
+ const ran = await work(signal === undefined ? deadline : AbortSignal.any([signal, deadline]));
200
+ if (deadline.aborted && signal?.aborted !== true)
201
+ return `${program} did not end within ${INSTALL_MS / 1000} seconds`;
202
+ return ran.status === 0 ? null : failureOf(ran, program);
214
203
  }
215
- export async function installCli(kind, signal) {
216
- const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
217
- return ran.status === 0 ? null : failureOf(ran, 'the install');
204
+ export function installCli(kind, signal) {
205
+ return bounded('the install', (within) => run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, within), signal);
218
206
  }
219
207
  function loginShell() {
220
208
  return process.env.SHELL || userInfo().shell;
@@ -222,13 +210,19 @@ function loginShell() {
222
210
  const FOUND = 'house-kit-cli ';
223
211
  const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
224
212
  const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
225
- function inLoginShell(script, argument = '', signal) {
226
- return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
213
+ function inLoginShell(script, argument = '', signal, timeoutMs = READ_MS) {
214
+ return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], timeoutMs, true, signal);
227
215
  }
228
- export async function locate(kind, signal) {
229
- const ran = await inLoginShell(LOOKUP, CLIS[kind].bin, signal);
216
+ export async function onLoginPath(program, signal) {
217
+ const ran = await inLoginShell(LOOKUP, program, signal);
230
218
  return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
231
219
  }
220
+ export function locate(kind, signal) {
221
+ return onLoginPath(CLIS[kind].bin, signal);
222
+ }
223
+ export function updateCli(kind) {
224
+ return bounded('the update', (within) => inLoginShell(CLIS[kind].update, '', within, INSTALL_MS));
225
+ }
232
226
  export async function loginPath() {
233
227
  const marker = 'house-kit-path ';
234
228
  const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
@@ -1,43 +1,16 @@
1
1
  import { subscribe } from 'node:diagnostics_channel';
2
2
  import { HouseRefusal, retryDelay } from './api.js';
3
- import { end, REPLACED, signInAgain, updateRequired } from './end.js';
3
+ import { end, REPLACED, signInAgain } from './end.js';
4
4
  import { readEnrolment } from './home.js';
5
5
  import { relayed } from './relay.js';
6
- import { KIT_WIRE, tooOld } from './wire.js';
6
+ import { Silence } from './silence.js';
7
+ import { endUnserved, KIT_WIRE } from './wire.js';
7
8
  const PATH = '/kit/stream';
8
9
  const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
9
10
  const REFUSAL_HEADER = 'x-house-refusal';
10
- // House beats each stream every thirty seconds, so two silent beats mean this Kit cannot hear House.
11
- export const STREAM_SILENCE_MS = 60_000;
12
- const SYSTEM_STREAM_CLOCK = {
13
- later: (delay, callback) => setTimeout(callback, delay),
14
- clear: (timer) => clearTimeout(timer),
15
- };
16
- export class StreamSilence {
17
- timer = null;
18
- silent;
19
- clock;
20
- interval;
21
- constructor(silent, clock = SYSTEM_STREAM_CLOCK, interval = STREAM_SILENCE_MS) {
22
- this.silent = silent;
23
- this.clock = clock;
24
- this.interval = interval;
25
- }
26
- heard() {
27
- this.stop();
28
- this.timer = this.clock.later(this.interval, () => {
29
- this.timer = null;
30
- this.silent();
31
- });
32
- }
33
- stop() {
34
- if (this.timer === null)
35
- return;
36
- this.clock.clear(this.timer);
37
- this.timer = null;
38
- }
39
- }
40
- function streamUrl(house) {
11
+ // House beats each Kit connection every thirty seconds, so two silent beats mean this Kit process cannot hear House.
12
+ export const CONNECTION_SILENCE_MS = 60_000;
13
+ function connectionUrl(house) {
41
14
  const url = new URL(PATH, house);
42
15
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
43
16
  return url;
@@ -56,16 +29,20 @@ subscribe('undici:request:headers', (message) => {
56
29
  return;
57
30
  refused = new HouseRefusal(PATH, response.statusCode, header(response.headers, REFUSAL_HEADER) || response.statusText);
58
31
  });
59
- export function holdStream(handlers) {
32
+ export function holdConnection(handlers) {
33
+ let connection = null;
60
34
  let socket = null;
61
35
  let failures = 0;
62
36
  const connect = async () => {
63
37
  const { house, credential } = (await readEnrolment());
38
+ if (socket !== null)
39
+ return;
64
40
  refused = null;
65
- const opening = new WebSocket(relayed(streamUrl(house)), {
41
+ const opening = new WebSocket(relayed(connectionUrl(house)), {
66
42
  protocols: [SUBPROTOCOL],
67
43
  headers: { authorization: `Bearer ${credential}` },
68
44
  });
45
+ socket = opening;
69
46
  let opened = false;
70
47
  let silence = null;
71
48
  const closed = (event) => {
@@ -73,28 +50,31 @@ export function holdStream(handlers) {
73
50
  silence = null;
74
51
  if (socket === opening)
75
52
  socket = null;
76
- process.stderr.write(`kit: control stream closed ${event.code} ${event.reason}\n`);
53
+ if (connection === opening)
54
+ connection = null;
55
+ handlers.closed();
56
+ process.stderr.write(`kit: connection closed ${event.code} ${event.reason}\n`);
77
57
  if (event.reason === 'replaced')
78
58
  end(REPLACED);
79
59
  if (event.reason === 'credential_rejected' || refused?.status === 401) {
80
60
  void signInAgain(credential);
81
61
  return;
82
62
  }
83
- if (refused !== null && tooOld(refused.text))
84
- updateRequired();
63
+ if (refused !== null)
64
+ endUnserved(refused.text);
85
65
  if (!opened && refused !== null)
86
66
  process.stderr.write(`kit: ${refused.message}\n`);
87
- // A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused or left unanswered backs off on the jittered curve, retryable or not, since a resident outlives a House deploy.
67
+ // A closed connection reopens in one to three seconds, spread so Kit processes one restart closed return apart; an opening House refused or left unanswered backs off on the jittered curve, retryable or not, since a resident outlives a House deploy.
88
68
  setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
89
69
  };
90
70
  opening.onopen = () => {
91
71
  opened = true;
92
72
  failures = 0;
93
- socket = opening;
94
- silence = new StreamSilence(() => {
95
- if (socket !== opening)
73
+ connection = opening;
74
+ silence = new Silence(CONNECTION_SILENCE_MS, () => {
75
+ if (connection !== opening)
96
76
  return;
97
- socket = null;
77
+ connection = null;
98
78
  opening.onclose = null;
99
79
  opening.close();
100
80
  closed(new CloseEvent('close', { code: 1006, reason: 'heartbeat_missed' }));
@@ -104,19 +84,21 @@ export function holdStream(handlers) {
104
84
  };
105
85
  opening.onmessage = (event) => {
106
86
  silence?.heard();
107
- const frame = JSON.parse(String(event.data));
108
- if (frame.type !== 'beat')
109
- handlers.frame(frame);
87
+ const arrived = JSON.parse(String(event.data));
88
+ if (arrived.type === 'input')
89
+ handlers.call(arrived);
90
+ else if (arrived.type !== 'beat')
91
+ handlers.notification(arrived);
110
92
  };
111
93
  opening.onerror = () => undefined;
112
94
  opening.onclose = closed;
113
95
  };
114
96
  void connect();
115
97
  return {
116
- send(frame) {
117
- if (socket === null || socket.readyState !== WebSocket.OPEN)
98
+ send(notification) {
99
+ if (connection === null || connection.readyState !== WebSocket.OPEN)
118
100
  return false;
119
- socket.send(JSON.stringify(frame));
101
+ connection.send(JSON.stringify(notification));
120
102
  return true;
121
103
  },
122
104
  };