@agentshouse/kit 0.1.0-alpha.1 → 0.1.0-alpha.11

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/README.md CHANGED
@@ -10,7 +10,7 @@ On Linux `amd64` or `arm64` with a working Docker Engine, run the published,
10
10
  versioned bootstrap once:
11
11
 
12
12
  ```sh
13
- bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.1/connect-linux.sh)
13
+ bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.11/connect-linux.sh)
14
14
  ```
15
15
 
16
16
  The release asset pins one immutable multi-architecture image digest. It creates
@@ -23,9 +23,65 @@ another House and is retained in Kit home. A repeated invocation reuses the
23
23
  same container, credential and Environment; a different installed image is
24
24
  left for the explicit update journey.
25
25
 
26
+ The versioned `bootstrap.json` is the machine-readable command source for this
27
+ release. It is exported from the npm package as `@agentshouse/kit/bootstrap`
28
+ (JSON import) and published byte-for-byte beside `connect-linux.sh` in the
29
+ GitHub release. Its schema version is `1`, its `version` is the Kit package
30
+ version, and `hosts` contains only bootstrap scripts published in that release.
31
+ The package exports only the `./bootstrap` subpath; `./package.json` and source
32
+ subpaths are not exported. For this release, only `hosts.linux` exists. In this
33
+ schema example, `<version>` stands for the package's version:
34
+
35
+ ```json
36
+ {
37
+ "schemaVersion": 1,
38
+ "version": "<version>",
39
+ "hosts": {
40
+ "linux": {
41
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v<version>/connect-linux.sh)",
42
+ "asset": {
43
+ "name": "connect-linux.sh",
44
+ "url": "https://github.com/agentshouse/kit/releases/download/v<version>/connect-linux.sh"
45
+ },
46
+ "workspace": { "argument": "--workspace", "quoting": "posix" }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ `command` is complete for the default workspace. To show a selected absolute
53
+ workspace, append the declared `workspace.argument` and the path as one POSIX
54
+ shell-quoted argument. For example, `/home/user/My Agents' House` becomes
55
+ `--workspace '/home/user/My Agents'\'' House'`. Quote the entire path before
56
+ adding it to a copyable shell command; never interpolate it as shell syntax.
57
+ The bootstrap validates an absolute workspace root and encodes it as one Docker
58
+ `--mount` CSV source field, including commas and double quotes in the name.
59
+ The package manifest contains no image digest: the release workflow
60
+ fills the immutable image digest into the published script after the package
61
+ has been built.
62
+
26
63
  The bootstrap does not install native Kit on an ordinary computer. The native
27
64
  package remains for managed Nodes and an explicitly selected eligible Kit host.
28
65
 
66
+ The same bootstrap is the host's entry point for the existing `kit` commands:
67
+ append `kit` and the command to the connect command above, as in
68
+ `bash <(curl -fsSL …/connect-linux.sh) kit apply` or
69
+ `bash <(curl -fsSL …/connect-linux.sh) kit telegram connect`. It forwards the
70
+ command and its arguments unchanged into the installed container through the
71
+ host's Docker client and ends with the command's own output and exit status.
72
+ Standard input stays attached and a terminal stays a terminal, so a hidden
73
+ prompt such as the Telegram bot token is read inside the container and never
74
+ becomes an argument or printed output. With the output redirected, the
75
+ command's output and errors stay separate and the terminal stops echoing typed
76
+ input until the command ends. A running container runs the command beside its
77
+ resident Kit; a stopped one runs it in a one-off container over the same Kit
78
+ home and workspace and stays stopped. `kit login` takes the bootstrap's own
79
+ login route, a one-off container on the host network so the host browser can
80
+ return to its loopback listener, or `--manual` for the URL and one-time code.
81
+ Forwarding installs no host-native Kit and gives the container no Docker socket
82
+ or further mount; it refuses when no Kit is installed or the installed one
83
+ differs from this bootstrap's.
84
+
29
85
  ## Building the package
30
86
 
31
87
  The repository checkout is the development source. Build the package
@@ -48,7 +104,8 @@ Runtime dependencies stay minimal and exactly pinned in `package.json` and
48
104
  `package-lock.json`: House's own `@agentshouse/mdmodel`, `yaml`, which reads
49
105
  House's text answers, and the Agent Client Protocol SDK
50
106
  `@agentclientprotocol/sdk` with its peer `zod`, which is this Kit's one protocol
51
- client for the compiled adapter contracts. The SDK's approval
107
+ client for the compiled adapter contracts, and `ws` for the authenticated Kit
108
+ control stream. The SDK's approval
52
109
  receipt is [`docs/agents/acp-sdk-dependency-approval.json`](docs/agents/acp-sdk-dependency-approval.json),
53
110
  which the User wrote once this adoption's proof had passed. The adapter
54
111
  packages themselves are Environment prerequisites, not Kit dependencies, and
@@ -575,6 +632,17 @@ minimum's major - under
575
632
  `$HOUSE_KIT_HOME/agents/<kind>/releases/<release>/`, never touching what the
576
633
  owner installed globally, and an acquired route's command is that installed
577
634
  executable rather than a path anyone writes into `kit.json`.
635
+ The resident Kit also listens for an `agents` availability pointer on its
636
+ control stream. It reads the whole wanted list from House over HTTPS, removes
637
+ Kit-owned CLI directories that are no longer wanted, installs newly wanted
638
+ kinds, and reports its held state immediately, including while another Agent
639
+ run remains active. A newly wanted kind that needs provider authorization uses
640
+ the existing guided House login ceremony; Kit reports readiness again when it
641
+ ends. A reconnect reads the list again, so a pointer missed during a stream
642
+ outage changes no authority.
643
+ Removal waits for an active worker pass or any unresolved live WorkerSession
644
+ binding to stop using a CLI directory; the Kit keeps reporting that kind until
645
+ it removes the directory.
578
646
 
579
647
  Every candidate release is proved before it serves: the Kit installs it beside
580
648
  the current release, runs the contract's own conformance check - the protocol's
@@ -1062,10 +1130,18 @@ retained references still waiting to be saved.
1062
1130
  Every command that changes or reads installation state takes this
1063
1131
  installation's exclusive Environment control lease for the length of its run
1064
1132
  and renews it before it expires, so two Kit commands never work against the
1065
- same installation at once. A command that ends, or is interrupted once its work
1066
- has stopped, releases that exact lease so the next command acquires at once; a
1067
- refused command leaves it to expire. A `kit login` credential is presented only to take
1068
- that lease.
1133
+ same installation at once. A command that ends, is refused, or is interrupted
1134
+ once its work has stopped releases that exact lease so the next command acquires
1135
+ at once. A `kit login` credential is presented only to take that lease.
1136
+
1137
+ While `kit resident` holds that lease, a command runs through it instead: the
1138
+ command presents no credential, the resident confirms its current lease with
1139
+ House and sends the command's House requests under it, an interrupted command
1140
+ cancels its request there, and the lease stays held when the command ends. The
1141
+ resident serves one such command at a time and refuses another with
1142
+ `environment_control_lease_active`, so two commands still never work at once.
1143
+ A resident whose lease House refuses answers the command with that refusal; the
1144
+ command never acquires a lease of its own while a resident serves it.
1069
1145
 
1070
1146
  Interrupting the command cancels the running connector without advancing the
1071
1147
  checkpoint.
@@ -1111,8 +1187,11 @@ It selects prefixes over the User root, materializes them into a named local
1111
1187
  directory, enumerates whole content and removals after the recorded positions,
1112
1188
  and submits a resolved exact-base change set the caller prepared. Kit does no
1113
1189
  local reconciliation. The mode shares no Working Copy manifest, never infers a
1114
- removal from a locally absent file, and writes authored mutations only for the
1115
- two Libraries and `ROOM.md`.
1190
+ removal from a locally absent file, and writes authored mutations only within
1191
+ House's declared write scopes: a selected Room's Library and, where granted,
1192
+ `ROOM.md`. `/private` selects the Private Room by its Room reference, records an independent Room
1193
+ position, and keeps its `agents/` cards and Room-specific private `AGENTS.md`
1194
+ beside `library/**`.
1116
1195
 
1117
1196
  ```sh
1118
1197
  kit sync bootstrap ./replica \
@@ -1179,7 +1258,7 @@ for a batch that reaches it.
1179
1258
 
1180
1259
  ## Attachments
1181
1260
 
1182
- Upload one local file as an attachment to a Room or to your private tree. Name
1261
+ Upload one local file as an attachment to a Room or to your Private Room. Name
1183
1262
  the place explicitly. Kit sends the descriptor and the selected bytes; it does
1184
1263
  not scan a directory, convert a URL, or attach sibling files, credentials, or
1185
1264
  absolute paths.
@@ -1583,7 +1662,7 @@ prepared model definitions survive a later content-mount refusal or unmount.
1583
1662
 
1584
1663
  The atomic text mount includes a report with pending transfer rows. Kit then
1585
1664
  uploads each downloaded file once as an attachment to the mount's Room or
1586
- private tree, independently retrieves and compares the saved bytes, and only
1665
+ Private Room, independently retrieves and compares the saved bytes, and only
1587
1666
  after that proof succeeds rewrites every link to it, in every referring
1588
1667
  document, to its attachment reference, such as `at_1x.q`. The reference is an
1589
1668
  identity House resolves, not an HTTP download URL. A link's query and fragment
@@ -8,12 +8,41 @@ WORKSPACE="$HOME/AgentsHouse"
8
8
  MANUAL=0
9
9
  WORKSPACE_SELECTED=0
10
10
  HOUSE_SELECTED=0
11
+ FORWARDING=0
12
+ FORWARD=()
11
13
 
12
14
  refuse() {
13
15
  printf 'kit_bootstrap_refused: %s\n' "$1" >&2
14
16
  exit 1
15
17
  }
16
18
 
19
+ bind_mount() {
20
+ local source=${1//\"/\"\"}
21
+ printf 'type=bind,"src=%s",dst=%s' "$source" "$2"
22
+ }
23
+
24
+ login_kit() {
25
+ if [[ " $* " == *' --manual '* ]]; then
26
+ "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login "$@"
27
+ return
28
+ fi
29
+ command -v xdg-open >/dev/null || refuse 'no host browser opener; rerun with --manual'
30
+ "${DOCKER_RUN[@]}" login "$@" --host-browser | while IFS= read -r line; do
31
+ if [[ "$line" == 'Open '* ]]; then
32
+ printf 'Opening House Login in the host browser.\n'
33
+ xdg-open "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
34
+ else
35
+ printf '%s\n' "$line"
36
+ fi
37
+ done
38
+ }
39
+
40
+ if [[ "${1:-}" == kit ]]; then
41
+ FORWARDING=1
42
+ FORWARD=("${@:2}")
43
+ set --
44
+ fi
45
+
17
46
  while (($#)); do
18
47
  case "$1" in
19
48
  --workspace|--house)
@@ -53,6 +82,10 @@ case "$DOCKER_PLATFORM" in
53
82
  *) refuse "unsupported Docker Engine platform $DOCKER_PLATFORM" ;;
54
83
  esac
55
84
  [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match Docker Engine linux/$DOCKER_ARCH"
85
+ if ((FORWARDING)); then
86
+ docker container inspect "$NAME" >/dev/null 2>&1 || refuse 'House Kit is not installed; run this bootstrap without arguments first'
87
+ WORKSPACE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")
88
+ fi
56
89
 
57
90
  umask 077
58
91
  KIT_HOME="$HOME/.house-kit"
@@ -65,6 +98,8 @@ for path in "$KIT_HOME" "$WORKSPACE"; do
65
98
  done
66
99
  KIT_HOME=$(cd "$KIT_HOME" && pwd -P)
67
100
  WORKSPACE=$(cd "$WORKSPACE" && pwd -P)
101
+ KIT_HOME_MOUNT=$(bind_mount "$KIT_HOME" /kit-home)
102
+ WORKSPACE_MOUNT=$(bind_mount "$WORKSPACE" /agents/house)
68
103
  if [[ -f "$KIT_HOME/credential" || -f "$KIT_HOME/environment" ]] && [[ ! -f "$KIT_HOME/house" ]]; then
69
104
  refuse 'stored House origin is missing for an enrolled Environment'
70
105
  fi
@@ -76,7 +111,7 @@ if [[ -f "$KIT_HOME/house" ]]; then
76
111
  HOUSE="$BOUND_HOUSE"
77
112
  fi
78
113
  [[ "$HOUSE" == https://* || "$HOUSE" == http://127.0.0.1:* || "$HOUSE" == http://localhost:* ]] || refuse 'House origin must use HTTPS or local loopback HTTP'
79
- DOCKER_RUN=(docker run --rm --network host --user "$(id -u):$(id -g)" --mount "type=bind,src=$KIT_HOME,dst=/kit-home" --mount "type=bind,src=$WORKSPACE,dst=/agents/house" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
114
+ DOCKER_RUN=(docker run --rm --network host --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
80
115
 
81
116
  if docker container inspect "$NAME" >/dev/null 2>&1; then
82
117
  [[ "$(docker inspect --format '{{.Config.Image}}' "$NAME")" == "$IMAGE" ]] || refuse 'an installed Kit uses a different image; update it explicitly'
@@ -88,6 +123,25 @@ if docker container inspect "$NAME" >/dev/null 2>&1; then
88
123
  [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
89
124
  [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
90
125
  [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
126
+ if ((FORWARDING)); then
127
+ if [[ "${FORWARD[0]:-}" == login ]]; then
128
+ login_kit "${FORWARD[@]:1}"
129
+ exit 0
130
+ fi
131
+ TERMINAL=()
132
+ if [[ -t 0 && -t 1 ]]; then TERMINAL=(-t); fi
133
+ if [[ -t 0 && ! -t 1 ]]; then
134
+ ECHOED=$(stty -g)
135
+ trap 'stty "$ECHOED"' EXIT
136
+ stty -echo
137
+ fi
138
+ if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
139
+ docker exec -i "${TERMINAL[@]}" "$NAME" kit "${FORWARD[@]}"
140
+ else
141
+ "${DOCKER_RUN[@]:0:2}" -i "${TERMINAL[@]}" "${DOCKER_RUN[@]:2}" "${FORWARD[@]}"
142
+ fi
143
+ exit
144
+ fi
91
145
  "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
92
146
  if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
93
147
  printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
@@ -108,20 +162,12 @@ if [[ -f "$KIT_HOME/credential" ]]; then
108
162
  "${DOCKER_RUN[@]}" credential check --house "$HOUSE"
109
163
  else
110
164
  if ((MANUAL)); then
111
- "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login --house "$HOUSE" --manual
165
+ login_kit --house "$HOUSE" --manual
112
166
  else
113
- command -v xdg-open >/dev/null || refuse 'no host browser opener; rerun with --manual'
114
- "${DOCKER_RUN[@]}" login --house "$HOUSE" --host-browser | while IFS= read -r line; do
115
- if [[ "$line" == 'Open '* ]]; then
116
- printf 'Opening House Login in the host browser.\n'
117
- xdg-open "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
118
- else
119
- printf '%s\n' "$line"
120
- fi
121
- done
167
+ login_kit --house "$HOUSE"
122
168
  fi
123
169
  fi
124
170
 
125
171
  "${DOCKER_RUN[@]}" apply --house "$HOUSE"
126
- docker run -d --name "$NAME" --restart unless-stopped --user "$(id -u):$(id -g)" --mount "type=bind,src=$KIT_HOME,dst=/kit-home" --mount "type=bind,src=$WORKSPACE,dst=/agents/house" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident --house "$HOUSE" >/dev/null
172
+ docker run -d --name "$NAME" --restart unless-stopped --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident --house "$HOUSE" >/dev/null
127
173
  printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
package/bin/kit.js CHANGED
@@ -140,7 +140,7 @@ async function runCredentialCheck(args, directory) {
140
140
  const house = await selectedHouse(values, directory);
141
141
  let session;
142
142
  try {
143
- session = await openSession({ house, directory });
143
+ session = await openSession({ house, directory, acquire: true });
144
144
  } catch (error) {
145
145
  if (error instanceof KitRefusal && error.code === "environment_control_lease_active") {
146
146
  write("Stored Kit authority is accepted; its Environment already has an active control lease.");
package/bootstrap.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "0.1.0-alpha.11",
4
+ "hosts": {
5
+ "linux": {
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.11/connect-linux.sh)",
7
+ "asset": {
8
+ "name": "connect-linux.sh",
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.11/connect-linux.sh"
10
+ },
11
+ "workspace": {
12
+ "argument": "--workspace",
13
+ "quoting": "posix"
14
+ }
15
+ }
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.11",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,9 +11,13 @@
11
11
  "bin": {
12
12
  "kit": "bin/kit.js"
13
13
  },
14
+ "exports": {
15
+ "./bootstrap": "./bootstrap.json"
16
+ },
14
17
  "dependencies": {
15
18
  "@agentclientprotocol/sdk": "1.5.0",
16
- "@agentshouse/mdmodel": "0.1.0-alpha.12",
19
+ "@agentshouse/mdmodel": "0.1.0-alpha.13",
20
+ "ws": "8.22.0",
17
21
  "yaml": "2.9.1",
18
22
  "zod": "4.6.5"
19
23
  },
package/src/agents.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { readFile, rm } from "node:fs/promises";
2
+ import { readFile, readdir, rm } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import {
@@ -11,6 +11,7 @@ import {
11
11
  } from "./adapter.js";
12
12
  import { inheritedEnvironment } from "./command.js";
13
13
  import {
14
+ ADAPTER_KINDS,
14
15
  AGENT_HOME,
15
16
  AGENT_INSTALL_TIMEOUT_MS,
16
17
  AGENT_RELEASES_HOME,
@@ -261,6 +262,60 @@ export async function updateDeclaredAgents(options, configuration, dependencies
261
262
  return updated;
262
263
  }
263
264
 
265
+ export async function removeUndeclaredAgents(directory, wanted, keep = false) {
266
+ const home = join(directory, AGENT_HOME);
267
+ let entries;
268
+ try {
269
+ entries = await readdir(home, { withFileTypes: true });
270
+ } catch (error) {
271
+ if (error.code === "ENOENT") return [];
272
+ throw error;
273
+ }
274
+ const retained = [];
275
+ for (const entry of entries) {
276
+ if (!wanted.includes(entry.name)) {
277
+ if (keep && ADAPTER_KINDS.includes(entry.name)) retained.push(entry.name);
278
+ else await rm(join(home, entry.name), { recursive: true, force: true });
279
+ }
280
+ }
281
+ return retained;
282
+ }
283
+
284
+ export async function reconcileAgentKinds(
285
+ options,
286
+ session,
287
+ configuration,
288
+ dependencies = {},
289
+ signal = session.signal,
290
+ state = {},
291
+ ) {
292
+ const selected = { ...configuration, agents: await desiredAgents(session, signal) };
293
+ state.onDesired?.(selected.agents);
294
+ const newlyWanted = selected.agents.filter((kind) => !state.lastDesired?.includes(kind));
295
+ const retained = await removeUndeclaredAgents(options.directory, selected.agents, state.busy);
296
+ const pointerKinds = state.busy
297
+ ? (
298
+ await Promise.all(
299
+ newlyWanted.map(async (kind) =>
300
+ (await readAgentState(options.directory, kind)).release === null ? kind : null,
301
+ ),
302
+ )
303
+ ).filter((kind) => kind !== null)
304
+ : newlyWanted;
305
+ const updated = await updateDeclaredAgents(
306
+ { directory: options.directory, at: dependencies.now?.(), forced: state.forced },
307
+ { ...selected, agents: state.pointer ? pointerKinds : selected.agents },
308
+ dependencies,
309
+ );
310
+ const readiness = await agentReadiness(
311
+ { directory: options.directory },
312
+ { ...selected, agents: [...selected.agents, ...retained] },
313
+ dependencies,
314
+ );
315
+ await reportAgentCapabilities(session, readiness.agents, signal);
316
+ return { selected, updated, reported: readiness.agents, refused: readiness.refused, retained };
317
+ }
318
+
264
319
  export async function agentSignedIn(directory, kind, dependencies = {}) {
265
320
  const contract = compiledContract(kind);
266
321
  const state = await readAgentState(directory, kind);
@@ -341,15 +396,15 @@ export async function updateAgents(options, dependencies = {}) {
341
396
  return maintainControl(
342
397
  session,
343
398
  async (signal) => {
344
- const selected = { ...configuration, agents: await desiredAgents(session, signal) };
345
- const updated = await updateDeclaredAgents(
346
- { directory: options.directory, at: dependencies.now?.(), forced: true },
347
- selected,
399
+ const result = await reconcileAgentKinds(
400
+ options,
401
+ session,
402
+ configuration,
348
403
  dependencies,
404
+ signal,
405
+ { forced: true },
349
406
  );
350
- const { agents } = await agentReadiness(options, selected, dependencies);
351
- await reportAgentCapabilities(session, agents, signal);
352
- return { updated, reported: agents };
407
+ return { updated: result.updated, reported: result.reported };
353
408
  },
354
409
  options.signal,
355
410
  );
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 = "2026-09-27-wanted-agent-clis";
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;
@@ -195,7 +195,7 @@ export const ADAPTER_CONTRACTS = Object.freeze({
195
195
  guidedLogin: CLAUDE_PASTED_CODE,
196
196
  identity: Object.freeze({ agentInfo: "@agentclientprotocol/claude-agent-acp" }),
197
197
  nativeQuestion: null,
198
- sessionLoad: false,
198
+ sessionLoad: true,
199
199
  sessionConfig: Object.freeze({ model: "model", effort: "effort" }),
200
200
  capabilities: Object.freeze({
201
201
  field_kinds: ELICITATION_FIELD_KINDS,
@@ -288,7 +288,7 @@ export const KIT_DOOR_DISCOVER_PATH = "/kit/door/discover";
288
288
  export const KIT_DOOR_BOOTSTRAP_PATH = "/kit/door/bootstrap";
289
289
  export const KIT_DOOR_ENUMERATE_PATH = "/kit/door/enumerate";
290
290
  export const KIT_DOOR_BATCH_PATH = "/kit/door/batch";
291
- export const EXPLICIT_SYNC_VERSION = "explicit-sync/1";
291
+ export const EXPLICIT_SYNC_VERSION = "explicit-sync/2";
292
292
  export const KIT_DOOR_UNMOUNT_PATH = "/kit/door/unmount";
293
293
 
294
294
  export const MIGRATOR_CONTRACT = "kit-migrator/1";
package/src/host.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFile, statfs } from "node:fs/promises";
2
2
  import { readConfiguration } from "./configuration.js";
3
3
  import { HOST_CPU_SAMPLE_MS } from "./contract.js";
4
- import { openSession, reportHostCapacity } from "./house.js";
4
+ import { maintainControl, openSession, reportHostCapacity } from "./house.js";
5
5
  import { KitRefusal } from "./refusal.js";
6
6
 
7
7
  const PROC_STAT = "/proc/stat";
@@ -116,6 +116,10 @@ export async function reportHost(options, dependencies = {}) {
116
116
  ? observed
117
117
  : { ...observed, maxima: declaredMaxima(configuration.host.maxima) };
118
118
  const session = await openSession(options, dependencies);
119
- await reportHostCapacity(session, declaration);
119
+ await maintainControl(
120
+ session,
121
+ (signal) => reportHostCapacity(session, declaration, signal),
122
+ options.signal,
123
+ );
120
124
  return declaration;
121
125
  }