@agentshouse/kit 0.1.0-alpha.25 → 0.1.0-alpha.27
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 +172 -158
- package/bin/connect-linux.sh +15 -4
- package/bin/kit.js +48 -46
- package/bootstrap.json +3 -3
- package/package.json +1 -1
- package/src/adapter-mcp.js +23 -1
- package/src/bridge.js +4 -0
- package/src/configuration.js +13 -209
- package/src/connector-login.js +3 -84
- package/src/contract.js +4 -0
- package/src/delivery.js +4 -7
- package/src/directory.js +6 -26
- 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/house.js +43 -5
- package/src/how-we-work.md +25 -0
- package/src/kit-work-stream.js +1 -1
- package/src/launcher.js +11 -4
- package/src/mcp.js +5 -0
- package/src/setup.js +8 -14
- 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 +51 -26
- package/src/state.js +19 -1
- package/src/tasks.js +72 -0
- package/src/worker.js +46 -68
- package/src/working-copy.js +1 -15
- 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/bin/kit.js
CHANGED
|
@@ -5,12 +5,12 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
import { connectHouse, loginHouse, storedEnvironment, storedHouse } from "../src/login.js";
|
|
6
6
|
import { apply } from "../src/apply.js";
|
|
7
7
|
import { captureSource, resetSource, retrySourceOriginals, sourceStatus } from "../src/source.js";
|
|
8
|
+
import { accountEntries, accountStatus, captureAccount } from "../src/google/entries.js";
|
|
8
9
|
import { setupEnvironment } from "../src/setup.js";
|
|
9
10
|
import { signInConnector } from "../src/connector-login.js";
|
|
10
11
|
import { attachSession } from "../src/attach.js";
|
|
11
12
|
import { runResident, runWorkers } from "../src/worker.js";
|
|
12
13
|
import { openSession, releaseControl, requestDrain } from "../src/house.js";
|
|
13
|
-
import { GOOGLE_OAUTH_CLIENT } from "../src/instance/google.js";
|
|
14
14
|
import { reportHost } from "../src/host.js";
|
|
15
15
|
import { signInAgent, updateAgents } from "../src/agents.js";
|
|
16
16
|
import { serveWorkspaces } from "../src/workspace.js";
|
|
@@ -37,8 +37,8 @@ const AGENTS_USAGE =
|
|
|
37
37
|
"usage: kit agents update [--config <path>] | kit agents sign-in <kind> [--config <path>]";
|
|
38
38
|
const WORKSPACE_USAGE = "usage: kit workspace host [--config <path>] [--passes <count>]";
|
|
39
39
|
const SOURCE_USAGE =
|
|
40
|
-
"usage: kit source <capture|
|
|
41
|
-
" kit source
|
|
40
|
+
"usage: kit source <capture|status> <account|instance> [service] [--config <path>]\n" +
|
|
41
|
+
" kit source <reset|retry-files> <instance> [service] [--config <path>]\n" +
|
|
42
42
|
" kit source sign-in <instance> [--config <path>]";
|
|
43
43
|
const SETUP_USAGE = "usage: kit setup [--config <path>]";
|
|
44
44
|
const REPOSITORY_USAGE =
|
|
@@ -108,13 +108,6 @@ function write(line) {
|
|
|
108
108
|
process.stdout.write(`${line}\n`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function builtGoogleOAuthClient() {
|
|
112
|
-
if (GOOGLE_OAUTH_CLIENT === null) {
|
|
113
|
-
throw new KitRefusal("google_oauth_client_unconfigured", "run npm run build");
|
|
114
|
-
}
|
|
115
|
-
return GOOGLE_OAUTH_CLIENT;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
111
|
async function runLogin(args, directory) {
|
|
119
112
|
const { values } = declared(args, ["--house", "--manual", "--host-browser", "--environment"], 0, { flags: ["--host-browser"] });
|
|
120
113
|
if (values["--manual"] && values["--host-browser"]) throw new Error(LOGIN_USAGE);
|
|
@@ -188,15 +181,12 @@ async function runSetup(args, directory) {
|
|
|
188
181
|
process.once("SIGINT", cancel);
|
|
189
182
|
let setup;
|
|
190
183
|
try {
|
|
191
|
-
setup = await setupEnvironment(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
},
|
|
198
|
-
{ googleOAuthClient: builtGoogleOAuthClient },
|
|
199
|
-
);
|
|
184
|
+
setup = await setupEnvironment({
|
|
185
|
+
house,
|
|
186
|
+
directory,
|
|
187
|
+
configuration: values["--config"] ?? join(directory, "kit.json"),
|
|
188
|
+
signal: cancellation.signal,
|
|
189
|
+
});
|
|
200
190
|
} finally {
|
|
201
191
|
process.removeListener("SIGINT", cancel);
|
|
202
192
|
}
|
|
@@ -268,22 +258,27 @@ async function runSourceCapture(args, directory) {
|
|
|
268
258
|
const house = await storedHouse(directory);
|
|
269
259
|
const cancellation = new AbortController();
|
|
270
260
|
process.once("SIGINT", () => cancellation.abort());
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
261
|
+
const entries = await accountEntries(directory, rest[0]);
|
|
262
|
+
const options = {
|
|
263
|
+
house,
|
|
264
|
+
directory,
|
|
265
|
+
service: rest[1] ?? null,
|
|
266
|
+
signal: cancellation.signal,
|
|
267
|
+
};
|
|
268
|
+
const result =
|
|
269
|
+
entries.length > 0
|
|
270
|
+
? await captureAccount({ ...options, account: rest[0], entries })
|
|
271
|
+
: await captureSource({
|
|
272
|
+
...options,
|
|
273
|
+
configuration: values["--config"] ?? join(directory, "kit.json"),
|
|
274
|
+
instance: rest[0],
|
|
275
|
+
});
|
|
282
276
|
for (const delivered of result.services) {
|
|
283
277
|
const category =
|
|
284
278
|
delivered.health.category === null ? "" : ` (${delivered.health.category})`;
|
|
279
|
+
const cause = delivered.health.cause === undefined ? "" : `, cause ${delivered.health.cause}`;
|
|
285
280
|
write(
|
|
286
|
-
`Delivered ${delivered.delivered} items for ${result.instance}/${delivered.service} in ${delivered.batches} batches; health ${delivered.health.state}${category}.`,
|
|
281
|
+
`Delivered ${delivered.delivered} items for ${result.instance}/${delivered.service} in ${delivered.batches} batches; health ${delivered.health.state}${category}${cause}.`,
|
|
287
282
|
);
|
|
288
283
|
if (delivered.refused !== null) {
|
|
289
284
|
write(
|
|
@@ -332,22 +327,32 @@ async function runSourceRetryFiles(args, directory) {
|
|
|
332
327
|
async function runSourceStatus(args, directory) {
|
|
333
328
|
const { values, rest } = declared(args, ["--config"], 2);
|
|
334
329
|
if (rest.length === 0) throw new Error(SOURCE_USAGE);
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
330
|
+
const entries = await accountEntries(directory, rest[0]);
|
|
331
|
+
const result =
|
|
332
|
+
entries.length > 0
|
|
333
|
+
? await accountStatus({ directory, account: rest[0], entries, service: rest[1] ?? null })
|
|
334
|
+
: await sourceStatus({
|
|
335
|
+
directory,
|
|
336
|
+
configuration: values["--config"] ?? join(directory, "kit.json"),
|
|
337
|
+
instance: rest[0],
|
|
338
|
+
service: rest[1] ?? null,
|
|
339
|
+
});
|
|
341
340
|
for (const reported of result.services) {
|
|
342
341
|
const category = reported.health.category === null ? "" : ` (${reported.health.category})`;
|
|
342
|
+
const cause = reported.health.cause === undefined ? "" : `, cause ${reported.health.cause}`;
|
|
343
343
|
const observed =
|
|
344
344
|
reported.health.observed_at === null ? "never observed" : reported.health.observed_at;
|
|
345
|
+
const signedIn =
|
|
346
|
+
reported.signed_in === undefined ? "" : reported.signed_in ? "signed in; " : "signed out; ";
|
|
345
347
|
write(
|
|
346
348
|
`${result.instance}/${reported.service} -> ${reported.source_ref} in ${reported.rooms.join(", ")}: ` +
|
|
347
|
-
`${reported.health.state}${category} at ${observed}; ` +
|
|
349
|
+
`${signedIn}${reported.health.state}${category}${cause} at ${observed}; ` +
|
|
348
350
|
`cursor ${reported.checkpoint?.cursor ?? "none"}, ${reported.gaps.length} gaps, ` +
|
|
349
351
|
`${reported.dropped.length} dropped items, ${reported.originals.length} unsaved originals.`,
|
|
350
352
|
);
|
|
353
|
+
if (reported.enable !== null && reported.enable !== undefined) {
|
|
354
|
+
write(`Enable the ${reported.enable} in the Google Cloud project of ${result.instance}'s OAuth client.`);
|
|
355
|
+
}
|
|
351
356
|
}
|
|
352
357
|
if (result.services.some((reported) => reported.health.state !== "ok")) process.exitCode = 1;
|
|
353
358
|
}
|
|
@@ -425,14 +430,11 @@ function writeConversationFiles(supervised) {
|
|
|
425
430
|
async function runWorkerRun(args, directory) {
|
|
426
431
|
const { values } = declared(args, ["--config"], 0);
|
|
427
432
|
const house = await storedHouse(directory);
|
|
428
|
-
const supervised = await runWorkers(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
},
|
|
434
|
-
{ googleOAuthClient: builtGoogleOAuthClient },
|
|
435
|
-
);
|
|
433
|
+
const supervised = await runWorkers({
|
|
434
|
+
house,
|
|
435
|
+
directory,
|
|
436
|
+
configuration: values["--config"] ?? join(directory, "kit.json"),
|
|
437
|
+
});
|
|
436
438
|
write(`Worker runtime ready on tmux ${supervised.tmux} in namespace ${supervised.namespace}.`);
|
|
437
439
|
if (supervised.epochReport !== null) {
|
|
438
440
|
write(`House started a new log epoch; reported ${supervised.epochReport.length} held Runs and House settled each.`);
|
package/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.27",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.27/connect-linux.sh)",
|
|
7
7
|
"asset": {
|
|
8
8
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.27/connect-linux.sh"
|
|
10
10
|
},
|
|
11
11
|
"workspace": {
|
|
12
12
|
"argument": "--workspace",
|
package/package.json
CHANGED
package/src/adapter-mcp.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import {
|
|
2
3
|
COMMAND_ADAPTER_INITIALIZE_TIMEOUT_MS,
|
|
3
4
|
HOW_WE_WORK_PATH,
|
|
4
5
|
MCP_PROTOCOL_VERSION,
|
|
5
6
|
SHELL_RENDER,
|
|
6
7
|
} from "./contract.js";
|
|
7
|
-
import { answerText, bridgeCall } from "./mcp.js";
|
|
8
|
+
import { answerText, bridgeCall, editRefusal } from "./mcp.js";
|
|
8
9
|
import { KitRefusal } from "./refusal.js";
|
|
9
10
|
|
|
11
|
+
const HOW_WE_WORK_TEXT = readFileSync(new URL("how-we-work.md", import.meta.url), "utf8");
|
|
12
|
+
|
|
10
13
|
function readableDiscovery(discovery) {
|
|
11
14
|
const identity = discovery?._meta?.["io.modelcontextprotocol/serverInfo"];
|
|
12
15
|
return Array.isArray(discovery?.supportedVersions) &&
|
|
@@ -84,3 +87,22 @@ export async function commandHouseHowWeWork(bridge) {
|
|
|
84
87
|
} while (cursor !== null);
|
|
85
88
|
return pages.join("\n");
|
|
86
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);
|
|
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);
|
|
108
|
+
}
|
package/src/bridge.js
CHANGED
|
@@ -228,6 +228,10 @@ export async function openBridge(options) {
|
|
|
228
228
|
else void Promise.resolve(reported).finally(() => state.interrupts.delete(frame.interrupt));
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
|
+
if (frame.type === "how_we_work") {
|
|
232
|
+
void options.howWeWork?.();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
231
235
|
if (frame.type === "turn_ended") {
|
|
232
236
|
state.stop = {
|
|
233
237
|
providerContinuation: frame.provider_continuation === true,
|
package/src/configuration.js
CHANGED
|
@@ -7,18 +7,10 @@ import {
|
|
|
7
7
|
MIGRATOR_TIMEOUT_MS,
|
|
8
8
|
} from "./contract.js";
|
|
9
9
|
import { declarationReader } from "./declaration.js";
|
|
10
|
-
import { GOOGLE_DRIVE_ID } from "./google/id.js";
|
|
11
10
|
import { KitRefusal } from "./refusal.js";
|
|
12
11
|
|
|
13
|
-
const PRESETS = new Set(["custom", "google"]);
|
|
14
12
|
const AGENT_CLIENTS = new Set(["claude-code"]);
|
|
15
|
-
const GOOGLE_CURATED_SERVICES = new Set(["gmail", "calendar", "drive"]);
|
|
16
13
|
const ENVIRONMENT_VALUE_MAX = 4096;
|
|
17
|
-
const GOOGLE_API_HOST = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.googleapis\.com$/;
|
|
18
|
-
const GOOGLE_API_PATH = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]+$/;
|
|
19
|
-
const GOOGLE_API_SCOPE = /^https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+$/;
|
|
20
|
-
const EXTRACTION_PATH = /^(?:[A-Za-z_][A-Za-z0-9_-]*|\d+)(?:\.(?:[A-Za-z_][A-Za-z0-9_-]*|\d+))*$/;
|
|
21
|
-
const QUERY_NAME = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
22
14
|
|
|
23
15
|
const { refuse, plain, closed, text, positiveCount, filled } = declarationReader(
|
|
24
16
|
"kit_configuration_invalid",
|
|
@@ -116,225 +108,33 @@ function backfillOf(value, declaration) {
|
|
|
116
108
|
return { kind: named[0], bound: positiveCount(backfill[named[0]], declaration) };
|
|
117
109
|
}
|
|
118
110
|
|
|
119
|
-
function
|
|
120
|
-
return filled(value, declaration).map((entry, position) =>
|
|
121
|
-
text(entry, `${declaration}[${position}]`, 512),
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function textArray(value, declaration) {
|
|
126
|
-
if (!Array.isArray(value)) refuse(declaration);
|
|
127
|
-
return value.map((entry, position) => text(entry, `${declaration}[${position}]`, 512));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function googleSelectionOf(value, declaration, service) {
|
|
131
|
-
if (service === "gmail") {
|
|
132
|
-
const selection = closed(value, ["labels"], declaration);
|
|
133
|
-
return { labels: textList(selection.labels, `${declaration}.labels`) };
|
|
134
|
-
}
|
|
135
|
-
if (service === "calendar") {
|
|
136
|
-
const selection = closed(value, ["calendars"], declaration);
|
|
137
|
-
return { calendars: textList(selection.calendars, `${declaration}.calendars`) };
|
|
138
|
-
}
|
|
139
|
-
if (service === "drive") {
|
|
140
|
-
const selection = closed(value, ["files", "folders"], declaration);
|
|
141
|
-
const files =
|
|
142
|
-
selection.files === undefined ? [] : textArray(selection.files, `${declaration}.files`);
|
|
143
|
-
const folders =
|
|
144
|
-
selection.folders === undefined ? [] : textArray(selection.folders, `${declaration}.folders`);
|
|
145
|
-
if (files.length + folders.length === 0) refuse(declaration);
|
|
146
|
-
files.forEach((id, position) => {
|
|
147
|
-
if (!GOOGLE_DRIVE_ID.test(id)) refuse(`${declaration}.files[${position}]`);
|
|
148
|
-
});
|
|
149
|
-
folders.forEach((id, position) => {
|
|
150
|
-
if (!GOOGLE_DRIVE_ID.test(id)) refuse(`${declaration}.folders[${position}]`);
|
|
151
|
-
});
|
|
152
|
-
return { files, folders };
|
|
153
|
-
}
|
|
154
|
-
refuse(declaration);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function extractionPath(value, declaration) {
|
|
158
|
-
const path = text(value, declaration, 512);
|
|
159
|
-
if (!EXTRACTION_PATH.test(path)) refuse(declaration);
|
|
160
|
-
return path;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function queryOf(value, declaration) {
|
|
164
|
-
const query = plain(value, declaration);
|
|
165
|
-
const parsed = {};
|
|
166
|
-
for (const [name, entry] of Object.entries(query)) {
|
|
167
|
-
if (!QUERY_NAME.test(name) || typeof entry !== "string" || entry.length > 2048) {
|
|
168
|
-
refuse(`${declaration}.${name}`);
|
|
169
|
-
}
|
|
170
|
-
parsed[name] = entry;
|
|
171
|
-
}
|
|
172
|
-
return parsed;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function declaredServiceOf(value, declaration) {
|
|
176
|
-
const declared = closed(
|
|
177
|
-
value,
|
|
178
|
-
["host", "path", "query", "page_size", "page_token", "items", "fields", "scopes"],
|
|
179
|
-
declaration,
|
|
180
|
-
);
|
|
181
|
-
const host = text(declared.host, `${declaration}.host`, 253);
|
|
182
|
-
if (!GOOGLE_API_HOST.test(host)) refuse(`${declaration}.host`);
|
|
183
|
-
const path = text(declared.path, `${declaration}.path`, 2048);
|
|
184
|
-
if (!GOOGLE_API_PATH.test(path) || path.includes("..") || path.includes("//")) {
|
|
185
|
-
refuse(`${declaration}.path`);
|
|
186
|
-
}
|
|
187
|
-
const tokens = closed(
|
|
188
|
-
declared.page_token,
|
|
189
|
-
["request", "response", "checkpoint_request", "checkpoint_response"],
|
|
190
|
-
`${declaration}.page_token`,
|
|
191
|
-
);
|
|
192
|
-
const pageToken = {
|
|
193
|
-
request: text(tokens.request, `${declaration}.page_token.request`, 128),
|
|
194
|
-
response: extractionPath(tokens.response, `${declaration}.page_token.response`),
|
|
195
|
-
checkpointRequest: text(
|
|
196
|
-
tokens.checkpoint_request,
|
|
197
|
-
`${declaration}.page_token.checkpoint_request`,
|
|
198
|
-
128,
|
|
199
|
-
),
|
|
200
|
-
checkpointResponse: extractionPath(
|
|
201
|
-
tokens.checkpoint_response,
|
|
202
|
-
`${declaration}.page_token.checkpoint_response`,
|
|
203
|
-
),
|
|
204
|
-
};
|
|
205
|
-
if (
|
|
206
|
-
!QUERY_NAME.test(pageToken.request) ||
|
|
207
|
-
!QUERY_NAME.test(pageToken.checkpointRequest) ||
|
|
208
|
-
pageToken.request === pageToken.checkpointRequest
|
|
209
|
-
) {
|
|
210
|
-
refuse(`${declaration}.page_token`);
|
|
211
|
-
}
|
|
212
|
-
const fields = closed(
|
|
213
|
-
declared.fields,
|
|
214
|
-
[
|
|
215
|
-
"native_id",
|
|
216
|
-
"native_version",
|
|
217
|
-
"provider_modified_at",
|
|
218
|
-
"observed_at",
|
|
219
|
-
"body",
|
|
220
|
-
"container",
|
|
221
|
-
"author",
|
|
222
|
-
"url",
|
|
223
|
-
],
|
|
224
|
-
`${declaration}.fields`,
|
|
225
|
-
);
|
|
226
|
-
const pageSize = text(declared.page_size, `${declaration}.page_size`, 128);
|
|
227
|
-
if (!QUERY_NAME.test(pageSize)) refuse(`${declaration}.page_size`);
|
|
228
|
-
const scopes = textList(declared.scopes, `${declaration}.scopes`).map((scope, position) => {
|
|
229
|
-
if (!GOOGLE_API_SCOPE.test(scope)) refuse(`${declaration}.scopes[${position}]`);
|
|
230
|
-
return scope;
|
|
231
|
-
});
|
|
232
|
-
return {
|
|
233
|
-
host,
|
|
234
|
-
path,
|
|
235
|
-
query: queryOf(declared.query, `${declaration}.query`),
|
|
236
|
-
pageSize,
|
|
237
|
-
scopes: [...new Set(scopes)],
|
|
238
|
-
pageToken,
|
|
239
|
-
items: extractionPath(declared.items, `${declaration}.items`),
|
|
240
|
-
fields: {
|
|
241
|
-
nativeId: extractionPath(fields.native_id, `${declaration}.fields.native_id`),
|
|
242
|
-
nativeVersion:
|
|
243
|
-
fields.native_version === undefined
|
|
244
|
-
? null
|
|
245
|
-
: extractionPath(fields.native_version, `${declaration}.fields.native_version`),
|
|
246
|
-
providerModifiedAt:
|
|
247
|
-
fields.provider_modified_at === undefined
|
|
248
|
-
? null
|
|
249
|
-
: extractionPath(
|
|
250
|
-
fields.provider_modified_at,
|
|
251
|
-
`${declaration}.fields.provider_modified_at`,
|
|
252
|
-
),
|
|
253
|
-
observedAt: extractionPath(fields.observed_at, `${declaration}.fields.observed_at`),
|
|
254
|
-
body: textList(fields.body, `${declaration}.fields.body`).map((path, position) =>
|
|
255
|
-
extractionPath(path, `${declaration}.fields.body[${position}]`),
|
|
256
|
-
),
|
|
257
|
-
container: extractionPath(fields.container, `${declaration}.fields.container`),
|
|
258
|
-
author:
|
|
259
|
-
fields.author === undefined
|
|
260
|
-
? null
|
|
261
|
-
: extractionPath(fields.author, `${declaration}.fields.author`),
|
|
262
|
-
url:
|
|
263
|
-
fields.url === undefined ? null : extractionPath(fields.url, `${declaration}.fields.url`),
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function serviceOf(value, declaration, preset) {
|
|
111
|
+
function serviceOf(value, declaration) {
|
|
269
112
|
const service = closed(
|
|
270
113
|
value,
|
|
271
|
-
["service", "source_ref", "rooms", "backfill", "connector"
|
|
114
|
+
["service", "source_ref", "rooms", "backfill", "connector"],
|
|
272
115
|
declaration,
|
|
273
116
|
);
|
|
274
|
-
const name = declaredId(service.service, `${declaration}.service`);
|
|
275
|
-
if (preset === "custom" && service.connector === undefined) refuse(`${declaration}.connector`);
|
|
276
|
-
if (preset === "custom" && service.selection !== undefined) refuse(`${declaration}.selection`);
|
|
277
|
-
if (preset === "custom" && service.declaration !== undefined) refuse(`${declaration}.declaration`);
|
|
278
|
-
if (preset === "google" && service.connector !== undefined) refuse(`${declaration}.connector`);
|
|
279
|
-
if (preset === "google" && GOOGLE_CURATED_SERVICES.has(name) && service.declaration !== undefined) {
|
|
280
|
-
refuse(`${declaration}.declaration`);
|
|
281
|
-
}
|
|
282
|
-
if (preset === "google" && !GOOGLE_CURATED_SERVICES.has(name)) {
|
|
283
|
-
if (service.selection !== undefined) refuse(`${declaration}.selection`);
|
|
284
|
-
if (service.declaration === undefined) refuse(`${declaration}.declaration`);
|
|
285
|
-
}
|
|
286
117
|
return {
|
|
287
|
-
service:
|
|
118
|
+
service: declaredId(service.service, `${declaration}.service`),
|
|
288
119
|
sourceRef: text(service.source_ref, `${declaration}.source_ref`, 512),
|
|
289
120
|
rooms: filled(service.rooms, `${declaration}.rooms`).map((room, position) =>
|
|
290
121
|
text(room, `${declaration}.rooms[${position}]`, 512),
|
|
291
122
|
),
|
|
292
123
|
backfill: backfillOf(service.backfill, `${declaration}.backfill`),
|
|
293
|
-
|
|
294
|
-
preset === "google" && GOOGLE_CURATED_SERVICES.has(name)
|
|
295
|
-
? googleSelectionOf(service.selection, `${declaration}.selection`, name)
|
|
296
|
-
: null,
|
|
297
|
-
connector: preset === "custom" ? connectorOf(service.connector, `${declaration}.connector`) : null,
|
|
298
|
-
declaration:
|
|
299
|
-
preset === "google" && !GOOGLE_CURATED_SERVICES.has(name)
|
|
300
|
-
? declaredServiceOf(service.declaration, `${declaration}.declaration`)
|
|
301
|
-
: null,
|
|
124
|
+
connector: connectorOf(service.connector, `${declaration}.connector`),
|
|
302
125
|
};
|
|
303
126
|
}
|
|
304
127
|
|
|
305
128
|
function instanceOf(value, declaration) {
|
|
306
|
-
const instance = closed(value, ["id", "preset", "services"
|
|
307
|
-
const preset = declaredId(instance.preset, `${declaration}.preset`);
|
|
308
|
-
if (!PRESETS.has(preset)) refuse(`${declaration}.preset`);
|
|
309
|
-
if (preset !== "google" && instance.scopes !== undefined) refuse(`${declaration}.scopes`);
|
|
310
|
-
const scopes =
|
|
311
|
-
instance.scopes === undefined
|
|
312
|
-
? []
|
|
313
|
-
: textList(instance.scopes, `${declaration}.scopes`).map((scope, position) => {
|
|
314
|
-
if (!GOOGLE_API_SCOPE.test(scope)) refuse(`${declaration}.scopes[${position}]`);
|
|
315
|
-
return scope;
|
|
316
|
-
});
|
|
129
|
+
const instance = closed(value, ["id", "preset", "services"], declaration);
|
|
317
130
|
const services = filled(instance.services, `${declaration}.services`).map((service, position) =>
|
|
318
|
-
serviceOf(service, `${declaration}.services[${position}]
|
|
131
|
+
serviceOf(service, `${declaration}.services[${position}]`),
|
|
319
132
|
);
|
|
320
133
|
services.forEach((service, position) => {
|
|
321
134
|
const first = services.findIndex((candidate) => candidate.service === service.service);
|
|
322
135
|
if (first !== position) refuse(`${declaration}.services[${position}].service`);
|
|
323
136
|
});
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
service.declaration !== null &&
|
|
327
|
-
service.declaration.scopes.some((scope) => !scopes.includes(scope))
|
|
328
|
-
) {
|
|
329
|
-
refuse(`${declaration}.services[${position}].declaration.scopes`);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
return {
|
|
333
|
-
id: declaredId(instance.id, `${declaration}.id`),
|
|
334
|
-
preset,
|
|
335
|
-
services,
|
|
336
|
-
...(preset === "google" ? { scopes: [...new Set(scopes)] } : {}),
|
|
337
|
-
};
|
|
137
|
+
return { id: declaredId(instance.id, `${declaration}.id`), preset: instance.preset, services };
|
|
338
138
|
}
|
|
339
139
|
|
|
340
140
|
function absolutePath(value, declaration) {
|
|
@@ -388,6 +188,7 @@ export function parseConfiguration(document) {
|
|
|
388
188
|
"migrators",
|
|
389
189
|
"clients",
|
|
390
190
|
"host",
|
|
191
|
+
"skills",
|
|
391
192
|
],
|
|
392
193
|
"configuration",
|
|
393
194
|
);
|
|
@@ -397,10 +198,12 @@ export function parseConfiguration(document) {
|
|
|
397
198
|
if (!EXECUTION_AUTHORITIES.includes(declared.execution_authority)) {
|
|
398
199
|
refuse("execution_authority");
|
|
399
200
|
}
|
|
201
|
+
const skills = declared.skills === undefined ? true : declared.skills;
|
|
202
|
+
if (typeof skills !== "boolean") refuse("skills");
|
|
400
203
|
if (!Array.isArray(declared.instances)) refuse("instances");
|
|
401
204
|
if (!Array.isArray(declared.migrators)) refuse("migrators");
|
|
402
|
-
const instances = declared.instances.
|
|
403
|
-
instanceOf(instance, `instances[${position}]`),
|
|
205
|
+
const instances = declared.instances.flatMap((instance, position) =>
|
|
206
|
+
instance?.preset === "custom" ? [instanceOf(instance, `instances[${position}]`)] : [],
|
|
404
207
|
);
|
|
405
208
|
instances.forEach((instance, position) => {
|
|
406
209
|
const first = instances.findIndex((candidate) => candidate.id === instance.id);
|
|
@@ -428,6 +231,7 @@ export function parseConfiguration(document) {
|
|
|
428
231
|
migrators,
|
|
429
232
|
clients,
|
|
430
233
|
host: hostOf(declared.host, "host"),
|
|
234
|
+
skills,
|
|
431
235
|
};
|
|
432
236
|
}
|
|
433
237
|
|
package/src/connector-login.js
CHANGED
|
@@ -1,91 +1,10 @@
|
|
|
1
1
|
import { readConfiguration, sourceInstance } from "./configuration.js";
|
|
2
2
|
import { authorizeCustom, customAuthorizationStatus } from "./custom/authorization.js";
|
|
3
|
-
import { googleAuthorization, authorizeGoogle, GOOGLE_SCOPES } from "./google/oauth.js";
|
|
4
|
-
import { KitRefusal } from "./refusal.js";
|
|
5
3
|
|
|
6
|
-
function
|
|
7
|
-
const curated = GOOGLE_SCOPES[service.service];
|
|
8
|
-
return curated === undefined ? service.declaration.scopes : [curated];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function googleServiceAuthorized(authorization, scopes) {
|
|
12
|
-
return authorization.grants.some((grant) =>
|
|
13
|
-
scopes.every((scope) => grant.scopes.includes(scope)),
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function googleConsent(service, context) {
|
|
18
|
-
return async ({ url }) => {
|
|
19
|
-
const collected = await context.present(
|
|
20
|
-
{
|
|
21
|
-
steps: [
|
|
22
|
-
{ kind: "visit", label: `Sign in to Google and allow access for ${service.service}`, url },
|
|
23
|
-
{
|
|
24
|
-
kind: "collect",
|
|
25
|
-
label: "The address your browser ended on",
|
|
26
|
-
name: "address",
|
|
27
|
-
description:
|
|
28
|
-
"After you allow access, Google sends your browser to a 127.0.0.1 address that does not open. Copy that whole address from the address bar and paste it here.",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
context.signal,
|
|
33
|
-
);
|
|
34
|
-
return collected.address;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const CONNECTOR_LOGINS = {
|
|
39
|
-
custom: {
|
|
40
|
-
status: customAuthorizationStatus,
|
|
41
|
-
login: authorizeCustom,
|
|
42
|
-
},
|
|
43
|
-
google: {
|
|
44
|
-
status: async (instance, context) => {
|
|
45
|
-
const authorization = await googleAuthorization(context.directory, instance.id);
|
|
46
|
-
if (authorization === null) return false;
|
|
47
|
-
return instance.services.every((service) =>
|
|
48
|
-
googleServiceAuthorized(authorization, googleServiceScopes(service)),
|
|
49
|
-
);
|
|
50
|
-
},
|
|
51
|
-
guided: async (instance, context) => {
|
|
52
|
-
for (const service of instance.services) {
|
|
53
|
-
try {
|
|
54
|
-
await authorizeGoogle(
|
|
55
|
-
{
|
|
56
|
-
directory: context.directory,
|
|
57
|
-
instance: instance.id,
|
|
58
|
-
client: context.dependencies.googleOAuthClient(),
|
|
59
|
-
scopes: googleServiceScopes(service),
|
|
60
|
-
approve: googleConsent(service, context),
|
|
61
|
-
},
|
|
62
|
-
context.dependencies.googleDependencies ?? {},
|
|
63
|
-
);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (context.signal.aborted) return "cancelled";
|
|
66
|
-
if (error instanceof KitRefusal && error.code === "google_authorization_declined") {
|
|
67
|
-
return "cancelled";
|
|
68
|
-
}
|
|
69
|
-
return "failed";
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return "ready";
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export function connectorLogin(instance, dependencies = {}) {
|
|
78
|
-
return { ...CONNECTOR_LOGINS, ...(dependencies.connectorLogins ?? {}) }[instance.preset];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function signInConnector(options, dependencies = {}) {
|
|
4
|
+
export async function signInConnector(options) {
|
|
82
5
|
const configuration = await readConfiguration(options.configuration);
|
|
83
6
|
const instance = sourceInstance(configuration, options.instance);
|
|
84
|
-
const gateway = connectorLogin(instance, dependencies);
|
|
85
|
-
if (gateway.login === undefined) {
|
|
86
|
-
throw new KitRefusal("kit_connector_login_guided", `${instance.id} signs in through House setup`);
|
|
87
|
-
}
|
|
88
7
|
const context = { directory: options.directory, signal: options.signal };
|
|
89
|
-
if (await
|
|
90
|
-
return (await
|
|
8
|
+
if (await customAuthorizationStatus(instance, context)) return "skipped";
|
|
9
|
+
return (await authorizeCustom(instance, context)) ? "ready" : "needs_attention";
|
|
91
10
|
}
|
package/src/contract.js
CHANGED
|
@@ -259,6 +259,10 @@ export const KIT_LOGIN_REPORT_PATH = "/kit/logins/report";
|
|
|
259
259
|
export const GUIDED_LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
260
260
|
export const GUIDED_LOGIN_OUTPUT_MAX_BYTES = 64 * 1024;
|
|
261
261
|
|
|
262
|
+
export const KIT_SOURCES_DESIRED_PATH = "/kit/sources/desired";
|
|
263
|
+
export const KIT_SOURCES_REPORT_PATH = "/kit/sources/report";
|
|
264
|
+
export const SOURCE_CAPTURE_INTERVAL_MS = 15 * 60 * 1000;
|
|
265
|
+
|
|
262
266
|
export const EXECUTION_AUTHORITIES = ["allow", "refuse"];
|
|
263
267
|
|
|
264
268
|
export const PERMISSION_OPTION_KINDS = Object.freeze({
|
package/src/delivery.js
CHANGED
|
@@ -54,10 +54,8 @@ const FAILURE_CATEGORIES = {
|
|
|
54
54
|
google_provider_unavailable: "provider",
|
|
55
55
|
google_provider_response_invalid: "provider",
|
|
56
56
|
google_checkpoint_invalid: "provider",
|
|
57
|
-
google_service_unsupported: "config",
|
|
58
|
-
google_oauth_client_unconfigured: "config",
|
|
59
|
-
google_drive_selection_exceeded: "config",
|
|
60
57
|
google_drive_item_missing: "provider",
|
|
58
|
+
api_disabled: "config",
|
|
61
59
|
};
|
|
62
60
|
|
|
63
61
|
export function fileDescriptor(file) {
|
|
@@ -184,8 +182,7 @@ export function stallOf(code, houseRetry = null) {
|
|
|
184
182
|
|
|
185
183
|
export function healthOfRun(stall) {
|
|
186
184
|
if (stall === null) return { state: "ok", category: null };
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
185
|
+
const category = stall.category ?? FAILURE_CATEGORIES[stall.code] ?? "internal";
|
|
186
|
+
if (stall.disposition === "retry") return { state: "retrying", category };
|
|
187
|
+
return { state: "failed", category, cause: stall.code };
|
|
191
188
|
}
|