@agentshouse/kit 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +697 -260
- package/bin/connect-linux.sh +200 -25
- package/bin/kit.js +179 -157
- package/bootstrap.json +17 -0
- package/package.json +7 -2
- package/src/adapter-mcp.js +76 -12
- package/src/agents.js +63 -8
- package/src/binding.js +52 -0
- package/src/blocks.js +2 -26
- package/src/bridge.js +28 -14
- package/src/bytes.js +19 -8
- package/src/configuration.js +13 -209
- package/src/connector-login.js +3 -84
- package/src/contract.js +36 -11
- package/src/delivery.js +4 -7
- package/src/directory.js +6 -26
- package/src/draft.js +4 -82
- package/src/git-main.js +138 -0
- package/src/git.js +62 -0
- package/src/google/calendar.js +39 -45
- package/src/google/drive.js +61 -186
- package/src/google/entries.js +346 -0
- package/src/google/gateway.js +3 -6
- package/src/google/gmail.js +33 -23
- package/src/google/oauth.js +106 -66
- package/src/google/source.js +21 -23
- package/src/host.js +6 -2
- package/src/house-main.js +65 -8
- package/src/house.js +446 -127
- package/src/how-we-work.md +25 -0
- package/src/inbound.js +2 -18
- package/src/kit-work-stream.js +118 -0
- package/src/launcher.js +46 -10
- package/src/local-files.js +3 -11
- package/src/login.js +23 -11
- package/src/mcp.js +18 -0
- package/src/mount.js +13 -21
- package/src/original.js +4 -4
- package/src/pacing.js +41 -0
- package/src/plan.js +37 -45
- package/src/repository.js +3 -61
- package/src/session-bin/git +2 -0
- package/src/setup.js +14 -15
- package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/src/skill-set/ask-matt/SKILL.md +91 -0
- package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
- package/src/skill-set/code-review/LICENSE +21 -0
- package/src/skill-set/code-review/SKILL.md +89 -0
- package/src/skill-set/code-review/agents/openai.yaml +3 -0
- package/src/skill-set/codebase-design/DEEPENING.md +37 -0
- package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/src/skill-set/codebase-design/LICENSE +21 -0
- package/src/skill-set/codebase-design/SKILL.md +116 -0
- package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
- package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
- package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
- package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/src/skill-set/domain-modeling/LICENSE +21 -0
- package/src/skill-set/domain-modeling/SKILL.md +76 -0
- package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
- package/src/skill-set/grill-me/LICENSE +21 -0
- package/src/skill-set/grill-me/SKILL.md +8 -0
- package/src/skill-set/grill-me/agents/openai.yaml +4 -0
- package/src/skill-set/grill-with-docs/LICENSE +21 -0
- package/src/skill-set/grill-with-docs/SKILL.md +8 -0
- package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
- package/src/skill-set/grilling/LICENSE +21 -0
- package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
- package/src/skill-set/handoff/LICENSE +21 -0
- package/src/skill-set/handoff/SKILL.md +17 -0
- package/src/skill-set/handoff/agents/openai.yaml +4 -0
- package/src/skill-set/implement/LICENSE +21 -0
- package/src/skill-set/implement/SKILL.md +16 -0
- package/src/skill-set/implement/agents/openai.yaml +4 -0
- package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
- package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
- package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
- package/src/skill-set/prototype/LICENSE +21 -0
- package/src/skill-set/prototype/LOGIC.md +67 -0
- package/src/skill-set/prototype/SKILL.md +28 -0
- package/src/skill-set/prototype/UI.md +112 -0
- package/src/skill-set/prototype/agents/openai.yaml +3 -0
- package/src/skill-set/research/LICENSE +21 -0
- package/src/skill-set/research/SKILL.md +14 -0
- package/src/skill-set/research/agents/openai.yaml +3 -0
- package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
- package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
- package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/LICENSE +21 -0
- package/src/skill-set/tdd/SKILL.md +40 -0
- package/src/skill-set/tdd/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/mocking.md +59 -0
- package/src/skill-set/tdd/tests.md +77 -0
- package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
- package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/src/skill-set/teach/LICENSE +21 -0
- package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
- package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
- package/src/skill-set/teach/SKILL.md +141 -0
- package/src/skill-set/teach/agents/openai.yaml +4 -0
- package/src/skill-set/to-questionnaire/LICENSE +21 -0
- package/src/skill-set/to-questionnaire/SKILL.md +55 -0
- package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
- package/src/skill-set/to-spec/LICENSE +21 -0
- package/src/skill-set/to-spec/SKILL.md +76 -0
- package/src/skill-set/to-spec/agents/openai.yaml +4 -0
- package/src/skill-set/to-tickets/LICENSE +21 -0
- package/src/skill-set/to-tickets/SKILL.md +106 -0
- package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
- package/src/skill-set/wait-what/LICENSE +21 -0
- package/src/skill-set/wait-what/SKILL.md +8 -0
- package/src/skill-set/wait-what/agents/openai.yaml +4 -0
- package/src/skill-set/wayfinder/LICENSE +21 -0
- package/src/skill-set/wayfinder/SKILL.md +129 -0
- package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
- package/src/skill-set/writing-for-agents/LICENSE +21 -0
- package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/src/skill-set/writing-for-agents/SKILL.md +83 -0
- package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
- package/src/skill-set.js +85 -0
- package/src/source.js +93 -54
- package/src/state.js +19 -1
- package/src/sync-library.js +7 -11
- package/src/sync-state.js +16 -3
- package/src/sync.js +36 -21
- package/src/tasks.js +75 -0
- package/src/telegram-view.js +214 -0
- package/src/telegram.js +161 -66
- package/src/turn-frames.js +69 -0
- package/src/worker.js +674 -184
- package/src/working-copy.js +1221 -0
- package/src/workspace.js +40 -32
- package/src/google/declared.js +0 -146
- package/src/google/id.js +0 -1
- package/src/instance/google.js +0 -1
- package/src/starter-set.js +0 -17
- /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
- /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.30",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,11 +9,16 @@
|
|
|
9
9
|
"private": false,
|
|
10
10
|
"type": "module",
|
|
11
11
|
"bin": {
|
|
12
|
+
"house": "src/session-bin/house",
|
|
12
13
|
"kit": "bin/kit.js"
|
|
13
14
|
},
|
|
15
|
+
"exports": {
|
|
16
|
+
"./bootstrap": "./bootstrap.json"
|
|
17
|
+
},
|
|
14
18
|
"dependencies": {
|
|
15
19
|
"@agentclientprotocol/sdk": "1.5.0",
|
|
16
|
-
"@agentshouse/mdmodel": "0.1.0-alpha.
|
|
20
|
+
"@agentshouse/mdmodel": "0.1.0-alpha.15",
|
|
21
|
+
"ws": "8.22.0",
|
|
17
22
|
"yaml": "2.9.1",
|
|
18
23
|
"zod": "4.6.5"
|
|
19
24
|
},
|
package/src/adapter-mcp.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
COMMAND_ADAPTER_INITIALIZE_TIMEOUT_MS,
|
|
4
|
+
HOW_WE_WORK_PATH,
|
|
5
|
+
MCP_PROTOCOL_VERSION,
|
|
6
|
+
SHELL_RENDER,
|
|
7
|
+
} from "./contract.js";
|
|
8
|
+
import { answerText, bridgeCall, editRefusal } from "./mcp.js";
|
|
3
9
|
import { KitRefusal } from "./refusal.js";
|
|
4
10
|
|
|
11
|
+
const HOW_WE_WORK_TEXT = readFileSync(new URL("how-we-work.md", import.meta.url), "utf8");
|
|
12
|
+
|
|
5
13
|
function readableDiscovery(discovery) {
|
|
6
14
|
const identity = discovery?._meta?.["io.modelcontextprotocol/serverInfo"];
|
|
7
15
|
return Array.isArray(discovery?.supportedVersions) &&
|
|
@@ -30,15 +38,71 @@ export async function commandHouseInstructions(bridge) {
|
|
|
30
38
|
return frame.result.instructions;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
function shellAnswer(text) {
|
|
42
|
+
const lines = text.split("\n");
|
|
43
|
+
if (!lines[0].startsWith(`protocol: render=${SHELL_RENDER} `)) return null;
|
|
44
|
+
const exit = Number(lines[2].slice("exit: ".length));
|
|
45
|
+
let at = lines[5]?.startsWith("authority: ") ? 6 : 5;
|
|
46
|
+
if (exit === 0) return { exit, stderr: [], stdout: lines.slice(at).join("\n"), cursor: null };
|
|
47
|
+
const stderr = [];
|
|
48
|
+
while (lines[at]?.startsWith("stderr: ")) stderr.push(lines[at++].slice("stderr: ".length));
|
|
49
|
+
const stdout = lines.slice(at);
|
|
50
|
+
const cursor = stdout.at(-1)?.startsWith("cursor: ") ? stdout.pop().slice("cursor: ".length) : null;
|
|
51
|
+
return { exit, stderr, stdout: stdout.join("\n"), cursor };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function howWeWorkPage(bridge, cursor) {
|
|
55
|
+
const frame = await bridgeCall(bridge, {
|
|
56
|
+
method: "tools/call",
|
|
57
|
+
params: {
|
|
58
|
+
name: "shell",
|
|
59
|
+
arguments: { command: `cat ${HOW_WE_WORK_PATH}`, ...(cursor === null ? {} : { cursor }) },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (frame.error !== undefined) {
|
|
63
|
+
throw new KitRefusal("kit_how_we_work_unreadable", frame.error.message ?? null);
|
|
64
|
+
}
|
|
65
|
+
const text = answerText(frame.result);
|
|
66
|
+
if (frame.result?.isError === true) {
|
|
67
|
+
if (text.split(":")[0] === "room_not_found") return null;
|
|
68
|
+
throw new KitRefusal("kit_how_we_work_unreadable", text);
|
|
69
|
+
}
|
|
70
|
+
const page = shellAnswer(text);
|
|
71
|
+
if (page === null) throw new KitRefusal("kit_how_we_work_unreadable", text);
|
|
72
|
+
if (page.cursor === null && page.exit !== 0) {
|
|
73
|
+
if (page.stderr.length === 1 && page.stderr[0].split(" ")[1] === "path_not_found") return null;
|
|
74
|
+
throw new KitRefusal("kit_how_we_work_unreadable", page.stderr.join("\n"));
|
|
75
|
+
}
|
|
76
|
+
return page;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function commandHouseHowWeWork(bridge) {
|
|
80
|
+
const pages = [];
|
|
81
|
+
let cursor = null;
|
|
82
|
+
do {
|
|
83
|
+
const page = await howWeWorkPage(bridge, cursor);
|
|
84
|
+
if (page === null) return "";
|
|
85
|
+
pages.push(page.stdout);
|
|
86
|
+
cursor = page.cursor;
|
|
87
|
+
} while (cursor !== null);
|
|
88
|
+
return pages.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function commandHouseCreateHowWeWork(bridge) {
|
|
92
|
+
const frame = await bridgeCall(bridge, {
|
|
93
|
+
method: "tools/call",
|
|
94
|
+
params: {
|
|
95
|
+
name: "edit",
|
|
96
|
+
arguments: { changes: [{ op: "create", path: HOW_WE_WORK_PATH, content: HOW_WE_WORK_TEXT }] },
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
if (frame.error !== undefined) {
|
|
100
|
+
throw new KitRefusal("kit_how_we_work_unwritable", frame.error.message ?? null);
|
|
43
101
|
}
|
|
102
|
+
if (frame.result?.isError !== true) return true;
|
|
103
|
+
const text = answerText(frame.result);
|
|
104
|
+
const { code, conflicts } = editRefusal(text);
|
|
105
|
+
if (code === "room_not_found" || code === "operation_denied") return false;
|
|
106
|
+
if (code === "edit_conflict" && conflicts?.some((conflict) => conflict.code === "path_exists")) return true;
|
|
107
|
+
throw new KitRefusal("kit_how_we_work_unwritable", text);
|
|
44
108
|
}
|
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
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
399
|
+
const result = await reconcileAgentKinds(
|
|
400
|
+
options,
|
|
401
|
+
session,
|
|
402
|
+
configuration,
|
|
348
403
|
dependencies,
|
|
404
|
+
signal,
|
|
405
|
+
{ forced: true },
|
|
349
406
|
);
|
|
350
|
-
|
|
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/binding.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { finalPlan, planSnapshot } from "./plan.js";
|
|
3
4
|
import { KitRefusal } from "./refusal.js";
|
|
4
5
|
import { readNote, writeNote } from "./state.js";
|
|
5
6
|
import { reportedNote, retainedReport } from "./terminal.js";
|
|
@@ -15,6 +16,18 @@ function bindingPath(directory, tmux) {
|
|
|
15
16
|
return join(bindingDirectory(directory), `${tmux}.json`);
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function stoppedNote(stopped) {
|
|
20
|
+
if (stopped === null || stopped === undefined) return null;
|
|
21
|
+
return {
|
|
22
|
+
turn_completed: stopped.turnCompleted,
|
|
23
|
+
capacity_exhausted: stopped.capacityExhausted,
|
|
24
|
+
provider_refused: stopped.providerRefused,
|
|
25
|
+
continuable: stopped.continuable,
|
|
26
|
+
failure: stopped.failure,
|
|
27
|
+
text_accepted: stopped.textAccepted,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
export async function writeBinding(directory, binding) {
|
|
19
32
|
const reported = binding.reported ?? null;
|
|
20
33
|
await writeNote(bindingPath(directory, binding.tmux), {
|
|
@@ -29,7 +42,9 @@ export async function writeBinding(directory, binding) {
|
|
|
29
42
|
sealed: binding.sealed === null || binding.sealed === undefined
|
|
30
43
|
? null
|
|
31
44
|
: { operation: binding.sealed.operation, blocks: binding.sealed.blocks },
|
|
45
|
+
stopped: stoppedNote(binding.stopped),
|
|
32
46
|
reported: reported === null ? null : reportedNote(reported),
|
|
47
|
+
plan: binding.plan ?? null,
|
|
33
48
|
});
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -45,6 +60,37 @@ function retainedSeal(stored) {
|
|
|
45
60
|
return { operation: stored.operation, blocks: stored.blocks };
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
function retainedPlan(stored) {
|
|
64
|
+
if (stored === null || stored === undefined) return null;
|
|
65
|
+
const plan = finalPlan(planSnapshot(stored));
|
|
66
|
+
return plan === null || JSON.stringify(plan) !== JSON.stringify(stored) ? undefined : plan;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function retainedFailure(stored) {
|
|
70
|
+
if (stored === null) return null;
|
|
71
|
+
if (typeof stored !== "object" || Array.isArray(stored)) return undefined;
|
|
72
|
+
if (typeof stored.code !== "string" || stored.code.length === 0) return undefined;
|
|
73
|
+
if (stored.detail !== null && typeof stored.detail !== "string") return undefined;
|
|
74
|
+
return { code: stored.code, detail: stored.detail };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function retainedStop(stored) {
|
|
78
|
+
if (stored === null || stored === undefined) return null;
|
|
79
|
+
if (typeof stored !== "object" || Array.isArray(stored)) return undefined;
|
|
80
|
+
const flags = ["turn_completed", "capacity_exhausted", "provider_refused", "continuable", "text_accepted"];
|
|
81
|
+
if (flags.some((flag) => typeof stored[flag] !== "boolean")) return undefined;
|
|
82
|
+
const failure = retainedFailure(stored.failure);
|
|
83
|
+
if (failure === undefined) return undefined;
|
|
84
|
+
return {
|
|
85
|
+
turnCompleted: stored.turn_completed,
|
|
86
|
+
capacityExhausted: stored.capacity_exhausted,
|
|
87
|
+
providerRefused: stored.provider_refused,
|
|
88
|
+
continuable: stored.continuable,
|
|
89
|
+
failure,
|
|
90
|
+
textAccepted: stored.text_accepted,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
48
94
|
function completeBinding(directory, stored) {
|
|
49
95
|
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return null;
|
|
50
96
|
const named = [stored.run, stored.session, stored.tmux, stored.socket, stored.nonce];
|
|
@@ -54,8 +100,12 @@ function completeBinding(directory, stored) {
|
|
|
54
100
|
if (stored.owner_stop !== null && typeof stored.owner_stop !== "string") return null;
|
|
55
101
|
const sealed = retainedSeal(stored.sealed);
|
|
56
102
|
if (sealed === undefined) return null;
|
|
103
|
+
const stopped = retainedStop(stored.stopped);
|
|
104
|
+
if (stopped === undefined) return null;
|
|
57
105
|
const reported = retainedReport(stored.reported);
|
|
58
106
|
if (reported === undefined) return null;
|
|
107
|
+
const plan = retainedPlan(stored.plan);
|
|
108
|
+
if (plan === undefined) return null;
|
|
59
109
|
return {
|
|
60
110
|
run: stored.run,
|
|
61
111
|
session: stored.session,
|
|
@@ -65,7 +115,9 @@ function completeBinding(directory, stored) {
|
|
|
65
115
|
conversation: stored.conversation === true,
|
|
66
116
|
ownerStop: stored.owner_stop,
|
|
67
117
|
sealed,
|
|
118
|
+
stopped,
|
|
68
119
|
reported,
|
|
120
|
+
plan,
|
|
69
121
|
};
|
|
70
122
|
}
|
|
71
123
|
|
package/src/blocks.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ASSISTANT_BLOCKS_MAX,
|
|
3
|
-
ASSISTANT_TABLE_COLUMNS_MAX,
|
|
4
|
-
ASSISTANT_TEXT_MAX_BYTES,
|
|
5
|
-
} from "./contract.js";
|
|
1
|
+
import { ASSISTANT_TABLE_COLUMNS_MAX } from "./contract.js";
|
|
6
2
|
|
|
7
3
|
const HEADING = /^(#{1,6})\s+(.+)$/;
|
|
8
4
|
const BULLET = /^ {0,3}[-*+]\s+(.+)$/;
|
|
@@ -132,25 +128,5 @@ export function assistantBlocks(text) {
|
|
|
132
128
|
index += 1;
|
|
133
129
|
}
|
|
134
130
|
endParagraph();
|
|
135
|
-
|
|
136
|
-
if (blocks.length <= ASSISTANT_BLOCKS_MAX) return blocks;
|
|
137
|
-
const whole = text.trim();
|
|
138
|
-
return whole === "" ? null : [{ type: "paragraph", text: whole }];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function blocksText(blocks) {
|
|
142
|
-
return blocks
|
|
143
|
-
.map((block) => {
|
|
144
|
-
if (block.type === "formula") return block.expression;
|
|
145
|
-
if (block.type === "list") return block.items.join("\n");
|
|
146
|
-
if (block.type === "table") {
|
|
147
|
-
return [block.headers, ...block.rows].map((row) => row.join("\t")).join("\n");
|
|
148
|
-
}
|
|
149
|
-
return block.text;
|
|
150
|
-
})
|
|
151
|
-
.join("\n\n");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function withinAssistantBound(text) {
|
|
155
|
-
return Buffer.byteLength(text, "utf8") <= ASSISTANT_TEXT_MAX_BYTES;
|
|
131
|
+
return blocks.length === 0 ? null : blocks;
|
|
156
132
|
}
|
package/src/bridge.js
CHANGED
|
@@ -16,9 +16,14 @@ import {
|
|
|
16
16
|
TURN_OUTCOMES,
|
|
17
17
|
} from "./contract.js";
|
|
18
18
|
import { operationId } from "./operation.js";
|
|
19
|
+
import { planSnapshot } from "./plan.js";
|
|
19
20
|
import { houseFailed } from "./refusal.js";
|
|
20
21
|
|
|
21
|
-
function
|
|
22
|
+
function sequenced(value) {
|
|
23
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function bridgedFrame(request, operation) {
|
|
22
27
|
if (typeof request.method !== "string") return request;
|
|
23
28
|
const params = typeof request.params === "object" && request.params !== null ? request.params : {};
|
|
24
29
|
const declared = typeof params._meta === "object" && params._meta !== null ? params._meta : {};
|
|
@@ -50,6 +55,7 @@ export async function openBridge(options) {
|
|
|
50
55
|
closed: false,
|
|
51
56
|
questions: new Map(),
|
|
52
57
|
interrupts: new Set(),
|
|
58
|
+
plan: null,
|
|
53
59
|
};
|
|
54
60
|
const connections = new Set();
|
|
55
61
|
const clients = new Map();
|
|
@@ -201,38 +207,45 @@ export async function openBridge(options) {
|
|
|
201
207
|
return;
|
|
202
208
|
}
|
|
203
209
|
if (frame.type === "draft") {
|
|
204
|
-
if (typeof frame.text === "string" && frame.text !== ""
|
|
210
|
+
if (typeof frame.text === "string" && frame.text !== "" && sequenced(frame.sequence)) {
|
|
211
|
+
options.draft?.({ text: frame.text, sequence: frame.sequence });
|
|
212
|
+
}
|
|
205
213
|
return;
|
|
206
214
|
}
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) {
|
|
213
|
-
void options.plan?.({
|
|
214
|
-
provider_turn: frame.provider_turn,
|
|
215
|
-
steps: frame.steps,
|
|
216
|
-
terminal: frame.terminal,
|
|
217
|
-
});
|
|
215
|
+
if (frame.type === "plan") {
|
|
216
|
+
const snapshot = planSnapshot(frame);
|
|
217
|
+
if (snapshot === null || !sequenced(frame.sequence)) return;
|
|
218
|
+
state.plan = snapshot;
|
|
219
|
+
options.plan?.({ ...snapshot, sequence: frame.sequence });
|
|
218
220
|
return;
|
|
219
221
|
}
|
|
222
|
+
const carried = planSnapshot(frame.plan);
|
|
223
|
+
if (carried !== null) state.plan = carried;
|
|
220
224
|
if (
|
|
221
225
|
frame.type === "interrupt_applied" &&
|
|
222
226
|
typeof frame.interrupt === "string" &&
|
|
223
227
|
["interrupted", "nothing_to_interrupt"].includes(frame.outcome) &&
|
|
224
228
|
state.interrupts.has(frame.interrupt)
|
|
225
229
|
) {
|
|
226
|
-
const reported = options.interrupt?.(
|
|
230
|
+
const reported = options.interrupt?.(
|
|
231
|
+
frame.interrupt,
|
|
232
|
+
frame.outcome,
|
|
233
|
+
frame.outcome === "interrupted" ? state.plan : null,
|
|
234
|
+
);
|
|
227
235
|
if (reported === undefined) state.interrupts.delete(frame.interrupt);
|
|
228
236
|
else void Promise.resolve(reported).finally(() => state.interrupts.delete(frame.interrupt));
|
|
229
237
|
return;
|
|
230
238
|
}
|
|
239
|
+
if (frame.type === "how_we_work") {
|
|
240
|
+
void options.howWeWork?.();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
231
243
|
if (frame.type === "turn_ended") {
|
|
232
244
|
state.stop = {
|
|
233
245
|
providerContinuation: frame.provider_continuation === true,
|
|
234
246
|
providerSessionId: typeof frame.provider_session_id === "string" && frame.provider_session_id.length > 0 && frame.provider_session_id.length <= 512 ? frame.provider_session_id : null,
|
|
235
247
|
stopOutcome: TURN_OUTCOMES.includes(frame.stop_outcome) ? frame.stop_outcome : null,
|
|
248
|
+
plan: carried,
|
|
236
249
|
};
|
|
237
250
|
return;
|
|
238
251
|
}
|
|
@@ -432,6 +445,7 @@ export async function openBridge(options) {
|
|
|
432
445
|
.filter((interaction) => interaction !== null),
|
|
433
446
|
assistant: () => [...state.assistant],
|
|
434
447
|
stop: () => state.stop,
|
|
448
|
+
plan: () => state.plan,
|
|
435
449
|
failure: () => state.failure,
|
|
436
450
|
stopRecovery: () => lifetime.abort(),
|
|
437
451
|
async close() {
|
package/src/bytes.js
CHANGED
|
@@ -31,6 +31,11 @@ async function downloadBody(response, signal) {
|
|
|
31
31
|
return Buffer.concat(chunks, size);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function framedBody(request) {
|
|
35
|
+
if (request.method !== "POST" || !Buffer.isBuffer(request.body)) throw new KitRefusal("kit_byte_body_invalid");
|
|
36
|
+
return Buffer.concat([Buffer.from(`${request.operation}\n`, "utf8"), request.body]);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
export async function transferBytes(request, dependencies = {}) {
|
|
35
40
|
if (request.method !== "GET" && request.method !== "POST") {
|
|
36
41
|
throw new KitRefusal("kit_byte_method_invalid", request.method);
|
|
@@ -38,26 +43,32 @@ export async function transferBytes(request, dependencies = {}) {
|
|
|
38
43
|
if (typeof request.url !== "string" || request.url.length === 0) {
|
|
39
44
|
throw new KitRefusal("kit_byte_url_invalid");
|
|
40
45
|
}
|
|
41
|
-
if (typeof request.operation !== "string" || request.operation.length === 0) {
|
|
46
|
+
if (request.method === "POST" && (typeof request.operation !== "string" || request.operation.length === 0)) {
|
|
42
47
|
throw new KitRefusal("kit_byte_operation_invalid");
|
|
43
48
|
}
|
|
49
|
+
const framed = request.framed === true;
|
|
50
|
+
const body = framed ? framedBody(request) : request.body;
|
|
44
51
|
if (request.method === "POST") {
|
|
45
|
-
if (!Buffer.isBuffer(
|
|
46
|
-
if (
|
|
52
|
+
if (!Buffer.isBuffer(body)) throw new KitRefusal("kit_byte_body_invalid");
|
|
53
|
+
if (body.length > BYTE_GRANT_POST_BYTES_MAX) {
|
|
47
54
|
throw new KitRefusal("original_bytes_exceeded");
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
const fetcher = dependencies.fetch ?? fetch;
|
|
51
|
-
const headers =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const headers = framed
|
|
59
|
+
? { "content-type": "text/plain; charset=utf-8" }
|
|
60
|
+
: request.method === "POST"
|
|
61
|
+
? {
|
|
62
|
+
[BYTE_GRANT_OPERATION_HEADER]: request.operation,
|
|
63
|
+
"content-type": "application/octet-stream",
|
|
64
|
+
}
|
|
65
|
+
: {};
|
|
55
66
|
let response;
|
|
56
67
|
try {
|
|
57
68
|
response = await fetcher(request.url, {
|
|
58
69
|
method: request.method,
|
|
59
70
|
headers,
|
|
60
|
-
body: request.method === "POST" ?
|
|
71
|
+
body: request.method === "POST" ? body : undefined,
|
|
61
72
|
redirect: "manual",
|
|
62
73
|
signal: request.signal,
|
|
63
74
|
});
|