@agentshouse/kit 0.2.1-alpha.8 → 0.2.1-c031c7457a661
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 +49 -24
- package/dist/abort.js +9 -0
- package/dist/acp.js +49 -86
- package/dist/agents.js +333 -52
- package/dist/api.js +40 -4
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +93 -78
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +217 -72
- package/dist/codex-main.js +58 -0
- package/dist/configure-main.js +6 -0
- package/dist/connection.js +126 -0
- package/dist/conversations.js +642 -261
- package/dist/copies.js +237 -173
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +140 -17
- package/dist/git-main.js +2 -2
- package/dist/git.js +16 -10
- package/dist/home.js +141 -10
- package/dist/house-cli.js +300 -0
- package/dist/house-main.js +5 -102
- package/dist/instructions.js +16 -17
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -16
- package/dist/login.js +128 -95
- package/dist/logout.js +19 -0
- package/dist/mcp.js +41 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +20 -0
- package/dist/resident.js +140 -51
- package/dist/scope.js +206 -0
- package/dist/secret-input.js +7 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +50 -31
- package/dist/skills.js +66 -0
- package/dist/transcription.js +201 -0
- package/dist/translate.js +2 -8
- package/dist/update.js +138 -0
- package/dist/web-wizard.js +100 -0
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +8 -8
- package/dist/blocks.js +0 -51
- package/dist/stream.js +0 -47
package/dist/files.js
CHANGED
|
@@ -1,21 +1,144 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir, rename, rm } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
|
+
import { Readable, Transform } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { parseDocument } from 'yaml';
|
|
8
|
+
import { retryDelay } from './api.js';
|
|
9
|
+
import { Unsettled } from './mcp.js';
|
|
10
|
+
import { relayed } from './relay.js';
|
|
11
|
+
const STALLED_ROUNDS = 8;
|
|
12
|
+
const PART_CHECKSUM = 'x-amz-checksum-sha256';
|
|
13
|
+
const LINK = /\]\((\/rooms\/[^/)\s]+\/[^)\s]+)\)/gu;
|
|
14
|
+
export function linkedPaths(text) {
|
|
15
|
+
return [...new Set(Array.from(text.matchAll(LINK), (link) => link[1]))];
|
|
16
|
+
}
|
|
17
|
+
export function inspectedFile(answer) {
|
|
18
|
+
const document = parseDocument(answer);
|
|
19
|
+
if (document.errors.length > 0)
|
|
20
|
+
return null;
|
|
21
|
+
const inspected = document.toJS();
|
|
22
|
+
if (typeof inspected?.download !== 'string')
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
download: inspected.download,
|
|
26
|
+
sha256: String(inspected.sha256),
|
|
27
|
+
media_type: typeof inspected.media_type === 'string' ? inspected.media_type : null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function paused(milliseconds) {
|
|
31
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
32
|
+
}
|
|
33
|
+
export function storeRefusal(act, status) {
|
|
34
|
+
return new Error(`the ${act} answered ${status}; ${act} the file again`);
|
|
35
|
+
}
|
|
36
|
+
async function partDigests(uploading, partBytes) {
|
|
37
|
+
const whole = createHash('sha256');
|
|
38
|
+
const digests = [];
|
|
39
|
+
for (let start = 0; digests.length === 0 || start < uploading.bytes; start += partBytes) {
|
|
40
|
+
const end = Math.min(start + partBytes, uploading.bytes);
|
|
41
|
+
const digest = createHash('sha256');
|
|
42
|
+
for await (const chunk of uploading.read(start, end)) {
|
|
43
|
+
digest.update(chunk);
|
|
44
|
+
whole.update(chunk);
|
|
45
|
+
}
|
|
46
|
+
digests.push(digest.digest());
|
|
47
|
+
}
|
|
48
|
+
if (whole.digest('hex') !== uploading.sha256)
|
|
49
|
+
throw new Error(`${uploading.name} changed while it was uploaded; upload it again`);
|
|
50
|
+
return digests;
|
|
51
|
+
}
|
|
52
|
+
export async function uploaded(declared, uploading) {
|
|
53
|
+
const partBytes = declared.part_bytes;
|
|
54
|
+
const digests = await partDigests(uploading, partBytes);
|
|
55
|
+
const partSha256 = digests.map((digest) => digest.toString('hex'));
|
|
56
|
+
let answer = await finishing(uploading, declared.id, partSha256);
|
|
57
|
+
let idle = 0;
|
|
58
|
+
let stalled = 0;
|
|
59
|
+
while ('upload' in answer) {
|
|
60
|
+
const { id, parts } = answer.upload;
|
|
61
|
+
let sent = 0;
|
|
62
|
+
let failure = null;
|
|
63
|
+
for (const { part, url } of parts ?? []) {
|
|
64
|
+
const start = (part - 1) * partBytes;
|
|
65
|
+
try {
|
|
66
|
+
await uploading.send(url, start, Math.min(start + partBytes, uploading.bytes), {
|
|
67
|
+
[PART_CHECKSUM]: digests[part - 1].toString('base64'),
|
|
68
|
+
});
|
|
69
|
+
sent++;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
failure = error;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (sent > 0) {
|
|
77
|
+
idle = 0;
|
|
78
|
+
stalled = 0;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (failure !== null && ++stalled >= STALLED_ROUNDS)
|
|
82
|
+
throw failure;
|
|
83
|
+
// A round that sent no part, its store unreachable, is followed by the next finishing call on the jittered curve.
|
|
84
|
+
await paused(retryDelay(idle++));
|
|
85
|
+
}
|
|
86
|
+
answer = await finishing(uploading, id, partSha256);
|
|
87
|
+
}
|
|
88
|
+
return answer;
|
|
89
|
+
}
|
|
90
|
+
async function finishing(uploading, upload, partSha256) {
|
|
91
|
+
for (let unsettled = 1;; unsettled++) {
|
|
92
|
+
try {
|
|
93
|
+
return await uploading.finish(upload, partSha256);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!(error instanceof Unsettled) || unsettled === STALLED_ROUNDS)
|
|
13
97
|
throw error;
|
|
98
|
+
}
|
|
99
|
+
// A finishing call House did not settle, unreached or refused short of final, is sent again on the jittered curve as a conversation resends it, so a dropped connection or a House restart resumes the upload instead of ending it.
|
|
100
|
+
await paused(retryDelay(unsettled - 1));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function drained(body) {
|
|
104
|
+
for await (const _chunk of body)
|
|
105
|
+
;
|
|
106
|
+
}
|
|
107
|
+
export async function downloaded(address, fetching, destination, name) {
|
|
108
|
+
for (let asked = 1;; asked++) {
|
|
109
|
+
const { download, sha256 } = await address();
|
|
110
|
+
const answer = await fetching(download);
|
|
111
|
+
if (answer.status === 403 && asked === 1) {
|
|
112
|
+
await drained(answer.body);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (answer.status !== 200) {
|
|
116
|
+
await drained(answer.body);
|
|
117
|
+
throw storeRefusal('download', answer.status);
|
|
118
|
+
}
|
|
119
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
120
|
+
const partial = join(dirname(destination), `.${basename(destination)}.${randomUUID()}`);
|
|
121
|
+
const digest = createHash('sha256');
|
|
122
|
+
const hashed = new Transform({
|
|
123
|
+
transform(chunk, _encoding, done) {
|
|
124
|
+
digest.update(chunk);
|
|
125
|
+
done(null, chunk);
|
|
126
|
+
},
|
|
14
127
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
128
|
+
try {
|
|
129
|
+
await pipeline(answer.body, hashed, createWriteStream(partial, { flags: 'wx' }));
|
|
130
|
+
if (digest.digest('hex') !== sha256)
|
|
131
|
+
throw new Error(`${name} did not arrive whole; download it again`);
|
|
132
|
+
await rename(partial, destination);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
await rm(partial, { force: true });
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
19
139
|
}
|
|
20
|
-
|
|
140
|
+
}
|
|
141
|
+
export async function fetched(url, signal) {
|
|
142
|
+
const answer = await fetch(relayed(url), { signal });
|
|
143
|
+
return { status: answer.status, body: answer.body ?? Readable.from([]) };
|
|
21
144
|
}
|
package/dist/git-main.js
CHANGED
|
@@ -4,7 +4,7 @@ import { accessSync, constants, realpathSync, statSync } from 'node:fs';
|
|
|
4
4
|
import { request } from 'node:http';
|
|
5
5
|
import { constants as system } from 'node:os';
|
|
6
6
|
import { delimiter, dirname, join } from 'node:path';
|
|
7
|
-
import {
|
|
7
|
+
import { localCopies } from './git.js';
|
|
8
8
|
import { kitHome } from './home.js';
|
|
9
9
|
const VALUED = new Set(['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env', '--attr-source', '--super-prefix']);
|
|
10
10
|
const READS = new Set([
|
|
@@ -125,7 +125,7 @@ async function observed(git, args) {
|
|
|
125
125
|
return delegated(git, args);
|
|
126
126
|
const located = captured(git, [...globals, 'rev-parse', '--path-format=absolute', '--git-common-dir', '--show-toplevel']);
|
|
127
127
|
const [common, worktree] = located?.split('\n') ?? [];
|
|
128
|
-
if (common === undefined || worktree === undefined || !dirname(common).startsWith(`${
|
|
128
|
+
if (common === undefined || worktree === undefined || !dirname(common).startsWith(`${localCopies()}/`)) {
|
|
129
129
|
return delegated(git, args);
|
|
130
130
|
}
|
|
131
131
|
const listing = [...globals, '-C', worktree, 'ls-files', '--stage', '-z'];
|
package/dist/git.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { isUtf8 } from 'node:buffer';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
3
5
|
import { withoutProxy } from './clis.js';
|
|
4
|
-
|
|
6
|
+
import { workspaceRoot } from './home.js';
|
|
7
|
+
export function localCopies() {
|
|
8
|
+
return join(workspaceRoot(), 'local-copies');
|
|
9
|
+
}
|
|
5
10
|
export const HOUSE_REF = 'refs/house/received';
|
|
6
|
-
const COMMITTER = '
|
|
11
|
+
const COMMITTER = 'Kit <house-kit@localhost>';
|
|
7
12
|
export function run(cwd, args, input, env = {}) {
|
|
8
13
|
return new Promise((resolve, reject) => {
|
|
9
14
|
const child = spawn('git', args, { cwd, env: { ...withoutProxy(), ...env } });
|
|
@@ -44,14 +49,15 @@ export async function tree(repository, commit) {
|
|
|
44
49
|
}
|
|
45
50
|
return entries;
|
|
46
51
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
async function blob(repository, object) {
|
|
53
|
+
return (await run(repository, ['cat-file', 'blob', object])).stdout;
|
|
54
|
+
}
|
|
55
|
+
export async function blobText(repository, object) {
|
|
56
|
+
return (await blob(repository, object)).toString('utf8');
|
|
57
|
+
}
|
|
58
|
+
export async function isText(repository, object, maxBytes) {
|
|
59
|
+
const bytes = await blob(repository, object);
|
|
60
|
+
return bytes.length <= maxBytes && isUtf8(bytes) && !bytes.includes(0);
|
|
55
61
|
}
|
|
56
62
|
export function blobId(content) {
|
|
57
63
|
const bytes = Buffer.from(content, 'utf8');
|
package/dist/home.js
CHANGED
|
@@ -1,13 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { selectRelay } from './relay.js';
|
|
4
7
|
const ENROLMENT = 'credential.json';
|
|
8
|
+
const DISCONNECTED = 'disconnected.json';
|
|
9
|
+
const CONFIGURATION = 'kit.json';
|
|
10
|
+
const OWN_AGENT = 'own-agent.json';
|
|
11
|
+
const RESTART = 'restart';
|
|
12
|
+
let defaulted;
|
|
13
|
+
function writable(directory) {
|
|
14
|
+
try {
|
|
15
|
+
accessSync(directory, constants.W_OK | constants.X_OK);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function privateDirectory(path) {
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(path, { mode: 0o700 });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== 'EEXIST')
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const found = lstatSync(path);
|
|
31
|
+
if (found.isSymbolicLink())
|
|
32
|
+
throw new Error(`${path} is a symbolic link`);
|
|
33
|
+
if (!found.isDirectory())
|
|
34
|
+
throw new Error(`${path} is not a directory`);
|
|
35
|
+
if (found.uid !== process.geteuid())
|
|
36
|
+
throw new Error(`${path} is not owned by this User`);
|
|
37
|
+
chmodSync(path, 0o700);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
function ownTemporaryHome() {
|
|
41
|
+
return privateDirectory(join(tmpdir(), `house-kit-${process.getuid()}`));
|
|
42
|
+
}
|
|
43
|
+
function defaultHome() {
|
|
44
|
+
const home = join(homedir(), '.house-kit');
|
|
45
|
+
return existsSync(home) || writable(homedir()) ? home : ownTemporaryHome();
|
|
46
|
+
}
|
|
5
47
|
export function kitHome() {
|
|
6
|
-
return process.env.HOUSE_KIT_HOME
|
|
48
|
+
return process.env.HOUSE_KIT_HOME || (defaulted ??= defaultHome());
|
|
7
49
|
}
|
|
8
|
-
export
|
|
50
|
+
export function socketDirectory() {
|
|
51
|
+
const sockets = privateDirectory(join(realpathSync('/tmp'), `hk-${process.geteuid()}`));
|
|
52
|
+
const home = kitHome();
|
|
53
|
+
const physical = existsSync(home) ? realpathSync(home) : resolve(home);
|
|
54
|
+
return privateDirectory(join(sockets, createHash('sha256').update(physical).digest('hex').slice(0, 12)));
|
|
55
|
+
}
|
|
56
|
+
export function ownerSocket() {
|
|
57
|
+
return join(socketDirectory(), 'owner.sock');
|
|
58
|
+
}
|
|
59
|
+
export function ownerLock() {
|
|
60
|
+
return join(socketDirectory(), 'owner.lock');
|
|
61
|
+
}
|
|
62
|
+
export function workspaceRoot() {
|
|
63
|
+
return process.env.HOUSE_KIT_WORKSPACE ?? '/agents/house';
|
|
64
|
+
}
|
|
65
|
+
export function agentBase() {
|
|
66
|
+
return join(workspaceRoot(), 'agents');
|
|
67
|
+
}
|
|
68
|
+
export async function readHome(name) {
|
|
9
69
|
try {
|
|
10
|
-
return JSON.parse(await readFile(join(kitHome(),
|
|
70
|
+
return JSON.parse(await readFile(join(kitHome(), name), 'utf8'));
|
|
11
71
|
}
|
|
12
72
|
catch (error) {
|
|
13
73
|
if (error.code === 'ENOENT')
|
|
@@ -15,10 +75,81 @@ export async function readEnrolment() {
|
|
|
15
75
|
throw error;
|
|
16
76
|
}
|
|
17
77
|
}
|
|
18
|
-
export async function
|
|
78
|
+
export async function writeHome(name, value) {
|
|
19
79
|
const home = kitHome();
|
|
20
80
|
await mkdir(home, { recursive: true, mode: 0o700 });
|
|
21
|
-
const temporary = join(home, `${
|
|
22
|
-
await writeFile(temporary, `${JSON.stringify(
|
|
23
|
-
await rename(temporary, join(home,
|
|
81
|
+
const temporary = join(home, `${name}.${randomUUID()}`);
|
|
82
|
+
await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
83
|
+
await rename(temporary, join(home, name));
|
|
84
|
+
}
|
|
85
|
+
export async function readEnrolment() {
|
|
86
|
+
const enrolment = await readHome(ENROLMENT);
|
|
87
|
+
if (enrolment !== null)
|
|
88
|
+
selectRelay(process.env.HOUSE_KIT_RELAY || enrolment.relay);
|
|
89
|
+
return enrolment;
|
|
90
|
+
}
|
|
91
|
+
export async function writeEnrolment(enrolment) {
|
|
92
|
+
await writeHome(ENROLMENT, enrolment);
|
|
93
|
+
await rm(join(kitHome(), DISCONNECTED), { force: true });
|
|
94
|
+
}
|
|
95
|
+
export function readDisconnected() {
|
|
96
|
+
return readHome(DISCONNECTED);
|
|
97
|
+
}
|
|
98
|
+
export function forgetDisconnected() {
|
|
99
|
+
return rm(join(kitHome(), DISCONNECTED), { force: true });
|
|
100
|
+
}
|
|
101
|
+
export async function forgetEnrolment({ house, environment }) {
|
|
102
|
+
await writeHome(DISCONNECTED, { house, environment });
|
|
103
|
+
await rm(join(kitHome(), ENROLMENT), { force: true });
|
|
104
|
+
}
|
|
105
|
+
export async function forgetRejected(rejected) {
|
|
106
|
+
const held = await readHome(ENROLMENT);
|
|
107
|
+
if (held?.credential !== rejected)
|
|
108
|
+
return;
|
|
109
|
+
await writeHome(DISCONNECTED, { house: held.house, environment: held.environment });
|
|
110
|
+
const enrolment = join(kitHome(), ENROLMENT);
|
|
111
|
+
const taken = `${enrolment}.${process.pid}`;
|
|
112
|
+
try {
|
|
113
|
+
renameSync(enrolment, taken);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error.code === 'ENOENT')
|
|
117
|
+
return;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
if (JSON.parse(readFileSync(taken, 'utf8')).credential !== rejected) {
|
|
121
|
+
try {
|
|
122
|
+
linkSync(taken, enrolment);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error.code !== 'EEXIST')
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
rmSync(join(kitHome(), DISCONNECTED), { force: true });
|
|
129
|
+
}
|
|
130
|
+
rmSync(taken);
|
|
131
|
+
}
|
|
132
|
+
export function enrolled() {
|
|
133
|
+
return existsSync(join(kitHome(), ENROLMENT));
|
|
134
|
+
}
|
|
135
|
+
export function restartAsked() {
|
|
136
|
+
return existsSync(join(kitHome(), RESTART));
|
|
137
|
+
}
|
|
138
|
+
export function forgetRestart() {
|
|
139
|
+
return rm(join(kitHome(), RESTART), { force: true });
|
|
140
|
+
}
|
|
141
|
+
export function readOwnAgent() {
|
|
142
|
+
return readHome(OWN_AGENT);
|
|
143
|
+
}
|
|
144
|
+
export function writeOwnAgent(ownAgent) {
|
|
145
|
+
return writeHome(OWN_AGENT, ownAgent);
|
|
146
|
+
}
|
|
147
|
+
export function forgetOwnAgent() {
|
|
148
|
+
return rm(join(kitHome(), OWN_AGENT), { force: true });
|
|
149
|
+
}
|
|
150
|
+
export async function readConfiguration() {
|
|
151
|
+
return { skills: (await readHome(CONFIGURATION))?.skills !== false };
|
|
152
|
+
}
|
|
153
|
+
export function writeConfiguration(configuration) {
|
|
154
|
+
return writeHome(CONFIGURATION, configuration);
|
|
24
155
|
}
|