@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c02dacffca87c
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 +48 -29
- package/dist/abort.js +9 -0
- package/dist/acp.js +102 -88
- package/dist/agents.js +356 -52
- package/dist/api.js +40 -4
- package/dist/app-git.js +48 -0
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +93 -78
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chatgpt.js +32 -0
- package/dist/chunks.js +49 -0
- package/dist/clis.js +224 -88
- package/dist/codex-main.js +58 -0
- package/dist/connection.js +105 -0
- package/dist/conversations.js +749 -270
- package/dist/copies.js +475 -260
- package/dist/end.js +33 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +546 -0
- package/dist/files.js +140 -17
- package/dist/git-main.js +2 -2
- package/dist/git.js +62 -22
- package/dist/home.js +120 -9
- package/dist/house-cli.js +299 -0
- package/dist/house-main.js +5 -102
- package/dist/instructions.js +17 -18
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -16
- package/dist/login.js +130 -95
- package/dist/logout.js +19 -0
- package/dist/mcp.js +43 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +82 -0
- package/dist/placement.js +31 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +41 -0
- package/dist/resident.js +144 -52
- package/dist/scope.js +214 -0
- package/dist/scripts.js +255 -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 +52 -32
- package/dist/silence.js +28 -0
- package/dist/skills.js +48 -39
- package/dist/transcription.js +210 -0
- package/dist/translate.js +2 -8
- package/dist/update.js +127 -0
- package/dist/wire.js +40 -0
- package/dist/work.js +346 -0
- package/package.json +21 -24
- package/dist/blocks.js +0 -51
- package/dist/bootstrap.json +0 -28
- package/dist/stream.js +0 -47
- package/how-we-work.md +0 -25
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -91
- package/skills/ask-matt/agents/openai.yaml +0 -4
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -116
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -140
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -76
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -8
- package/skills/grill-me/agents/openai.yaml +0 -4
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -8
- package/skills/grill-with-docs/agents/openai.yaml +0 -4
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -17
- package/skills/handoff/agents/openai.yaml +0 -4
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -16
- package/skills/implement/agents/openai.yaml +0 -4
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -72
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -28
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -14
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/resolving-merge-conflicts/LICENSE +0 -21
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -40
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -141
- package/skills/teach/agents/openai.yaml +0 -4
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -55
- package/skills/to-questionnaire/agents/openai.yaml +0 -4
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -76
- package/skills/to-spec/agents/openai.yaml +0 -4
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -106
- package/skills/to-tickets/agents/openai.yaml +0 -4
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -8
- package/skills/wait-what/agents/openai.yaml +0 -4
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -129
- package/skills/wayfinder/agents/openai.yaml +0 -4
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -83
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/bridge.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
|
-
import {
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
4
|
import { createServer, request as httpRequest } from 'node:http';
|
|
5
5
|
import { request as httpsRequest } from 'node:https';
|
|
6
|
-
import { delimiter, join } from 'node:path';
|
|
6
|
+
import { delimiter, dirname, join } from 'node:path';
|
|
7
7
|
import { parse } from 'yaml';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
|
|
9
|
+
import { loginPath } from './clis.js';
|
|
10
|
+
import { changedPaths, } from './copies.js';
|
|
11
|
+
import { kitHome, readEnrolment, socketDirectory } from './home.js';
|
|
12
|
+
import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
|
|
11
13
|
import { operationId } from './operation.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
14
|
+
import { refusalOf, retryable } from './refusals.js';
|
|
15
|
+
import { relayed } from './relay.js';
|
|
16
|
+
const MCP_REQUEST_BYTES = 4_000_000;
|
|
15
17
|
export async function received(request) {
|
|
16
18
|
let text = '';
|
|
17
19
|
request.setEncoding('utf8');
|
|
@@ -19,7 +21,8 @@ export async function received(request) {
|
|
|
19
21
|
text += chunk;
|
|
20
22
|
return text;
|
|
21
23
|
}
|
|
22
|
-
function relay(request, response,
|
|
24
|
+
function relay(request, response, house, authorization) {
|
|
25
|
+
const target = relayed(house);
|
|
23
26
|
const send = target.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
24
27
|
const forwarded = send(target, {
|
|
25
28
|
method: request.method,
|
|
@@ -31,16 +34,37 @@ function relay(request, response, target, authorization) {
|
|
|
31
34
|
forwarded.on('error', () => response.destroy());
|
|
32
35
|
request.pipe(forwarded);
|
|
33
36
|
}
|
|
37
|
+
function refusalIn(text, status) {
|
|
38
|
+
return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
|
|
39
|
+
}
|
|
34
40
|
function answered(forwarded) {
|
|
35
|
-
if (forwarded.status >= 500)
|
|
36
|
-
throw new Error(`House answered ${forwarded.status}`);
|
|
37
41
|
if (forwarded.status !== 200)
|
|
38
|
-
return { refused:
|
|
42
|
+
return { refused: refusalIn(forwarded.text, forwarded.status) };
|
|
39
43
|
const answer = JSON.parse(forwarded.text);
|
|
40
44
|
if (answer.error !== undefined)
|
|
41
|
-
return { refused:
|
|
45
|
+
return { refused: refusalIn(answer.error.message, forwarded.status) };
|
|
42
46
|
const text = answer.result.content.map((content) => content.text).join('\n');
|
|
43
|
-
return answer.result.isError === true ? { refused:
|
|
47
|
+
return answer.result.isError === true ? { refused: refusalIn(text, forwarded.status) } : { text };
|
|
48
|
+
}
|
|
49
|
+
function pendingSeconds(forwarded) {
|
|
50
|
+
if (forwarded.status !== 200)
|
|
51
|
+
return null;
|
|
52
|
+
const { result } = JSON.parse(forwarded.text);
|
|
53
|
+
if (result === undefined || result.isError === true)
|
|
54
|
+
return null;
|
|
55
|
+
const text = result.content.map((content) => content.text).join('\n');
|
|
56
|
+
if (!text.startsWith('{"pending":'))
|
|
57
|
+
return null;
|
|
58
|
+
const seconds = JSON.parse(text).pending.retry_after;
|
|
59
|
+
return typeof seconds === 'number' ? seconds : null;
|
|
60
|
+
}
|
|
61
|
+
function resendAfter(forwarded, attempt) {
|
|
62
|
+
if (forwarded === null)
|
|
63
|
+
return retryDelay(attempt);
|
|
64
|
+
if (forwarded.status !== 200)
|
|
65
|
+
return retryable(forwarded.text) === false ? null : retryDelay(attempt);
|
|
66
|
+
const seconds = pendingSeconds(forwarded);
|
|
67
|
+
return seconds === null ? null : seconds * 1000;
|
|
44
68
|
}
|
|
45
69
|
function editAnswer(forwarded) {
|
|
46
70
|
const answer = answered(forwarded);
|
|
@@ -50,44 +74,33 @@ function editAnswer(forwarded) {
|
|
|
50
74
|
return {
|
|
51
75
|
accepted: results.flatMap((result) => [
|
|
52
76
|
...(result.from === undefined ? [] : [{ path: result.from, revision: null, content: null }]),
|
|
53
|
-
{ path: result.path, revision: result.
|
|
77
|
+
{ path: result.path, revision: result.revision ?? null },
|
|
54
78
|
]),
|
|
55
79
|
};
|
|
56
80
|
}
|
|
57
81
|
export async function openBridge(house, copies, conversationId, signal) {
|
|
82
|
+
const path = [join(kitHome(), 'shim'), dirname(process.execPath), await loginPath()].join(delimiter);
|
|
58
83
|
const origin = (await readEnrolment()).house;
|
|
59
|
-
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
84
|
+
const { credential } = await retrying(() => house.post(`/kit/conversations/${conversationId}/credential`, {}, signal), signal);
|
|
60
85
|
const closed = new AbortController();
|
|
61
|
-
const
|
|
62
|
-
...message,
|
|
63
|
-
params: {
|
|
64
|
-
...message.params,
|
|
65
|
-
_meta: {
|
|
66
|
-
'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
|
|
67
|
-
'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
68
|
-
'io.modelcontextprotocol/clientCapabilities': {},
|
|
69
|
-
...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
});
|
|
86
|
+
const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
|
|
73
87
|
const send = async (message, operation) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
signal: closed.signal
|
|
87
|
-
}
|
|
88
|
-
return { status: answer.status, text: await answer.text() };
|
|
88
|
+
if (operation === null)
|
|
89
|
+
return sent(message, null);
|
|
90
|
+
for (let attempt = 0;; attempt++) {
|
|
91
|
+
const forwarded = await sent(message, operation).catch((error) => {
|
|
92
|
+
if (closed.signal.aborted)
|
|
93
|
+
throw error;
|
|
94
|
+
return null;
|
|
95
|
+
});
|
|
96
|
+
const after = resendAfter(forwarded, attempt);
|
|
97
|
+
if (after === null)
|
|
98
|
+
return forwarded;
|
|
99
|
+
// A Tool call House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
|
|
100
|
+
await delay(after, undefined, { signal: closed.signal });
|
|
101
|
+
}
|
|
89
102
|
};
|
|
90
|
-
const call = (name, args) => (
|
|
103
|
+
const call = (name, args) => rpc('tools/call', { name, arguments: args });
|
|
91
104
|
const staged = async (operation, changes) => {
|
|
92
105
|
const bytes = Buffer.from(JSON.stringify({ changes }));
|
|
93
106
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
@@ -98,42 +111,39 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
98
111
|
if ('refused' in prepared)
|
|
99
112
|
return prepared;
|
|
100
113
|
const { transfer } = parse(prepared.text);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
let text;
|
|
115
|
+
try {
|
|
116
|
+
text = await retrying(async () => {
|
|
117
|
+
const posted = await fetch(relayed(transfer.url), {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
120
|
+
body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
|
|
121
|
+
signal: closed.signal,
|
|
122
|
+
});
|
|
123
|
+
const answer = await posted.text();
|
|
124
|
+
if (!posted.ok)
|
|
125
|
+
throw new HouseRefusal('/', posted.status, answer);
|
|
126
|
+
return answer;
|
|
127
|
+
}, closed.signal);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (!finalRefusal(error))
|
|
131
|
+
throw error;
|
|
132
|
+
return { refused: refusalIn(error.text, error.status) };
|
|
133
|
+
}
|
|
112
134
|
const held = JSON.parse(text).transfer;
|
|
113
135
|
return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
|
|
114
136
|
};
|
|
115
|
-
const forward =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const tool = message.method === 'tools/call' ? String(message.params?.name) : null;
|
|
119
|
-
const writes = tool !== null &&
|
|
120
|
-
(await house.post('/kit/tools/mutations', {}, closed.signal)).tools.some((listed) => listed.name === tool && listed.writes);
|
|
121
|
-
return send(message, writes ? operationId() : null);
|
|
122
|
-
}
|
|
123
|
-
const answer = await fetch(new URL(path, origin), {
|
|
124
|
-
method: 'POST',
|
|
125
|
-
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
126
|
-
body: text,
|
|
127
|
-
signal: closed.signal,
|
|
128
|
-
});
|
|
129
|
-
return { status: answer.status, text: await answer.text() };
|
|
137
|
+
const forward = (text) => {
|
|
138
|
+
const message = JSON.parse(text);
|
|
139
|
+
return send(message, message.method === 'tools/call' ? operationId() : null);
|
|
130
140
|
};
|
|
131
141
|
const caller = {
|
|
132
142
|
key: `conversation:${conversationId}`,
|
|
133
143
|
submit: async (operation, changes, held, retain) => {
|
|
134
144
|
const inline = call('edit', { changes });
|
|
135
145
|
let upload = held;
|
|
136
|
-
if (upload === undefined && Buffer.byteLength(
|
|
146
|
+
if (upload === undefined && Buffer.byteLength(mcpBody(inline, operation)) > MCP_REQUEST_BYTES) {
|
|
137
147
|
const prepared = await staged(operation, changes);
|
|
138
148
|
if ('refused' in prepared)
|
|
139
149
|
return prepared;
|
|
@@ -148,15 +158,15 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
148
158
|
const { cwd, commit } = JSON.parse(text);
|
|
149
159
|
return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
|
|
150
160
|
}
|
|
161
|
+
if (path === '/git/fetch')
|
|
162
|
+
return { status: 200, text: JSON.stringify(await copies.fetch()) };
|
|
151
163
|
if (path === '/git/observed') {
|
|
152
164
|
await copies.observed(JSON.parse(text));
|
|
153
165
|
return { status: 200, text: '{}' };
|
|
154
166
|
}
|
|
155
|
-
return
|
|
167
|
+
return path === '/' ? forward(text) : { status: 404, text: '' };
|
|
156
168
|
};
|
|
157
|
-
const
|
|
158
|
-
await mkdir(sockets, { recursive: true, mode: 0o700 });
|
|
159
|
-
const socketPath = join(sockets, `${randomUUID()}.sock`);
|
|
169
|
+
const socketPath = join(socketDirectory(), `${randomUUID()}.sock`);
|
|
160
170
|
const bridge = createServer(async (request, response) => {
|
|
161
171
|
try {
|
|
162
172
|
const path = request.url ?? '';
|
|
@@ -168,7 +178,12 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
168
178
|
response.writeHead(answered.status, { 'content-type': 'application/json' }).end(answered.text);
|
|
169
179
|
}
|
|
170
180
|
catch (error) {
|
|
171
|
-
|
|
181
|
+
if (error instanceof HouseRefusal) {
|
|
182
|
+
response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const verb = request.url === '/git/push' || request.url === '/git/fetch';
|
|
186
|
+
response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
|
|
172
187
|
}
|
|
173
188
|
});
|
|
174
189
|
bridge.listen(socketPath);
|
|
@@ -189,13 +204,13 @@ export async function openBridge(house, copies, conversationId, signal) {
|
|
|
189
204
|
return {
|
|
190
205
|
env: {
|
|
191
206
|
HOUSE_BRIDGE: socketPath,
|
|
192
|
-
PATH:
|
|
207
|
+
PATH: path,
|
|
193
208
|
GIT_CONFIG_COUNT: '1',
|
|
194
209
|
GIT_CONFIG_KEY_0: `url.${proxy}/app/.insteadOf`,
|
|
195
210
|
GIT_CONFIG_VALUE_0: app,
|
|
196
211
|
},
|
|
197
212
|
tool: async (name, args) => {
|
|
198
|
-
const forwarded = await forward(
|
|
213
|
+
const forwarded = await forward(JSON.stringify(call(name, args)));
|
|
199
214
|
if (forwarded.status !== 200)
|
|
200
215
|
throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
|
|
201
216
|
const answer = JSON.parse(forwarded.text);
|
package/dist/building.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import { Transform } from 'node:stream';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { readEnrolment } from './home.js';
|
|
7
|
+
import { relayed } from './relay.js';
|
|
8
|
+
const MANIFEST = 'bootstrap.json';
|
|
9
|
+
async function served(name, signal) {
|
|
10
|
+
const enrolment = await readEnrolment();
|
|
11
|
+
if (enrolment === null)
|
|
12
|
+
throw new Error('this Kit is connected to no House');
|
|
13
|
+
const answer = await fetch(relayed(new URL(`/kit-installer/${encodeURIComponent(name)}`, enrolment.house)), { signal });
|
|
14
|
+
if (answer.status === 200 && answer.body !== null)
|
|
15
|
+
return answer;
|
|
16
|
+
await answer.body?.cancel();
|
|
17
|
+
throw new Error(`House answered ${answer.status} for ${name}`);
|
|
18
|
+
}
|
|
19
|
+
export async function servedRelease(signal) {
|
|
20
|
+
const manifest = (await (await served(MANIFEST, signal)).json());
|
|
21
|
+
const installer = manifest.hosts?.linux;
|
|
22
|
+
const sha256 = typeof installer === 'string' ? manifest.files?.[installer]?.sha256 : undefined;
|
|
23
|
+
if (typeof manifest.version !== 'string' || typeof installer !== 'string' || typeof sha256 !== 'string') {
|
|
24
|
+
throw new Error(`House serves no Kit release in ${MANIFEST}`);
|
|
25
|
+
}
|
|
26
|
+
return { version: manifest.version, installer, sha256 };
|
|
27
|
+
}
|
|
28
|
+
export async function downloadServed(name, sha256, destination, signal, most = Infinity) {
|
|
29
|
+
const answer = await served(name, signal);
|
|
30
|
+
const digest = createHash('sha256');
|
|
31
|
+
let bytes = 0;
|
|
32
|
+
const hashed = new Transform({
|
|
33
|
+
transform(chunk, _encoding, done) {
|
|
34
|
+
bytes += chunk.length;
|
|
35
|
+
if (bytes > most)
|
|
36
|
+
return done(new Error(`${name} is larger than its pinned ${most} bytes`));
|
|
37
|
+
digest.update(chunk);
|
|
38
|
+
done(null, chunk);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
await pipeline(answer.body, hashed, createWriteStream(destination), { signal });
|
|
43
|
+
if (digest.digest('hex') !== sha256)
|
|
44
|
+
throw new Error(`${name} does not match its sha256`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
await rm(destination, { force: true });
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/calls.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
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 Calls {
|
|
8
|
+
house;
|
|
9
|
+
changed;
|
|
10
|
+
carried = new Map();
|
|
11
|
+
path = join(kitHome(), 'call-results.json');
|
|
12
|
+
results;
|
|
13
|
+
returned = new Set();
|
|
14
|
+
released = new Set();
|
|
15
|
+
open = 0;
|
|
16
|
+
constructor(house, changed) {
|
|
17
|
+
this.house = house;
|
|
18
|
+
this.changed = changed;
|
|
19
|
+
this.results = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
|
|
20
|
+
}
|
|
21
|
+
keepResult(callId, result) {
|
|
22
|
+
if (this.returned.has(callId))
|
|
23
|
+
return;
|
|
24
|
+
this.results.set(callId, result);
|
|
25
|
+
this.save();
|
|
26
|
+
}
|
|
27
|
+
idle() {
|
|
28
|
+
return this.open === 0;
|
|
29
|
+
}
|
|
30
|
+
carry(callId, work) {
|
|
31
|
+
if (this.released.has(callId))
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
const known = this.carried.get(callId);
|
|
34
|
+
const result = this.results.get(callId);
|
|
35
|
+
const carried = known ?? (result === undefined ? work() : Promise.resolve({ result }));
|
|
36
|
+
if (known === undefined) {
|
|
37
|
+
this.carried.set(callId, carried);
|
|
38
|
+
carried.catch(() => this.carried.delete(callId));
|
|
39
|
+
}
|
|
40
|
+
this.open++;
|
|
41
|
+
this.changed();
|
|
42
|
+
return carried
|
|
43
|
+
.then(async ({ result, returned, release }) => {
|
|
44
|
+
try {
|
|
45
|
+
await this.house.deliver(`/kit/inputs/${callId}/ack`, result);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.returned.add(callId);
|
|
49
|
+
if (this.results.delete(callId))
|
|
50
|
+
this.save();
|
|
51
|
+
}
|
|
52
|
+
if (known !== undefined)
|
|
53
|
+
return;
|
|
54
|
+
if (release) {
|
|
55
|
+
this.carried.delete(callId);
|
|
56
|
+
this.released.add(callId);
|
|
57
|
+
}
|
|
58
|
+
await returned?.();
|
|
59
|
+
})
|
|
60
|
+
.catch(logged)
|
|
61
|
+
.finally(() => {
|
|
62
|
+
this.open--;
|
|
63
|
+
this.changed();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
save() {
|
|
67
|
+
const temporary = `${this.path}.${process.pid}`;
|
|
68
|
+
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.results)), { mode: 0o600 });
|
|
69
|
+
renameSync(temporary, this.path);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { placeAppGit } from './app-git.js';
|
|
3
|
+
import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
|
|
4
|
+
import { accepted, DEFAULT_HOUSE } from './login.js';
|
|
5
|
+
const KEY_SOURCE = 'New key for a script on Connections';
|
|
6
|
+
const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
|
|
7
|
+
--key <key> a key from ${KEY_SOURCE}
|
|
8
|
+
--house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
|
|
9
|
+
`;
|
|
10
|
+
export async function chatgpt(argv) {
|
|
11
|
+
const { values } = parseArgs({
|
|
12
|
+
args: argv,
|
|
13
|
+
options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
|
|
14
|
+
});
|
|
15
|
+
if (values.help) {
|
|
16
|
+
process.stdout.write(USAGE);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (values.key === undefined)
|
|
20
|
+
throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
|
|
21
|
+
const before = await readOwnAgent();
|
|
22
|
+
const house = values.house ?? before?.house ?? DEFAULT_HOUSE;
|
|
23
|
+
if (!(await accepted(house, values.key)))
|
|
24
|
+
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
25
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
26
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
27
|
+
await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
28
|
+
if (before !== null && before.credential !== values.key) {
|
|
29
|
+
process.stdout.write('This replaced the key this Kit home held; give each session its own HOUSE_KIT_HOME to keep its own key.\n');
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
32
|
+
}
|
package/dist/chunks.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { downloaded, fetched } from './files.js';
|
|
4
|
+
const DOWNLOADS_AT_ONCE = 6;
|
|
5
|
+
// A chunk is about one megabyte, so a download still unfinished after two minutes holds every Room behind it on a dead connection.
|
|
6
|
+
const CHUNK_DOWNLOAD_MS = 120_000;
|
|
7
|
+
class AddressExpired extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("House's store refused every chunk it asked for");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function once(chunk) {
|
|
13
|
+
let asked = false;
|
|
14
|
+
return async () => {
|
|
15
|
+
if (asked)
|
|
16
|
+
throw new AddressExpired();
|
|
17
|
+
asked = true;
|
|
18
|
+
return { download: chunk.download, sha256: chunk.hash };
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export async function stagedChunks(directory, chunks) {
|
|
22
|
+
let next = 0;
|
|
23
|
+
let failure;
|
|
24
|
+
const downloading = async () => {
|
|
25
|
+
while (next < chunks.length && failure === undefined) {
|
|
26
|
+
const chunk = chunks[next++];
|
|
27
|
+
const reading = (url) => fetched(url, AbortSignal.timeout(CHUNK_DOWNLOAD_MS));
|
|
28
|
+
await downloaded(once(chunk), reading, join(directory, chunk.hash), 'A Room chunk').catch((error) => {
|
|
29
|
+
failure ??= error;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
await Promise.all(Array.from({ length: DOWNLOADS_AT_ONCE }, downloading));
|
|
34
|
+
return failure;
|
|
35
|
+
}
|
|
36
|
+
export async function* chunkTexts(directory, chunks, models) {
|
|
37
|
+
yield* models;
|
|
38
|
+
for (const { hash } of chunks) {
|
|
39
|
+
const file = join(directory, hash);
|
|
40
|
+
for (const line of (await readFile(file, 'utf8')).split('\n')) {
|
|
41
|
+
if (line === '')
|
|
42
|
+
continue;
|
|
43
|
+
const entry = JSON.parse(line);
|
|
44
|
+
if (entry.content !== undefined)
|
|
45
|
+
yield entry;
|
|
46
|
+
}
|
|
47
|
+
await rm(file);
|
|
48
|
+
}
|
|
49
|
+
}
|