@agentshouse/kit 0.1.0-alpha.12 → 0.1.0-alpha.13
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 +14 -7
- package/bin/kit.js +3 -1
- package/bootstrap.json +3 -3
- package/package.json +1 -1
- package/src/house.js +17 -7
- package/src/telegram.js +5 -3
- package/src/worker.js +10 -3
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.
|
|
13
|
+
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.13/connect-linux.sh)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
The release asset pins one immutable multi-architecture image digest. It creates
|
|
@@ -27,16 +27,18 @@ installed container runs another image, the bootstrap pulls this release's
|
|
|
27
27
|
image and asks the running resident Kit to drain with `kit resident drain`. The
|
|
28
28
|
resident stops acquiring Runs, resumptions, setup and agent CLI work, while the
|
|
29
29
|
Sessions it supervises keep their renewal, owner stops, answers and reports.
|
|
30
|
-
While any Agent Session is still running or unreported,
|
|
31
|
-
|
|
30
|
+
While any Agent Session is still running or unreported, including one whose Run
|
|
31
|
+
was being acquired when the drain began, the bootstrap names it and leaves the
|
|
32
|
+
container as it is; rerun it once they end. Once nothing remains,
|
|
32
33
|
the resident stops renewing, releases its exact lease and stays quiescent, and
|
|
33
34
|
the bootstrap replaces the container with one on this release's image over the
|
|
34
35
|
same Kit home and workspace. The credential, Environment, Kit-home state and
|
|
35
36
|
everything under the workspace are kept; anything installed elsewhere in the old
|
|
36
37
|
container is not. A stopped container is replaced without a drain.
|
|
37
38
|
|
|
38
|
-
If House refuses the installed Kit with `kit_upgrade_required`,
|
|
39
|
-
|
|
39
|
+
If House refuses the installed Kit with `kit_upgrade_required`, including in
|
|
40
|
+
its answer to a command the resident serves, the resident calls House no more,
|
|
41
|
+
neither to release nor to report. It keeps supervising
|
|
40
42
|
every running Session locally and never stops one to make progress. A Session
|
|
41
43
|
that ends keeps its exact binding, stop observation and outcome in Kit home,
|
|
42
44
|
and the drain answers ready once no supervised process runs. The updated Kit
|
|
@@ -1170,8 +1172,13 @@ A resident whose lease House refuses answers the command with that refusal; the
|
|
|
1170
1172
|
command never acquires a lease of its own while a resident serves it. While an
|
|
1171
1173
|
update drains, `state/update-drain` refuses every such command, `kit login` and
|
|
1172
1174
|
`kit worker run` with `kit_update_draining` before it enrolls, acquires or
|
|
1173
|
-
delegates anything
|
|
1174
|
-
|
|
1175
|
+
delegates anything. A command already served keeps the resident's lease until
|
|
1176
|
+
it ends, while the resident refuses its acquisition of new work and its
|
|
1177
|
+
Telegram updates with `kit_update_draining`, which ends a served
|
|
1178
|
+
`kit telegram serve` once each of its activities reports the deliveries and
|
|
1179
|
+
files it already acquired; until it ends, the drain answers that it waits for
|
|
1180
|
+
that command and the bootstrap leaves the container as it is. The bootstrap
|
|
1181
|
+
removes the file when it replaces the container.
|
|
1175
1182
|
|
|
1176
1183
|
Interrupting the command cancels the running connector without advancing the
|
|
1177
1184
|
checkpoint.
|
package/bin/kit.js
CHANGED
|
@@ -177,7 +177,9 @@ async function runResidentDrain(args, directory) {
|
|
|
177
177
|
write(
|
|
178
178
|
answer.sessions.length > 0
|
|
179
179
|
? `House Kit is draining for its update and waits for Agent Sessions ${answer.sessions.join(", ")}.`
|
|
180
|
-
:
|
|
180
|
+
: answer.serving === true
|
|
181
|
+
? "House Kit is draining for its update and waits for the Kit command it serves."
|
|
182
|
+
: `House Kit is draining for its update; House refused its lease release (${answer.refused}).`,
|
|
181
183
|
);
|
|
182
184
|
process.exitCode = 1;
|
|
183
185
|
}
|
package/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.13/connect-linux.sh)",
|
|
7
7
|
"asset": {
|
|
8
8
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.13/connect-linux.sh"
|
|
10
10
|
},
|
|
11
11
|
"workspace": {
|
|
12
12
|
"argument": "--workspace",
|
package/package.json
CHANGED
package/src/house.js
CHANGED
|
@@ -56,6 +56,10 @@ async function readCredential(directory) {
|
|
|
56
56
|
return stored;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function latchRefusal(answer, dependencies) {
|
|
60
|
+
if (answer?.error?.code === UPGRADE_REQUIRED) dependencies.contractRefused?.();
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
async function houseAnswer(house, path, init, dependencies, signal) {
|
|
60
64
|
const fetcher = dependencies.fetch ?? fetch;
|
|
61
65
|
let response;
|
|
@@ -73,7 +77,7 @@ async function houseAnswer(house, path, init, dependencies, signal) {
|
|
|
73
77
|
} catch {
|
|
74
78
|
answer = null;
|
|
75
79
|
}
|
|
76
|
-
|
|
80
|
+
latchRefusal(answer, dependencies);
|
|
77
81
|
throwHouseAnswer(response.status, answer, path);
|
|
78
82
|
if (answer === null) throw new KitRefusal("kit_house_unavailable", path);
|
|
79
83
|
return answer;
|
|
@@ -130,11 +134,18 @@ function residentAnswer(status, code) {
|
|
|
130
134
|
return { status, type: "application/json", body };
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
function newWork(path) {
|
|
138
|
+
return path === "/kit/telegram/updates" || (path.endsWith("/acquire") && path !== "/kit/commands/acquire");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function residentRelay(session, request, signal, draining) {
|
|
134
142
|
const path = typeof request.path === "string" ? request.path : "";
|
|
135
143
|
if (!path.startsWith("/kit/") || path.startsWith("/kit/lease")) {
|
|
136
144
|
return residentAnswer(403, "kit_resident_route_refused");
|
|
137
145
|
}
|
|
146
|
+
if (draining && newWork(path)) {
|
|
147
|
+
return residentAnswer(409, UPDATE_DRAINING);
|
|
148
|
+
}
|
|
138
149
|
let authorization;
|
|
139
150
|
try {
|
|
140
151
|
authorization = await heldControl(session, signal);
|
|
@@ -149,6 +160,7 @@ async function residentRelay(session, request, signal) {
|
|
|
149
160
|
signal,
|
|
150
161
|
});
|
|
151
162
|
const body = Buffer.from(await response.arrayBuffer());
|
|
163
|
+
if (response.status === 409) latchRefusal(JSON.parse(body.toString("utf8")), session.dependencies);
|
|
152
164
|
return {
|
|
153
165
|
status: response.status,
|
|
154
166
|
type: response.headers.get("content-type"),
|
|
@@ -168,9 +180,7 @@ export async function serveResident(session, directory, signal, drain) {
|
|
|
168
180
|
let draining = false;
|
|
169
181
|
function answerDrain(connection) {
|
|
170
182
|
draining = true;
|
|
171
|
-
|
|
172
|
-
holder?.destroy();
|
|
173
|
-
answered.then(
|
|
183
|
+
drain(holder !== null).then(
|
|
174
184
|
(answer) => {
|
|
175
185
|
if (!connection.destroyed) writeFrame(connection, answer);
|
|
176
186
|
connection.end();
|
|
@@ -181,7 +191,7 @@ export async function serveResident(session, directory, signal, drain) {
|
|
|
181
191
|
}
|
|
182
192
|
function hold(connection) {
|
|
183
193
|
if (holder !== null || draining || existsSync(updateDrain(directory))) {
|
|
184
|
-
writeFrame(connection, { refused: holder === null ? UPDATE_DRAINING : LEASE_ACTIVE });
|
|
194
|
+
writeFrame(connection, { refused: draining || holder === null ? UPDATE_DRAINING : LEASE_ACTIVE });
|
|
185
195
|
connection.end();
|
|
186
196
|
return false;
|
|
187
197
|
}
|
|
@@ -218,7 +228,7 @@ export async function serveResident(session, directory, signal, drain) {
|
|
|
218
228
|
}
|
|
219
229
|
const cancelled = new AbortController();
|
|
220
230
|
relayed.set(request.id, cancelled);
|
|
221
|
-
void residentRelay(session, request, AbortSignal.any([signal, cancelled.signal])).then(
|
|
231
|
+
void residentRelay(session, request, AbortSignal.any([signal, cancelled.signal]), draining).then(
|
|
222
232
|
(answer) => {
|
|
223
233
|
relayed.delete(request.id);
|
|
224
234
|
if (!connection.destroyed) writeFrame(connection, { id: request.id, ...answer });
|
package/src/telegram.js
CHANGED
|
@@ -863,20 +863,21 @@ async function onePass(gateway, activity, work) {
|
|
|
863
863
|
try {
|
|
864
864
|
await work(gateway);
|
|
865
865
|
} catch (error) {
|
|
866
|
-
if (cancelled(error) || !(error instanceof KitRefusal)) throw error;
|
|
866
|
+
if (cancelled(error) || !(error instanceof KitRefusal) || error.code === "kit_update_draining") throw error;
|
|
867
867
|
gateway.summary.failures.push({ activity, item: null, code: error.code });
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
870
|
|
|
871
871
|
async function paceActivity(gateway, stopping, activity, work) {
|
|
872
872
|
try {
|
|
873
|
-
for (let index = 0; index < gateway.passes; index += 1) {
|
|
873
|
+
for (let index = 0; index < gateway.passes && !gateway.draining; index += 1) {
|
|
874
874
|
if (gateway.signal?.aborted === true) throw new KitRefusal("kit_command_cancelled");
|
|
875
875
|
await onePass(gateway, activity, work);
|
|
876
876
|
if (index + 1 < gateway.passes) await gateway.wait(TELEGRAM_GATEWAY_POLL_MS, gateway.signal);
|
|
877
877
|
}
|
|
878
878
|
} catch (error) {
|
|
879
|
-
|
|
879
|
+
if (error.code === "kit_update_draining") gateway.draining = true;
|
|
880
|
+
else stopping.abort();
|
|
880
881
|
throw error;
|
|
881
882
|
}
|
|
882
883
|
}
|
|
@@ -964,6 +965,7 @@ function gatewayContext(session, options, signal, summary = emptySummary()) {
|
|
|
964
965
|
gate: rateGate(() => session.now().getTime()),
|
|
965
966
|
wait: session.dependencies.wait ?? pause,
|
|
966
967
|
passes: options.passes ?? Number.POSITIVE_INFINITY,
|
|
968
|
+
draining: false,
|
|
967
969
|
summary,
|
|
968
970
|
};
|
|
969
971
|
}
|
package/src/worker.js
CHANGED
|
@@ -177,6 +177,7 @@ async function supervise(context, bound, bridge) {
|
|
|
177
177
|
while (live) {
|
|
178
178
|
await Promise.race([terminal, delay(WORKER_SUPERVISION_POLL_MS)]);
|
|
179
179
|
if (!live) return null;
|
|
180
|
+
await context.update?.announce();
|
|
180
181
|
if (Date.now() >= commanded) {
|
|
181
182
|
commanded = Date.now() + WORKER_COMMAND_POLL_MS;
|
|
182
183
|
const polled = await pendingCommands(context, ["stop", "interrupt", "answer"]);
|
|
@@ -849,20 +850,26 @@ function residentUpdate(context, wake) {
|
|
|
849
850
|
get requested() {
|
|
850
851
|
return requested;
|
|
851
852
|
},
|
|
852
|
-
async request() {
|
|
853
|
+
async request(serving) {
|
|
853
854
|
if (!requested) {
|
|
854
855
|
requested = true;
|
|
855
856
|
await writeText(updateDrain(context.directory), "");
|
|
856
857
|
}
|
|
857
858
|
wake.notify();
|
|
858
|
-
if (ready !== null) return ready;
|
|
859
859
|
const sessions = await heldSessions(context);
|
|
860
|
+
if (ready !== null) return ready;
|
|
861
|
+
if (serving) return { ready: false, sessions, serving };
|
|
860
862
|
if (sessions.length > 0) return { ready: false, sessions };
|
|
861
863
|
return new Promise((resolve) => waiting.push(resolve));
|
|
862
864
|
},
|
|
863
865
|
answer(answer) {
|
|
864
866
|
for (const resolve of waiting.splice(0)) resolve(answer);
|
|
865
867
|
},
|
|
868
|
+
async announce() {
|
|
869
|
+
if (waiting.length === 0) return;
|
|
870
|
+
const sessions = await heldSessions(context);
|
|
871
|
+
if (sessions.length > 0) this.answer({ ready: false, sessions });
|
|
872
|
+
},
|
|
866
873
|
quiesce() {
|
|
867
874
|
ready = { ready: true, sessions: [] };
|
|
868
875
|
this.answer(ready);
|
|
@@ -902,7 +909,7 @@ export async function runResident(options, dependencies = {}) {
|
|
|
902
909
|
const wake = residentWake();
|
|
903
910
|
const update = residentUpdate(context, wake);
|
|
904
911
|
context.update = update;
|
|
905
|
-
const served = await serveResident(session, context.directory, options.signal, () => update.request());
|
|
912
|
+
const served = await serveResident(session, context.directory, options.signal, (serving) => update.request(serving));
|
|
906
913
|
const serve = async (signal) => {
|
|
907
914
|
context.busy = true;
|
|
908
915
|
const watcher = new AbortController();
|