@suwujs/king-ai 0.2.0
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 +96 -0
- package/dist/src/agent-config-validation.d.ts +9 -0
- package/dist/src/agent-config-validation.js +30 -0
- package/dist/src/api.d.ts +4 -0
- package/dist/src/api.js +48 -0
- package/dist/src/attachments.d.ts +45 -0
- package/dist/src/attachments.js +322 -0
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +1697 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +20 -0
- package/dist/src/cron.d.ts +11 -0
- package/dist/src/cron.js +65 -0
- package/dist/src/daemon.d.ts +36 -0
- package/dist/src/daemon.js +373 -0
- package/dist/src/engine.d.ts +32 -0
- package/dist/src/engine.js +1014 -0
- package/dist/src/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat.js +28 -0
- package/dist/src/host-api.d.ts +40 -0
- package/dist/src/host-api.js +59 -0
- package/dist/src/host-control.d.ts +48 -0
- package/dist/src/host-control.js +1279 -0
- package/dist/src/host-export.d.ts +50 -0
- package/dist/src/host-export.js +187 -0
- package/dist/src/host-feedback.d.ts +78 -0
- package/dist/src/host-feedback.js +178 -0
- package/dist/src/host-home.d.ts +13 -0
- package/dist/src/host-home.js +54 -0
- package/dist/src/host-ledger.d.ts +261 -0
- package/dist/src/host-ledger.js +554 -0
- package/dist/src/host-loop-events.d.ts +69 -0
- package/dist/src/host-loop-events.js +288 -0
- package/dist/src/host-permission.d.ts +36 -0
- package/dist/src/host-permission.js +180 -0
- package/dist/src/host-policy.d.ts +15 -0
- package/dist/src/host-policy.js +36 -0
- package/dist/src/host-run-executor.d.ts +13 -0
- package/dist/src/host-run-executor.js +221 -0
- package/dist/src/host-run-heartbeat.d.ts +40 -0
- package/dist/src/host-run-heartbeat.js +103 -0
- package/dist/src/host-run-layout.d.ts +17 -0
- package/dist/src/host-run-layout.js +387 -0
- package/dist/src/host-run-meta.d.ts +41 -0
- package/dist/src/host-run-meta.js +115 -0
- package/dist/src/host-run-spec.d.ts +149 -0
- package/dist/src/host-run-spec.js +465 -0
- package/dist/src/host-runs.d.ts +77 -0
- package/dist/src/host-runs.js +195 -0
- package/dist/src/host-sdk.d.ts +412 -0
- package/dist/src/host-sdk.js +628 -0
- package/dist/src/host-server.d.ts +26 -0
- package/dist/src/host-server.js +921 -0
- package/dist/src/host-timeline.d.ts +24 -0
- package/dist/src/host-timeline.js +161 -0
- package/dist/src/jsonl.d.ts +13 -0
- package/dist/src/jsonl.js +47 -0
- package/dist/src/lifecycle.d.ts +5 -0
- package/dist/src/lifecycle.js +18 -0
- package/dist/src/message-routing.d.ts +32 -0
- package/dist/src/message-routing.js +119 -0
- package/dist/src/paths.d.ts +19 -0
- package/dist/src/paths.js +26 -0
- package/dist/src/project-profile.d.ts +49 -0
- package/dist/src/project-profile.js +356 -0
- package/dist/src/remediation.d.ts +14 -0
- package/dist/src/remediation.js +114 -0
- package/dist/src/remote-devices.d.ts +41 -0
- package/dist/src/remote-devices.js +156 -0
- package/dist/src/remote-diagnostics.d.ts +39 -0
- package/dist/src/remote-diagnostics.js +199 -0
- package/dist/src/remote-ssh.d.ts +39 -0
- package/dist/src/remote-ssh.js +129 -0
- package/dist/src/run-stream.d.ts +57 -0
- package/dist/src/run-stream.js +119 -0
- package/dist/src/runner.d.ts +131 -0
- package/dist/src/runner.js +1161 -0
- package/dist/src/runtime-data.d.ts +68 -0
- package/dist/src/runtime-data.js +172 -0
- package/dist/src/service.d.ts +114 -0
- package/dist/src/service.js +631 -0
- package/dist/src/shared-skills.d.ts +26 -0
- package/dist/src/shared-skills.js +85 -0
- package/dist/src/shim.d.ts +1 -0
- package/dist/src/shim.js +64 -0
- package/dist/src/skill-check.d.ts +17 -0
- package/dist/src/skill-check.js +158 -0
- package/dist/src/sse.d.ts +9 -0
- package/dist/src/sse.js +36 -0
- package/dist/src/team-routing.d.ts +55 -0
- package/dist/src/team-routing.js +131 -0
- package/dist/src/team-workflow.d.ts +78 -0
- package/dist/src/team-workflow.js +253 -0
- package/dist/src/text.d.ts +7 -0
- package/dist/src/text.js +27 -0
- package/dist/src/types.d.ts +98 -0
- package/dist/src/types.js +1 -0
- package/dist/src/usage.d.ts +116 -0
- package/dist/src/usage.js +350 -0
- package/dist/src/workspace.d.ts +9 -0
- package/dist/src/workspace.js +56 -0
- package/dist/src/worktree.d.ts +47 -0
- package/dist/src/worktree.js +201 -0
- package/package.json +63 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { cp, mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { basename, delimiter, join } from "node:path";
|
|
5
|
+
function splitRoots(value) {
|
|
6
|
+
if (!value)
|
|
7
|
+
return [];
|
|
8
|
+
return value
|
|
9
|
+
.split(delimiter)
|
|
10
|
+
.flatMap((entry) => entry.split(","))
|
|
11
|
+
.map((entry) => entry.trim())
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.filter((entry, idx, all) => all.indexOf(entry) === idx);
|
|
14
|
+
}
|
|
15
|
+
export function sharedSkillRoots(env = process.env) {
|
|
16
|
+
return splitRoots(env.KING_AI_SHARED_SKILLS);
|
|
17
|
+
}
|
|
18
|
+
export function sharedSkillSnapshotsRoot(env = process.env) {
|
|
19
|
+
return env.KING_AI_SKILL_SNAPSHOTS_DIR;
|
|
20
|
+
}
|
|
21
|
+
export async function listSharedSkills(sourceRoots) {
|
|
22
|
+
const skills = [];
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
for (const root of sourceRoots) {
|
|
25
|
+
if (!existsSync(root))
|
|
26
|
+
continue;
|
|
27
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
28
|
+
if (!entry.isDirectory())
|
|
29
|
+
continue;
|
|
30
|
+
const sourceDir = join(root, entry.name);
|
|
31
|
+
if (!existsSync(join(sourceDir, "SKILL.md")))
|
|
32
|
+
continue;
|
|
33
|
+
if (seen.has(entry.name))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(entry.name);
|
|
36
|
+
skills.push({ name: entry.name, sourceDir });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return skills.sort((left, right) => left.name.localeCompare(right.name));
|
|
40
|
+
}
|
|
41
|
+
export async function installSharedSkills(agentHome, sourceRoots = sharedSkillRoots(), env = process.env) {
|
|
42
|
+
const skills = await listSharedSkills(sourceRoots);
|
|
43
|
+
const targets = [join(agentHome, ".claude", "skills"), join(agentHome, ".codex", "skills")];
|
|
44
|
+
const snapshot = await createSharedSkillSnapshot(agentHome, skills, env);
|
|
45
|
+
for (const target of targets)
|
|
46
|
+
await mkdir(target, { recursive: true });
|
|
47
|
+
for (const skill of skills) {
|
|
48
|
+
const sourceStat = await stat(skill.sourceDir);
|
|
49
|
+
if (!sourceStat.isDirectory())
|
|
50
|
+
continue;
|
|
51
|
+
for (const target of targets) {
|
|
52
|
+
const dest = join(target, skill.name || basename(skill.sourceDir));
|
|
53
|
+
await rm(dest, { recursive: true, force: true });
|
|
54
|
+
const snapshotSkill = snapshot?.skills.find((entry) => entry.name === skill.name);
|
|
55
|
+
await cp(snapshotSkill?.snapshotDir ?? skill.sourceDir, dest, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { sourceRoots, installed: skills, targets, snapshot };
|
|
59
|
+
}
|
|
60
|
+
async function createSharedSkillSnapshot(agentHome, skills, env = process.env) {
|
|
61
|
+
if (skills.length === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const snapshotId = `skills-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomUUID()}`;
|
|
64
|
+
const root = join(sharedSkillSnapshotsRoot(env) || join(agentHome, ".king-ai", "skill-snapshots"), snapshotId);
|
|
65
|
+
await mkdir(root, { recursive: true });
|
|
66
|
+
const snapshotSkills = [];
|
|
67
|
+
for (const skill of skills) {
|
|
68
|
+
const snapshotDir = join(root, skill.name || basename(skill.sourceDir));
|
|
69
|
+
await cp(skill.sourceDir, snapshotDir, { recursive: true });
|
|
70
|
+
snapshotSkills.push({
|
|
71
|
+
name: skill.name,
|
|
72
|
+
sourceDir: skill.sourceDir,
|
|
73
|
+
snapshotDir
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const snapshot = {
|
|
77
|
+
id: snapshotId,
|
|
78
|
+
createdAt: new Date().toISOString(),
|
|
79
|
+
root,
|
|
80
|
+
manifestPath: join(root, "manifest.json"),
|
|
81
|
+
skills: snapshotSkills
|
|
82
|
+
};
|
|
83
|
+
await writeFile(snapshot.manifestPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
|
|
84
|
+
return snapshot;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeShim(binDir: string): Promise<void>;
|
package/dist/src/shim.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const SHIM = `#!/usr/bin/env node
|
|
4
|
+
'use strict'
|
|
5
|
+
;(async () => {
|
|
6
|
+
const commandName = require('path').basename(process.argv[1] || process.argv[0] || 'king-ai')
|
|
7
|
+
const url = process.env.KING_AI_AGENT_RUNTIME_URL
|
|
8
|
+
let token = process.env.KING_AI_AGENT_RUNTIME_TOKEN
|
|
9
|
+
const tenant = process.env.KING_AI_AGENT_RUNTIME_TENANT
|
|
10
|
+
const tokenFile = process.env.KING_AI_AGENT_RUNTIME_TOKEN_FILE
|
|
11
|
+
if (tokenFile) {
|
|
12
|
+
try {
|
|
13
|
+
const fresh = require('fs').readFileSync(tokenFile, 'utf8').trim()
|
|
14
|
+
if (fresh) token = fresh
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
if (!url || !token) {
|
|
18
|
+
console.error(commandName + ': runtime env not set')
|
|
19
|
+
process.exit(70)
|
|
20
|
+
}
|
|
21
|
+
const fs = require('fs')
|
|
22
|
+
const argv = process.argv.slice(2)
|
|
23
|
+
const fileIdx = argv.indexOf('--file')
|
|
24
|
+
if (fileIdx >= 0 && argv[fileIdx + 1] !== undefined) {
|
|
25
|
+
try {
|
|
26
|
+
argv.splice(fileIdx, 2, fs.readFileSync(argv[fileIdx + 1], 'utf8'))
|
|
27
|
+
} catch {
|
|
28
|
+
console.error(commandName + ': cannot read --file ' + argv[fileIdx + 1])
|
|
29
|
+
process.exit(70)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const stdinIdx = argv.indexOf('--stdin')
|
|
33
|
+
if (stdinIdx >= 0) {
|
|
34
|
+
let body = ''
|
|
35
|
+
try { body = fs.readFileSync(0, 'utf8') } catch {}
|
|
36
|
+
argv.splice(stdinIdx, 1, body)
|
|
37
|
+
}
|
|
38
|
+
const res = await fetch(url + '/cli', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json', ...(tenant ? { 'X-King-AI-Tenant': tenant } : {}) },
|
|
41
|
+
body: JSON.stringify({ argv, agentId: process.env.KING_AI_AGENT_ID || undefined, engine: process.env.KING_AI_AGENT_ENGINE || undefined })
|
|
42
|
+
})
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text().catch(() => '')
|
|
45
|
+
console.error(commandName + ': HTTP ' + res.status + ' ' + text)
|
|
46
|
+
process.exit(70)
|
|
47
|
+
}
|
|
48
|
+
const data = await res.json()
|
|
49
|
+
if (typeof data.text === 'string' && data.text) process.stdout.write(data.text + '\\n')
|
|
50
|
+
process.exit(typeof data.exitCode === 'number' ? data.exitCode : 0)
|
|
51
|
+
})().catch((err) => {
|
|
52
|
+
const commandName = require('path').basename(process.argv[1] || process.argv[0] || 'king-ai')
|
|
53
|
+
console.error(commandName + ':', err && err.message ? err.message : err)
|
|
54
|
+
process.exit(70)
|
|
55
|
+
})
|
|
56
|
+
`;
|
|
57
|
+
export async function writeShim(binDir) {
|
|
58
|
+
await mkdir(binDir, { recursive: true });
|
|
59
|
+
for (const name of ["king-ai"]) {
|
|
60
|
+
const shim = join(binDir, name);
|
|
61
|
+
await writeFile(shim, SHIM, "utf8");
|
|
62
|
+
await chmod(shim, 0o755);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const KNOWN_COMMANDS: Set<string>;
|
|
2
|
+
export declare const KNOWN_SUBCOMMANDS: Record<string, Set<string>>;
|
|
3
|
+
export interface SkillCheckResult {
|
|
4
|
+
skillName: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
referencedCommands: string[];
|
|
7
|
+
invalidCommands: string[];
|
|
8
|
+
warnings: string[];
|
|
9
|
+
valid: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function extractCommands(content: string): string[];
|
|
12
|
+
export declare function validateCommand(commandReference: string): boolean;
|
|
13
|
+
export declare function checkSkill(filePath: string): SkillCheckResult;
|
|
14
|
+
export declare function findSkillFiles(skillsDir: string): string[];
|
|
15
|
+
export declare function checkAllSkills(skillsDir: string): SkillCheckResult[];
|
|
16
|
+
export declare function formatDashboard(results: SkillCheckResult[], commandName?: string): string;
|
|
17
|
+
export declare function runSkillCheck(skillsDir: string, commandName?: string): void;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
export const KNOWN_COMMANDS = new Set([
|
|
4
|
+
"agent",
|
|
5
|
+
"agents",
|
|
6
|
+
"agenda",
|
|
7
|
+
"artifact",
|
|
8
|
+
"calendar",
|
|
9
|
+
"capsule",
|
|
10
|
+
"card",
|
|
11
|
+
"claim",
|
|
12
|
+
"contacts",
|
|
13
|
+
"context",
|
|
14
|
+
"dm",
|
|
15
|
+
"doc",
|
|
16
|
+
"escalate",
|
|
17
|
+
"eval",
|
|
18
|
+
"evaluate",
|
|
19
|
+
"feedback",
|
|
20
|
+
"glance",
|
|
21
|
+
"hypothesis",
|
|
22
|
+
"inbox",
|
|
23
|
+
"initiative",
|
|
24
|
+
"loop",
|
|
25
|
+
"messages",
|
|
26
|
+
"merge",
|
|
27
|
+
"observe",
|
|
28
|
+
"participants",
|
|
29
|
+
"preamble",
|
|
30
|
+
"plan",
|
|
31
|
+
"react",
|
|
32
|
+
"reaction",
|
|
33
|
+
"recv",
|
|
34
|
+
"reply",
|
|
35
|
+
"review",
|
|
36
|
+
"route",
|
|
37
|
+
"roster",
|
|
38
|
+
"safety",
|
|
39
|
+
"send",
|
|
40
|
+
"status",
|
|
41
|
+
"task",
|
|
42
|
+
"unclaim",
|
|
43
|
+
"watch",
|
|
44
|
+
"whoami"
|
|
45
|
+
]);
|
|
46
|
+
export const KNOWN_SUBCOMMANDS = {
|
|
47
|
+
agent: new Set(["computer"]),
|
|
48
|
+
agents: new Set(["spawn", "destroy"]),
|
|
49
|
+
artifact: new Set(["put", "list", "get", "check"]),
|
|
50
|
+
calendar: new Set(["list", "create"]),
|
|
51
|
+
capsule: new Set(["create", "list", "mine", "get", "update"]),
|
|
52
|
+
card: new Set(["list", "create", "claim", "move", "done", "release"]),
|
|
53
|
+
context: new Set(["get", "set", "list", "delete"]),
|
|
54
|
+
doc: new Set(["list", "create", "show", "append", "update"]),
|
|
55
|
+
eval: new Set(["parse", "record", "list", "get"]),
|
|
56
|
+
evaluate: new Set(["parse", "record", "list", "get"]),
|
|
57
|
+
feedback: new Set(["record", "list", "summary", "get"]),
|
|
58
|
+
hypothesis: new Set(["create", "list", "update"]),
|
|
59
|
+
safety: new Set(["check", "request", "list", "get", "approve", "deny", "pending"]),
|
|
60
|
+
initiative: new Set(["create", "list", "get", "update"]),
|
|
61
|
+
loop: new Set(["tick", "emit", "classify", "recent", "snapshot", "list"]),
|
|
62
|
+
merge: new Set(["enqueue", "list", "get", "mark"]),
|
|
63
|
+
plan: new Set(["parse", "apply"]),
|
|
64
|
+
review: new Set(["record", "list", "get"]),
|
|
65
|
+
route: new Set(["set", "list", "delete", "remove", "emit"]),
|
|
66
|
+
task: new Set(["create", "list", "get", "update", "done"])
|
|
67
|
+
};
|
|
68
|
+
export function extractCommands(content) {
|
|
69
|
+
const commands = [];
|
|
70
|
+
const regex = /\bking-ai\s+([a-z][\w:-]*)(?:\s+([a-z][\w:-]*))?/g;
|
|
71
|
+
let match;
|
|
72
|
+
while ((match = regex.exec(content)) !== null) {
|
|
73
|
+
const topLevel = match[1];
|
|
74
|
+
const subcommand = match[2];
|
|
75
|
+
if (!topLevel)
|
|
76
|
+
continue;
|
|
77
|
+
if (subcommand && KNOWN_SUBCOMMANDS[topLevel]) {
|
|
78
|
+
commands.push(`${topLevel} ${subcommand}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
commands.push(topLevel);
|
|
82
|
+
}
|
|
83
|
+
return [...new Set(commands)];
|
|
84
|
+
}
|
|
85
|
+
export function validateCommand(commandReference) {
|
|
86
|
+
const [topLevel, subcommand] = commandReference.split(" ");
|
|
87
|
+
if (!topLevel || !KNOWN_COMMANDS.has(topLevel))
|
|
88
|
+
return false;
|
|
89
|
+
if (!subcommand)
|
|
90
|
+
return true;
|
|
91
|
+
return KNOWN_SUBCOMMANDS[topLevel]?.has(subcommand) ?? true;
|
|
92
|
+
}
|
|
93
|
+
export function checkSkill(filePath) {
|
|
94
|
+
const content = readFileSync(filePath, "utf8");
|
|
95
|
+
const referencedCommands = extractCommands(content);
|
|
96
|
+
const invalidCommands = referencedCommands.filter((commandReference) => !validateCommand(commandReference));
|
|
97
|
+
const warnings = [];
|
|
98
|
+
if (referencedCommands.length === 0)
|
|
99
|
+
warnings.push("No king-ai CLI commands referenced");
|
|
100
|
+
return {
|
|
101
|
+
skillName: basename(dirname(filePath)),
|
|
102
|
+
filePath,
|
|
103
|
+
referencedCommands,
|
|
104
|
+
invalidCommands,
|
|
105
|
+
warnings,
|
|
106
|
+
valid: invalidCommands.length === 0
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function findSkillFiles(skillsDir) {
|
|
110
|
+
if (!existsSync(skillsDir))
|
|
111
|
+
return [];
|
|
112
|
+
const files = [];
|
|
113
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
114
|
+
const entryPath = join(skillsDir, entry.name);
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
const skillFile = join(entryPath, "SKILL.md");
|
|
117
|
+
if (existsSync(skillFile))
|
|
118
|
+
files.push(skillFile);
|
|
119
|
+
files.push(...findSkillFiles(entryPath));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [...new Set(files)].sort();
|
|
123
|
+
}
|
|
124
|
+
export function checkAllSkills(skillsDir) {
|
|
125
|
+
return findSkillFiles(skillsDir).map((filePath) => checkSkill(filePath));
|
|
126
|
+
}
|
|
127
|
+
export function formatDashboard(results, commandName = "king-ai") {
|
|
128
|
+
const lines = [
|
|
129
|
+
`${commandName} skill-check - Skill command reference health`,
|
|
130
|
+
"=".repeat(56)
|
|
131
|
+
];
|
|
132
|
+
let passed = 0;
|
|
133
|
+
let failed = 0;
|
|
134
|
+
for (const result of results) {
|
|
135
|
+
if (result.valid)
|
|
136
|
+
passed++;
|
|
137
|
+
else
|
|
138
|
+
failed++;
|
|
139
|
+
lines.push(`${result.valid ? "[ok]" : "[fail]"} ${result.skillName}`);
|
|
140
|
+
lines.push(` Commands: ${result.referencedCommands.length ? result.referencedCommands.join(", ") : "none"}`);
|
|
141
|
+
if (result.invalidCommands.length)
|
|
142
|
+
lines.push(` Invalid: ${result.invalidCommands.join(", ")}`);
|
|
143
|
+
for (const warning of result.warnings)
|
|
144
|
+
lines.push(` [warn] ${warning}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push("-".repeat(56));
|
|
147
|
+
lines.push(`Total: ${results.length} skills, ${passed} passed, ${failed} failed`);
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
export function runSkillCheck(skillsDir, commandName = "king-ai") {
|
|
151
|
+
if (!existsSync(skillsDir)) {
|
|
152
|
+
throw new Error(`Skills directory not found: ${skillsDir}`);
|
|
153
|
+
}
|
|
154
|
+
const results = checkAllSkills(skillsDir);
|
|
155
|
+
console.log(formatDashboard(results, commandName));
|
|
156
|
+
if (results.some((result) => !result.valid))
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SseEvent {
|
|
2
|
+
event?: string;
|
|
3
|
+
data?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_SSE_MAX_BUFFER_BYTES: number;
|
|
7
|
+
export declare function parseSseStream(body: AsyncIterable<Uint8Array | string>, options?: {
|
|
8
|
+
maxBufferBytes?: number;
|
|
9
|
+
}): AsyncGenerator<SseEvent>;
|
package/dist/src/sse.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const DEFAULT_SSE_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
2
|
+
export async function* parseSseStream(body, options = {}) {
|
|
3
|
+
const decoder = new TextDecoder("utf-8");
|
|
4
|
+
const maxBufferBytes = options.maxBufferBytes ?? DEFAULT_SSE_MAX_BUFFER_BYTES;
|
|
5
|
+
let buf = "";
|
|
6
|
+
for await (const chunk of body) {
|
|
7
|
+
buf += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
8
|
+
if (Buffer.byteLength(buf, "utf8") > maxBufferBytes) {
|
|
9
|
+
throw new Error(`SSE frame exceeded ${maxBufferBytes} bytes without a terminator`);
|
|
10
|
+
}
|
|
11
|
+
let splitAt;
|
|
12
|
+
while ((splitAt = buf.indexOf("\n\n")) >= 0) {
|
|
13
|
+
const block = buf.slice(0, splitAt);
|
|
14
|
+
buf = buf.slice(splitAt + 2);
|
|
15
|
+
const event = {};
|
|
16
|
+
for (const rawLine of block.split("\n")) {
|
|
17
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
18
|
+
if (!line || line.startsWith(":"))
|
|
19
|
+
continue;
|
|
20
|
+
const colon = line.indexOf(":");
|
|
21
|
+
if (colon < 0)
|
|
22
|
+
continue;
|
|
23
|
+
const field = line.slice(0, colon);
|
|
24
|
+
const value = line.slice(colon + 1).replace(/^ /, "");
|
|
25
|
+
if (field === "event")
|
|
26
|
+
event.event = value;
|
|
27
|
+
if (field === "id")
|
|
28
|
+
event.id = value;
|
|
29
|
+
if (field === "data")
|
|
30
|
+
event.data = (event.data ?? "") + value;
|
|
31
|
+
}
|
|
32
|
+
if (event.event || event.data || event.id)
|
|
33
|
+
yield event;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { KingHandoffPolicy, KingTeamSpec, KingWorkflowObjectType } from "./team-workflow.js";
|
|
2
|
+
/**
|
|
3
|
+
* The minimal shape of a workflow card the router reasons about. Kept structural (not coupled to
|
|
4
|
+
* the ledger record type) so the engine stays a pure function of team policy + card state.
|
|
5
|
+
*/
|
|
6
|
+
export interface RoutableCard {
|
|
7
|
+
id: string;
|
|
8
|
+
kind: KingWorkflowObjectType;
|
|
9
|
+
status: string;
|
|
10
|
+
ownerRole?: string;
|
|
11
|
+
reviewerRole?: string;
|
|
12
|
+
handoffPolicy?: KingHandoffPolicy;
|
|
13
|
+
sourceId?: string;
|
|
14
|
+
sourceOwnerRole?: string;
|
|
15
|
+
result?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface HandoffCard {
|
|
18
|
+
kind: KingWorkflowObjectType;
|
|
19
|
+
title: string;
|
|
20
|
+
status: string;
|
|
21
|
+
ownerRole?: string;
|
|
22
|
+
reviewerRole?: string;
|
|
23
|
+
targetRole?: string;
|
|
24
|
+
sourceId: string;
|
|
25
|
+
dependsOn: string[];
|
|
26
|
+
decisionBy?: string;
|
|
27
|
+
detail: string;
|
|
28
|
+
handoffPolicy?: KingHandoffPolicy;
|
|
29
|
+
}
|
|
30
|
+
export interface HandoffAction {
|
|
31
|
+
reason: "review" | "human-escalation" | "next-role";
|
|
32
|
+
card: HandoffCard;
|
|
33
|
+
}
|
|
34
|
+
export type ReviewVerdict = "approved" | "changes_requested";
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the handoff policy that governs a card: an explicit policy on the card wins, otherwise the
|
|
37
|
+
* owning role's policy from the team spec.
|
|
38
|
+
*/
|
|
39
|
+
export declare function roleHandoffPolicy(team: KingTeamSpec, roleId: string | undefined): KingHandoffPolicy | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Capability-first owner selection: pick the role whose declared capabilities best overlap the work's
|
|
42
|
+
* required capabilities. Returns undefined when routing is not capability-first or nothing matches,
|
|
43
|
+
* leaving the caller to fall back to an explicit owner.
|
|
44
|
+
*/
|
|
45
|
+
export declare function selectOwnerRole(team: KingTeamSpec, requiredCapabilities: string[]): string | undefined;
|
|
46
|
+
export declare function normalizeReviewVerdict(result?: string): ReviewVerdict | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Given a card that has just reached `done`, compute the follow-up workflow cards the team policy
|
|
49
|
+
* requires. This is what turns routingPolicy/handoffPolicy from declared data into an executed
|
|
50
|
+
* workflow: completing a builder card routes to its reviewer, an ops/human-gated card escalates to a
|
|
51
|
+
* decision, and a card with a `nextRole` hands off down the chain.
|
|
52
|
+
*
|
|
53
|
+
* Returns an empty list when no routing applies (not done, a decision being resolved, or no policy).
|
|
54
|
+
*/
|
|
55
|
+
export declare function planHandoff(card: RoutableCard, team: KingTeamSpec): HandoffAction[];
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the handoff policy that governs a card: an explicit policy on the card wins, otherwise the
|
|
3
|
+
* owning role's policy from the team spec.
|
|
4
|
+
*/
|
|
5
|
+
export function roleHandoffPolicy(team, roleId) {
|
|
6
|
+
if (!roleId)
|
|
7
|
+
return undefined;
|
|
8
|
+
const role = team.roles.find((entry) => entry.id === roleId) ?? team.roles.find((entry) => entry.template === roleId);
|
|
9
|
+
return role?.handoffPolicy;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Capability-first owner selection: pick the role whose declared capabilities best overlap the work's
|
|
13
|
+
* required capabilities. Returns undefined when routing is not capability-first or nothing matches,
|
|
14
|
+
* leaving the caller to fall back to an explicit owner.
|
|
15
|
+
*/
|
|
16
|
+
export function selectOwnerRole(team, requiredCapabilities) {
|
|
17
|
+
if (!team.routingPolicy.capabilityFirst || requiredCapabilities.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
const required = new Set(requiredCapabilities);
|
|
20
|
+
let best;
|
|
21
|
+
let bestScore = 0;
|
|
22
|
+
for (const role of team.roles) {
|
|
23
|
+
const score = role.capabilities.reduce((count, capability) => (required.has(capability) ? count + 1 : count), 0);
|
|
24
|
+
if (score > bestScore) {
|
|
25
|
+
bestScore = score;
|
|
26
|
+
best = role.id;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return best;
|
|
30
|
+
}
|
|
31
|
+
export function normalizeReviewVerdict(result) {
|
|
32
|
+
const value = result?.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
33
|
+
if (!value)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (value === "approved" || value === "approve" || value === "accepted" || value === "pass" || value === "passed")
|
|
36
|
+
return "approved";
|
|
37
|
+
if (value === "changes_requested" || value === "change_requested" || value === "rejected" || value === "needs_work" || value === "fail" || value === "failed")
|
|
38
|
+
return "changes_requested";
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Given a card that has just reached `done`, compute the follow-up workflow cards the team policy
|
|
43
|
+
* requires. This is what turns routingPolicy/handoffPolicy from declared data into an executed
|
|
44
|
+
* workflow: completing a builder card routes to its reviewer, an ops/human-gated card escalates to a
|
|
45
|
+
* decision, and a card with a `nextRole` hands off down the chain.
|
|
46
|
+
*
|
|
47
|
+
* Returns an empty list when no routing applies (not done, a decision being resolved, or no policy).
|
|
48
|
+
*/
|
|
49
|
+
export function planHandoff(card, team) {
|
|
50
|
+
if (card.status !== "done")
|
|
51
|
+
return [];
|
|
52
|
+
// Resolving a decision must never spawn another decision/review, or the gate would never close.
|
|
53
|
+
if (card.kind === "decision")
|
|
54
|
+
return [];
|
|
55
|
+
const verdict = card.kind === "review" ? normalizeReviewVerdict(card.result) : undefined;
|
|
56
|
+
if (verdict === "changes_requested" && card.sourceId && card.sourceOwnerRole) {
|
|
57
|
+
return [{
|
|
58
|
+
reason: "next-role",
|
|
59
|
+
card: {
|
|
60
|
+
kind: "handoff",
|
|
61
|
+
title: `Changes requested for ${card.sourceId}`,
|
|
62
|
+
status: "assigned",
|
|
63
|
+
ownerRole: card.sourceOwnerRole,
|
|
64
|
+
targetRole: card.sourceOwnerRole,
|
|
65
|
+
sourceId: card.id,
|
|
66
|
+
dependsOn: [card.id],
|
|
67
|
+
detail: `Review ${card.id} requested changes on ${card.sourceId}; route back to ${card.sourceOwnerRole}.`
|
|
68
|
+
}
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
const policy = card.handoffPolicy ?? roleHandoffPolicy(team, card.ownerRole);
|
|
72
|
+
if (!policy)
|
|
73
|
+
return [];
|
|
74
|
+
const reviewer = card.reviewerRole ?? policy.reviewerRole;
|
|
75
|
+
const owner = card.ownerRole ?? "owner";
|
|
76
|
+
if (policy.acceptanceRequired && reviewer && card.kind !== "review") {
|
|
77
|
+
return [{
|
|
78
|
+
reason: "review",
|
|
79
|
+
card: {
|
|
80
|
+
kind: "review",
|
|
81
|
+
title: `Review ${card.id}`,
|
|
82
|
+
status: "review",
|
|
83
|
+
ownerRole: reviewer,
|
|
84
|
+
reviewerRole: reviewer,
|
|
85
|
+
targetRole: reviewer,
|
|
86
|
+
sourceId: card.id,
|
|
87
|
+
dependsOn: [card.id],
|
|
88
|
+
detail: `Auto-routed for review by ${reviewer} after ${owner} completed ${card.id}.`,
|
|
89
|
+
// Carry the onward policy so completing the review continues the chain without re-triggering
|
|
90
|
+
// another review.
|
|
91
|
+
handoffPolicy: {
|
|
92
|
+
mode: policy.mode,
|
|
93
|
+
nextRole: policy.nextRole,
|
|
94
|
+
escalation: policy.escalation,
|
|
95
|
+
acceptanceRequired: false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}];
|
|
99
|
+
}
|
|
100
|
+
if (policy.escalation === "human" || policy.mode === "human-decision") {
|
|
101
|
+
return [{
|
|
102
|
+
reason: "human-escalation",
|
|
103
|
+
card: {
|
|
104
|
+
kind: "decision",
|
|
105
|
+
title: `Decision after ${card.id}`,
|
|
106
|
+
status: "waiting_human",
|
|
107
|
+
ownerRole: card.ownerRole,
|
|
108
|
+
sourceId: card.id,
|
|
109
|
+
dependsOn: [card.id],
|
|
110
|
+
decisionBy: "human",
|
|
111
|
+
detail: `Auto-routed: ${owner} completed ${card.id}; a human decision is required before continuing.`
|
|
112
|
+
}
|
|
113
|
+
}];
|
|
114
|
+
}
|
|
115
|
+
if (policy.nextRole) {
|
|
116
|
+
return [{
|
|
117
|
+
reason: "next-role",
|
|
118
|
+
card: {
|
|
119
|
+
kind: "handoff",
|
|
120
|
+
title: `Handoff to ${policy.nextRole} after ${card.id}`,
|
|
121
|
+
status: "assigned",
|
|
122
|
+
ownerRole: policy.nextRole,
|
|
123
|
+
targetRole: policy.nextRole,
|
|
124
|
+
sourceId: card.id,
|
|
125
|
+
dependsOn: [card.id],
|
|
126
|
+
detail: `Auto-routed from ${owner} to ${policy.nextRole} after ${card.id}.`
|
|
127
|
+
}
|
|
128
|
+
}];
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type KingRoleTemplateId = "planner" | "builder" | "reviewer" | "tester" | "ops" | "researcher" | "doc-writer" | "summarizer";
|
|
2
|
+
export type KingRoutingMode = "one-of-us" | "each" | "review-required" | "human-decision";
|
|
3
|
+
export type KingWorkflowObjectType = "initiative" | "task" | "handoff" | "review" | "decision" | "artifact";
|
|
4
|
+
export type KingTeamPermissionAction = "assign-task" | "claim-task" | "create-artifact" | "create-decision" | "approve-decision" | "close-task" | "change-scope" | "deploy-release" | "view-audit" | "manage-queue" | "view-cost";
|
|
5
|
+
export interface KingRoleTemplate {
|
|
6
|
+
id: KingRoleTemplateId;
|
|
7
|
+
name: string;
|
|
8
|
+
responsibility: string;
|
|
9
|
+
defaultRoutingMode: KingRoutingMode;
|
|
10
|
+
capabilityHints: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface KingTeamRole {
|
|
13
|
+
id: string;
|
|
14
|
+
template: KingRoleTemplateId;
|
|
15
|
+
responsibility: string;
|
|
16
|
+
capabilities: string[];
|
|
17
|
+
handoffPolicy: KingHandoffPolicy;
|
|
18
|
+
}
|
|
19
|
+
export interface KingHandoffPolicy {
|
|
20
|
+
mode: KingRoutingMode;
|
|
21
|
+
nextRole?: string;
|
|
22
|
+
reviewerRole?: string;
|
|
23
|
+
escalation?: "human" | "coordinator" | "none";
|
|
24
|
+
acceptanceRequired: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface KingTeamSpec {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
roles: KingTeamRole[];
|
|
30
|
+
routingPolicy: KingRoutingPolicy;
|
|
31
|
+
permissionPolicy: KingTeamPermissionPolicy;
|
|
32
|
+
}
|
|
33
|
+
export interface KingRoutingPolicy {
|
|
34
|
+
defaultMode: KingRoutingMode;
|
|
35
|
+
capabilityFirst: boolean;
|
|
36
|
+
reviewRequiredFor: string[];
|
|
37
|
+
humanDecisionFor: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface KingTeamPermissionRule {
|
|
40
|
+
role: string;
|
|
41
|
+
allow: KingTeamPermissionAction[];
|
|
42
|
+
requireHumanDecision?: KingTeamPermissionAction[];
|
|
43
|
+
requireReviewBy?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface KingTeamPermissionPolicy {
|
|
46
|
+
defaultDecision: "deny";
|
|
47
|
+
rules: KingTeamPermissionRule[];
|
|
48
|
+
}
|
|
49
|
+
export interface KingScenarioTemplate {
|
|
50
|
+
id: "repo-takeover" | "bug-investigation" | "product-design" | "release-check" | "research-brief";
|
|
51
|
+
name: string;
|
|
52
|
+
goal: string;
|
|
53
|
+
team: KingTeamSpec;
|
|
54
|
+
acceptance: string[];
|
|
55
|
+
tasks: Array<{
|
|
56
|
+
title: string;
|
|
57
|
+
ownerRole: string;
|
|
58
|
+
reviewerRole?: string;
|
|
59
|
+
dependsOn?: string[];
|
|
60
|
+
acceptance: string[];
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export interface KingAgentRoleLike {
|
|
64
|
+
id?: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
role?: string;
|
|
67
|
+
}
|
|
68
|
+
export declare const KING_AI_ROLE_TEMPLATES: KingRoleTemplate[];
|
|
69
|
+
export declare function defaultTeamSpec(id?: string, name?: string): KingTeamSpec;
|
|
70
|
+
export declare function checkTeamPermission(team: KingTeamSpec, role: string, action: KingTeamPermissionAction): {
|
|
71
|
+
decision: "allow" | "deny" | "human-decision";
|
|
72
|
+
rule?: KingTeamPermissionRule;
|
|
73
|
+
};
|
|
74
|
+
export declare function normalizeTeamRoleId(role: string): string;
|
|
75
|
+
export declare function roleTemplateForAgent(agent: KingAgentRoleLike): KingRoleTemplateId;
|
|
76
|
+
export declare function requiredCapabilitiesForText(text: string): string[];
|
|
77
|
+
export declare function roleTemplateById(id: KingRoleTemplateId): KingRoleTemplate;
|
|
78
|
+
export declare function scenarioTemplate(id: KingScenarioTemplate["id"]): KingScenarioTemplate;
|