@agentshouse/kit 0.2.1-c2c600f2d2b30 → 0.2.1-c30728be20485

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
1
2
  import { end, SIGN_IN_AGAIN } from './end.js';
2
3
  import { readEnrolment } from './home.js';
4
+ import { retryable } from './refusals.js';
3
5
  import { relayed } from './relay.js';
4
6
  export class HouseRefusal extends Error {
5
7
  status;
@@ -10,10 +12,26 @@ export class HouseRefusal extends Error {
10
12
  this.text = text;
11
13
  }
12
14
  }
15
+ export function finalRefusal(error) {
16
+ return error instanceof HouseRefusal && retryable(error.text) === false;
17
+ }
13
18
  export function retryDelay(attempt) {
14
19
  // Full jitter, doubling from one second to a 30-second cap, spreads the Kits one outage dropped and retries within half a minute of House's return.
15
20
  return Math.random() * Math.min(30_000, 1000 * 2 ** attempt);
16
21
  }
22
+ export async function retrying(sent, signal) {
23
+ for (let attempt = 0;; attempt++) {
24
+ try {
25
+ return await sent();
26
+ }
27
+ catch (error) {
28
+ if (!(error instanceof HouseRefusal) || retryable(error.text) !== true)
29
+ throw error;
30
+ }
31
+ // A request House refused as retryable is sent again on the jittered curve.
32
+ await delay(retryDelay(attempt), undefined, { signal });
33
+ }
34
+ }
17
35
  export async function post(house, path, body, credential, signal) {
18
36
  const answer = await fetch(relayed(new URL(path, house)), {
19
37
  method: 'POST',
@@ -48,7 +66,7 @@ export const house = {
48
66
  return await enrolledPost(path, body);
49
67
  }
50
68
  catch (error) {
51
- if (error instanceof HouseRefusal && error.status < 500)
69
+ if (finalRefusal(error))
52
70
  throw error;
53
71
  process.stderr.write(`kit: ${path} did not reach House: ${error.message}\n`);
54
72
  // A delivery House did not take is sent again on the jittered curve for as long as it is still current.
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "version": "0.2.1-c2c600f2d2b30",
3
+ "version": "0.2.1-c30728be20485",
4
4
  "hosts": {
5
5
  "macos": {
6
6
  "asset": {
7
7
  "name": "connect-linux.sh",
8
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c2c600f2d2b30/connect-linux.sh"
8
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c30728be20485/connect-linux.sh"
9
9
  }
10
10
  },
11
11
  "linux": {
12
12
  "asset": {
13
13
  "name": "connect-linux.sh",
14
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c2c600f2d2b30/connect-linux.sh"
14
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c30728be20485/connect-linux.sh"
15
15
  }
16
16
  },
17
17
  "windows": {
18
18
  "asset": {
19
19
  "name": "connect-windows.ps1",
20
- "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c2c600f2d2b30/connect-windows.ps1"
20
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-c30728be20485/connect-windows.ps1"
21
21
  }
22
22
  }
23
23
  }
package/dist/bridge.js CHANGED
@@ -6,13 +6,13 @@ import { createServer, request as httpRequest } from 'node:http';
6
6
  import { request as httpsRequest } from 'node:https';
7
7
  import { delimiter, dirname, join } from 'node:path';
8
8
  import { parse } from 'yaml';
9
- import { HouseRefusal, retryDelay } from './api.js';
9
+ import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
10
10
  import { loginPath } from './clis.js';
11
11
  import { changedPaths, } from './copies.js';
12
12
  import { kitHome, readEnrolment } from './home.js';
13
13
  import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
14
14
  import { operationId, uuidOf } from './operation.js';
15
- import { refusalOf } from './refusals.js';
15
+ import { refusalOf, retryable } from './refusals.js';
16
16
  import { relayed } from './relay.js';
17
17
  const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
18
18
  const ATTACHMENTS = '/kit/attachments/upload';
@@ -40,8 +40,6 @@ function refusalIn(text, status) {
40
40
  return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
41
41
  }
42
42
  function answered(forwarded) {
43
- if (forwarded.status >= 500)
44
- throw new HouseRefusal('/', forwarded.status, forwarded.text);
45
43
  if (forwarded.status !== 200)
46
44
  return { refused: refusalIn(forwarded.text, forwarded.status) };
47
45
  const answer = JSON.parse(forwarded.text);
@@ -65,8 +63,8 @@ function pendingSeconds(forwarded) {
65
63
  function resendAfter(forwarded, attempt) {
66
64
  if (forwarded === null)
67
65
  return retryDelay(attempt);
68
- if (forwarded.status >= 500)
69
- return refusalOf(forwarded.text) === null ? retryDelay(attempt) : null;
66
+ if (forwarded.status !== 200)
67
+ return retryable(forwarded.text) === false ? null : retryDelay(attempt);
70
68
  const seconds = pendingSeconds(forwarded);
71
69
  return seconds === null ? null : seconds * 1000;
72
70
  }
@@ -120,17 +118,26 @@ export async function openBridge(house, copies, conversationId, signal) {
120
118
  if ('refused' in prepared)
121
119
  return prepared;
122
120
  const { transfer } = parse(prepared.text);
123
- const posted = await fetch(relayed(transfer.url), {
124
- method: 'POST',
125
- headers: { 'content-type': 'text/plain; charset=utf-8' },
126
- body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
127
- signal: closed.signal,
128
- });
129
- const text = await posted.text();
130
- if (posted.status >= 500)
131
- throw new HouseRefusal('/', posted.status, text);
132
- if (!posted.ok)
133
- return { refused: refusalIn(text, posted.status) };
121
+ let text;
122
+ try {
123
+ text = await retrying(async () => {
124
+ const posted = await fetch(relayed(transfer.url), {
125
+ method: 'POST',
126
+ headers: { 'content-type': 'text/plain; charset=utf-8' },
127
+ body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
128
+ signal: closed.signal,
129
+ });
130
+ const answer = await posted.text();
131
+ if (!posted.ok)
132
+ throw new HouseRefusal('/', posted.status, answer);
133
+ return answer;
134
+ }, closed.signal);
135
+ }
136
+ catch (error) {
137
+ if (!finalRefusal(error))
138
+ throw error;
139
+ return { refused: refusalIn(error.text, error.status) };
140
+ }
134
141
  const held = JSON.parse(text).transfer;
135
142
  return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
136
143
  };
package/dist/copies.js CHANGED
@@ -2,7 +2,7 @@ import { parse } from '@agentshouse/mdmodel';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
- import { HouseRefusal } from './api.js';
5
+ import { finalRefusal, HouseRefusal, retrying } from './api.js';
6
6
  import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
7
7
  import { kitHome } from './home.js';
8
8
  import { operationId } from './operation.js';
@@ -160,11 +160,11 @@ export class LocalCopies {
160
160
  key: 'owner',
161
161
  submit: async (operation, changes) => {
162
162
  try {
163
- const outcome = await house.post('/kit/door/batch', { operation_id: operation, changes });
163
+ const outcome = await retrying(() => house.post('/kit/door/batch', { operation_id: operation, changes }));
164
164
  return { accepted: outcome.contents };
165
165
  }
166
166
  catch (error) {
167
- if (!(error instanceof HouseRefusal) || error.status >= 500)
167
+ if (!finalRefusal(error))
168
168
  throw error;
169
169
  return {
170
170
  refused: refusalOf(error.text) ?? { code: '', text: `House refused it with ${error.status}`, conflicts: [] },
package/dist/refusals.js CHANGED
@@ -21,6 +21,18 @@ function spoken(text) {
21
21
  const detail = said.indexOf('\n');
22
22
  return { code: head[1], text: said, conflicts: detail === -1 ? [] : conflictCodes(said.slice(detail + 1)) };
23
23
  }
24
+ export function retryable(text) {
25
+ let body;
26
+ try {
27
+ body = JSON.parse(text);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ const error = body?.error;
33
+ const verdict = error?.retryable ?? error?.data?.retryable;
34
+ return typeof verdict === 'boolean' ? verdict : null;
35
+ }
24
36
  export function refusalOf(text) {
25
37
  let body;
26
38
  try {
@@ -1,4 +1,4 @@
1
- import { HouseRefusal, retryDelay } from './api.js';
1
+ import { finalRefusal, retryDelay } from './api.js';
2
2
  export async function holdSecretInput(house, subject, steps, waiting) {
3
3
  for (let failures = 0; waiting();) {
4
4
  try {
@@ -10,7 +10,7 @@ export async function holdSecretInput(house, subject, steps, waiting) {
10
10
  failures = 0;
11
11
  }
12
12
  catch (error) {
13
- if (error instanceof HouseRefusal && error.status < 500)
13
+ if (finalRefusal(error))
14
14
  return null;
15
15
  process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
16
16
  // A wait that did not reach House is held again on the jittered curve for as long as its asker still waits.
package/dist/stream.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { subscribe } from 'node:diagnostics_channel';
2
- import { HouseRefusal, retryDelay } from './api.js';
2
+ import { finalRefusal, HouseRefusal, retryDelay } from './api.js';
3
3
  import { end, REPLACED, SIGN_IN_AGAIN } from './end.js';
4
4
  import { readEnrolment } from './home.js';
5
5
  import { relayed } from './relay.js';
@@ -54,9 +54,9 @@ export function holdStream(handlers) {
54
54
  end(REPLACED);
55
55
  if (event.reason === 'credential_rejected' || refused?.status === 401)
56
56
  end(SIGN_IN_AGAIN);
57
- if (refused !== null && refused.status >= 400 && refused.status < 500)
57
+ if (finalRefusal(refused))
58
58
  end(refused.message);
59
- // A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; a failed opening backs off on the jittered curve.
59
+ // A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused as retryable or left unanswered backs off on the jittered curve.
60
60
  setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
61
61
  };
62
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.2.1-c2c600f2d2b30",
3
+ "version": "0.2.1-c30728be20485",
4
4
  "description": "House Kit connects an Environment to agents.house.",
5
5
  "license": "MIT",
6
6
  "repository": {