@agentshouse/kit 0.2.1-c70284f7a7466 → 0.2.1-c7a397cb0cce7
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/acp.js +34 -14
- package/dist/agents.js +34 -14
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +1 -1
- package/dist/conversations.js +195 -79
- package/dist/copies.js +38 -22
- package/dist/files.js +1 -1
- package/dist/inputs.js +63 -0
- package/dist/resident.js +9 -5
- package/dist/scope.js +3 -0
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/stream.js +4 -4
- package/dist/translate.js +2 -8
- package/dist/update.js +20 -23
- package/dist/wire.js +1 -1
- package/package.json +2 -2
package/dist/files.js
CHANGED
|
@@ -11,7 +11,7 @@ export function verified(bytes, sha256, name) {
|
|
|
11
11
|
export async function placeFiles(house, directory, files, signal) {
|
|
12
12
|
const paths = [];
|
|
13
13
|
for (const file of files) {
|
|
14
|
-
const original = await house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal);
|
|
14
|
+
const original = await retrying(() => house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal), signal);
|
|
15
15
|
const answer = await fetch(relayed(original.download.url), { signal });
|
|
16
16
|
if (!answer.ok)
|
|
17
17
|
throw new Error(`${file.name} did not download: ${answer.status}`);
|
package/dist/inputs.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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 Inputs {
|
|
8
|
+
house;
|
|
9
|
+
changed;
|
|
10
|
+
carried = new Map();
|
|
11
|
+
path = join(kitHome(), 'handed-inputs.json');
|
|
12
|
+
handed;
|
|
13
|
+
answered = new Set();
|
|
14
|
+
open = 0;
|
|
15
|
+
constructor(house, changed) {
|
|
16
|
+
this.house = house;
|
|
17
|
+
this.changed = changed;
|
|
18
|
+
this.handed = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
|
|
19
|
+
}
|
|
20
|
+
hands(inputId, ack) {
|
|
21
|
+
if (this.answered.has(inputId))
|
|
22
|
+
return;
|
|
23
|
+
this.handed.set(inputId, ack);
|
|
24
|
+
this.save();
|
|
25
|
+
}
|
|
26
|
+
idle() {
|
|
27
|
+
return this.open === 0;
|
|
28
|
+
}
|
|
29
|
+
carry(inputId, work) {
|
|
30
|
+
const known = this.carried.get(inputId);
|
|
31
|
+
const handed = this.handed.get(inputId);
|
|
32
|
+
const carried = known ?? (handed === undefined ? work() : Promise.resolve({ ack: handed }));
|
|
33
|
+
if (known === undefined) {
|
|
34
|
+
this.carried.set(inputId, carried);
|
|
35
|
+
carried.catch(() => this.carried.delete(inputId));
|
|
36
|
+
}
|
|
37
|
+
this.open++;
|
|
38
|
+
this.changed();
|
|
39
|
+
return carried
|
|
40
|
+
.then(async ({ ack, acknowledged }) => {
|
|
41
|
+
try {
|
|
42
|
+
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.answered.add(inputId);
|
|
46
|
+
if (this.handed.delete(inputId))
|
|
47
|
+
this.save();
|
|
48
|
+
}
|
|
49
|
+
if (known === undefined)
|
|
50
|
+
await acknowledged?.();
|
|
51
|
+
})
|
|
52
|
+
.catch(logged)
|
|
53
|
+
.finally(() => {
|
|
54
|
+
this.open--;
|
|
55
|
+
this.changed();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
save() {
|
|
59
|
+
const temporary = `${this.path}.${process.pid}`;
|
|
60
|
+
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.handed)), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, this.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/resident.js
CHANGED
|
@@ -12,6 +12,7 @@ import { CONVERSATION_INPUTS, Conversations } from './conversations.js';
|
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
13
|
import { restart } from './end.js';
|
|
14
14
|
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
15
|
+
import { Inputs } from './inputs.js';
|
|
15
16
|
import { killDescendants } from './scope.js';
|
|
16
17
|
import { quoted } from './shell.js';
|
|
17
18
|
import { SignIns } from './sign-in.js';
|
|
@@ -133,7 +134,7 @@ export async function resident() {
|
|
|
133
134
|
const agents = new Agents(house, () => changed());
|
|
134
135
|
let stream = null;
|
|
135
136
|
let reported = false;
|
|
136
|
-
const idle = () =>
|
|
137
|
+
const idle = () => inputs.idle() && conversations.idle() && agents.idle();
|
|
137
138
|
const changed = () => {
|
|
138
139
|
if (!idle()) {
|
|
139
140
|
reported = false;
|
|
@@ -146,15 +147,17 @@ export async function resident() {
|
|
|
146
147
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
147
148
|
}
|
|
148
149
|
};
|
|
150
|
+
const inputs = new Inputs(house, changed);
|
|
149
151
|
const conversations = new Conversations({
|
|
150
152
|
house,
|
|
151
153
|
agents,
|
|
152
154
|
copies,
|
|
155
|
+
inputs,
|
|
153
156
|
send: (frame) => stream?.send(frame) ?? false,
|
|
154
157
|
changed,
|
|
155
158
|
});
|
|
156
|
-
const signIns = new SignIns(house, agents,
|
|
157
|
-
const updates = new Updates(
|
|
159
|
+
const signIns = new SignIns(house, agents, inputs);
|
|
160
|
+
const updates = new Updates(inputs);
|
|
158
161
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
159
162
|
process.once(signal, () => {
|
|
160
163
|
killDescendants();
|
|
@@ -173,10 +176,11 @@ export async function resident() {
|
|
|
173
176
|
frame: (arrived) => {
|
|
174
177
|
if (arrived.type === 'watching')
|
|
175
178
|
conversations.watching(arrived.watching === true);
|
|
179
|
+
if (arrived.type === 'authority_changed')
|
|
180
|
+
logged(copies.changed(arrived.rooms));
|
|
176
181
|
if (arrived.type === 'work_available' && arrived.subject === 'agents')
|
|
177
182
|
logged(agents.refresh());
|
|
178
183
|
if (arrived.type === 'input') {
|
|
179
|
-
reported = false;
|
|
180
184
|
if (arrived.kind === 'sign_in')
|
|
181
185
|
signIns.start(arrived);
|
|
182
186
|
else if (arrived.kind === 'update')
|
|
@@ -185,7 +189,7 @@ export async function resident() {
|
|
|
185
189
|
conversations.input(arrived);
|
|
186
190
|
else {
|
|
187
191
|
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
188
|
-
|
|
192
|
+
void inputs.carry(String(arrived.input_id), async () => ({ ack: refused }));
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
195
|
},
|
package/dist/scope.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
const LISTING_BYTES = 1 << 28;
|
|
5
5
|
const CGROUPS = '/sys/fs/cgroup';
|
|
6
6
|
const GONE = new Set(['ENOENT', 'ESRCH']);
|
|
7
|
+
const EXITED = new Set(['Z', 'X']);
|
|
7
8
|
function failure(error) {
|
|
8
9
|
return error.code ?? '';
|
|
9
10
|
}
|
|
@@ -24,6 +25,8 @@ function read(pid) {
|
|
|
24
25
|
try {
|
|
25
26
|
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
26
27
|
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
28
|
+
if (EXITED.has(fields[0]))
|
|
29
|
+
return null;
|
|
27
30
|
const command = readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\0', ' ');
|
|
28
31
|
return { parent: Number(fields[1]), session: fields[3], command };
|
|
29
32
|
}
|
package/dist/service.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { kitHome } from './home.js';
|
|
2
5
|
const UNIT = 'house-kit.service';
|
|
3
6
|
// launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
|
|
4
7
|
const MANAGER_MS = 10_000;
|
|
@@ -12,18 +15,37 @@ function start(command, args) {
|
|
|
12
15
|
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
13
16
|
throw new Error(`the House Kit service could not be started: ${cause}`);
|
|
14
17
|
}
|
|
18
|
+
function physical(path) {
|
|
19
|
+
return existsSync(path) ? realpathSync(path) : resolve(path);
|
|
20
|
+
}
|
|
21
|
+
function systemdHome(environment) {
|
|
22
|
+
for (const [, quoted, bare] of environment.matchAll(/"((?:[^"\\]|\\.)*)"|(\S+)/g)) {
|
|
23
|
+
const assignment = bare ?? quoted.replace(/\\(.)/g, '$1');
|
|
24
|
+
if (assignment.startsWith('HOUSE_KIT_HOME='))
|
|
25
|
+
return assignment.slice('HOUSE_KIT_HOME='.length);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function runsThisHome(served) {
|
|
30
|
+
return served !== undefined && physical(served) === physical(kitHome());
|
|
31
|
+
}
|
|
15
32
|
export function startService() {
|
|
16
33
|
if (process.platform === 'darwin') {
|
|
17
34
|
const service = `gui/${process.getuid()}/house-kit`;
|
|
18
35
|
const printed = manage('launchctl', ['print', service]);
|
|
19
36
|
if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
|
|
20
37
|
return false;
|
|
38
|
+
if (!runsThisHome(/^\t+HOUSE_KIT_HOME => (.+)$/m.exec(printed.stdout)?.[1]))
|
|
39
|
+
return false;
|
|
21
40
|
start('launchctl', ['kickstart', service]);
|
|
22
41
|
return true;
|
|
23
42
|
}
|
|
24
43
|
for (const scope of [['--user'], []]) {
|
|
25
44
|
if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
|
|
26
45
|
continue;
|
|
46
|
+
const environment = manage('systemctl', [...scope, 'show', '--property=Environment', '--value', UNIT]).stdout;
|
|
47
|
+
if (!runsThisHome(systemdHome(environment)))
|
|
48
|
+
continue;
|
|
27
49
|
if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
|
|
28
50
|
return false;
|
|
29
51
|
start('systemctl', [...scope, 'start', UNIT]);
|
package/dist/sign-in.js
CHANGED
|
@@ -9,36 +9,20 @@ const CODE = /^[A-Z0-9]+-[A-Z0-9]+$/;
|
|
|
9
9
|
function causeOf(error) {
|
|
10
10
|
return error instanceof Error ? error.message : String(error);
|
|
11
11
|
}
|
|
12
|
-
function logged(error) {
|
|
13
|
-
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
14
|
-
}
|
|
15
12
|
function endOf(signal) {
|
|
16
13
|
return new Promise((resolve) => signal.addEventListener('abort', () => resolve(true), { once: true }));
|
|
17
14
|
}
|
|
18
15
|
export class SignIns {
|
|
19
16
|
house;
|
|
20
17
|
agents;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(house, agents, changed) {
|
|
18
|
+
inputs;
|
|
19
|
+
constructor(house, agents, inputs) {
|
|
24
20
|
this.house = house;
|
|
25
21
|
this.agents = agents;
|
|
26
|
-
this.
|
|
27
|
-
}
|
|
28
|
-
idle() {
|
|
29
|
-
return this.running.size === 0;
|
|
22
|
+
this.inputs = inputs;
|
|
30
23
|
}
|
|
31
24
|
start(input) {
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
this.running.add(input.input_id);
|
|
35
|
-
this.changed();
|
|
36
|
-
this.signIn(input)
|
|
37
|
-
.catch(logged)
|
|
38
|
-
.finally(() => {
|
|
39
|
-
this.running.delete(input.input_id);
|
|
40
|
-
this.changed();
|
|
41
|
-
});
|
|
25
|
+
void this.inputs.carry(input.input_id, () => this.signIn(input));
|
|
42
26
|
}
|
|
43
27
|
async signIn(input) {
|
|
44
28
|
const ending = new AbortController();
|
|
@@ -48,9 +32,7 @@ export class SignIns {
|
|
|
48
32
|
clearTimeout(timer);
|
|
49
33
|
if (ended)
|
|
50
34
|
process.stderr.write(`kit: the ${input.cli} sign-in reached the end House set for it\n`);
|
|
51
|
-
|
|
52
|
-
if (!ended)
|
|
53
|
-
await this.agents.reread(input.cli);
|
|
35
|
+
return ended ? { ack: {} } : { ack: {}, acknowledged: () => this.agents.reread(input.cli) };
|
|
54
36
|
}
|
|
55
37
|
async signedIn(input, signal) {
|
|
56
38
|
await this.login(input, signal);
|
package/dist/stream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { subscribe } from 'node:diagnostics_channel';
|
|
2
|
-
import {
|
|
2
|
+
import { HouseRefusal, retryDelay } from './api.js';
|
|
3
3
|
import { end, REPLACED, signInAgain, updateRequired } from './end.js';
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
@@ -82,9 +82,9 @@ export function holdStream(handlers) {
|
|
|
82
82
|
}
|
|
83
83
|
if (refused !== null && tooOld(refused.text))
|
|
84
84
|
updateRequired();
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
// A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused
|
|
85
|
+
if (!opened && refused !== null)
|
|
86
|
+
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.
|
|
88
88
|
setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
|
|
89
89
|
};
|
|
90
90
|
opening.onopen = () => {
|
package/dist/translate.js
CHANGED
|
@@ -3,14 +3,8 @@ function same(left, right) {
|
|
|
3
3
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
4
4
|
}
|
|
5
5
|
function applied(document, changes) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const result = apply(current, change);
|
|
9
|
-
if (result.outcome !== 'applied')
|
|
10
|
-
return null;
|
|
11
|
-
current = result.document;
|
|
12
|
-
}
|
|
13
|
-
return serialize(current);
|
|
6
|
+
const result = apply(document, changes);
|
|
7
|
+
return result.outcome === 'applied' ? serialize(result.document) : null;
|
|
14
8
|
}
|
|
15
9
|
function components(path, before, after, text) {
|
|
16
10
|
const edits = [];
|
package/dist/update.js
CHANGED
|
@@ -6,9 +6,6 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { run } from './clis.js';
|
|
7
7
|
import { kitHome, workspaceRoot } from './home.js';
|
|
8
8
|
import { service, throughSudo } from './placement.js';
|
|
9
|
-
function logged(error) {
|
|
10
|
-
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
|
-
}
|
|
12
9
|
function directory() {
|
|
13
10
|
return join(kitHome(), 'update');
|
|
14
11
|
}
|
|
@@ -50,6 +47,12 @@ async function recorded() {
|
|
|
50
47
|
return null;
|
|
51
48
|
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
52
49
|
}
|
|
50
|
+
async function forget(inputId) {
|
|
51
|
+
if ((await pendingInput()) !== inputId)
|
|
52
|
+
return;
|
|
53
|
+
await rm(pending(), { force: true });
|
|
54
|
+
await rm(outcome(), { force: true });
|
|
55
|
+
}
|
|
53
56
|
async function finished(inputId) {
|
|
54
57
|
for (;;) {
|
|
55
58
|
const ack = await recorded();
|
|
@@ -90,27 +93,28 @@ function rerun(script, inputId, flags) {
|
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
export class Updates {
|
|
93
|
-
|
|
94
|
-
held = new Set();
|
|
96
|
+
inputs;
|
|
95
97
|
queue = Promise.resolve();
|
|
96
|
-
constructor(
|
|
97
|
-
this.
|
|
98
|
+
constructor(inputs) {
|
|
99
|
+
this.inputs = inputs;
|
|
98
100
|
}
|
|
99
101
|
async resumed() {
|
|
100
102
|
const inputId = await pendingInput();
|
|
101
103
|
if (inputId === null)
|
|
102
104
|
return;
|
|
103
|
-
await this.
|
|
105
|
+
await this.enqueue(inputId, () => finished(inputId));
|
|
104
106
|
}
|
|
105
107
|
start(input) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
void this.enqueue(input.input_id, () => this.carry(input));
|
|
109
|
+
}
|
|
110
|
+
enqueue(inputId, work) {
|
|
111
|
+
const before = this.queue;
|
|
112
|
+
const carried = this.inputs.carry(inputId, async () => {
|
|
113
|
+
await before;
|
|
114
|
+
return { ack: await work(), acknowledged: () => forget(inputId) };
|
|
115
|
+
});
|
|
116
|
+
this.queue = before.then(() => carried);
|
|
117
|
+
return carried;
|
|
114
118
|
}
|
|
115
119
|
async carry(input) {
|
|
116
120
|
if ((await pendingInput()) === input.input_id)
|
|
@@ -131,11 +135,4 @@ export class Updates {
|
|
|
131
135
|
refused: 'the Kit bootstrap did not finish',
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
|
-
async acknowledge(inputId, ack) {
|
|
135
|
-
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
136
|
-
if ((await pendingInput()) !== inputId)
|
|
137
|
-
return;
|
|
138
|
-
await rm(pending(), { force: true });
|
|
139
|
-
await rm(outcome(), { force: true });
|
|
140
|
-
}
|
|
141
138
|
}
|
package/dist/wire.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-c7a397cb0cce7",
|
|
4
4
|
"description": "House Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@agentclientprotocol/sdk": "1.7.0",
|
|
31
|
-
"@agentshouse/mdmodel": "0.1.0-
|
|
31
|
+
"@agentshouse/mdmodel": "0.1.0-cabd61587647f",
|
|
32
32
|
"yaml": "2.9.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|