open-multi-agent-kit 0.90.3 → 0.90.5
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/CHANGELOG.md +38 -0
- package/README.md +16 -14
- package/dist/config.d.ts +3 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -19
- package/dist/config.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +250 -0
- package/dist/core/adaptorch-bridge.d.ts.map +1 -0
- package/dist/core/adaptorch-bridge.js +466 -0
- package/dist/core/adaptorch-bridge.js.map +1 -0
- package/dist/core/agent-session.d.ts +97 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +194 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bang-skill-invocation.d.ts.map +1 -1
- package/dist/core/bang-skill-invocation.js +149 -0
- package/dist/core/bang-skill-invocation.js.map +1 -1
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +15 -2
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +10 -2
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +7 -1
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +1 -1
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/reasoning-router-bias.d.ts +107 -0
- package/dist/core/reasoning-router-bias.d.ts.map +1 -0
- package/dist/core/reasoning-router-bias.js +278 -0
- package/dist/core/reasoning-router-bias.js.map +1 -0
- package/dist/core/reasoning-router-resolver.d.ts +44 -0
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -0
- package/dist/core/reasoning-router-resolver.js +79 -0
- package/dist/core/reasoning-router-resolver.js.map +1 -0
- package/dist/core/reasoning-router-v4-weights.d.ts +80 -0
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -0
- package/dist/core/reasoning-router-v4-weights.js +68 -0
- package/dist/core/reasoning-router-v4-weights.js.map +1 -0
- package/dist/core/reasoning-router-v4.d.ts +118 -0
- package/dist/core/reasoning-router-v4.d.ts.map +1 -0
- package/dist/core/reasoning-router-v4.js +585 -0
- package/dist/core/reasoning-router-v4.js.map +1 -0
- package/dist/core/router-feedback-collector.d.ts +98 -0
- package/dist/core/router-feedback-collector.d.ts.map +1 -0
- package/dist/core/router-feedback-collector.js +236 -0
- package/dist/core/router-feedback-collector.js.map +1 -0
- package/dist/core/settings-manager.d.ts +28 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +16 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-runtime-status.d.ts +15 -0
- package/dist/modes/interactive/components/control-panel-runtime-status.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-runtime-status.js +95 -5
- package/dist/modes/interactive/components/control-panel-runtime-status.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +60 -17
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/loadout-domains/README.md +8 -6
- package/docs/loadout-domains/ai-agent-ops.md +3 -2
- package/docs/loadout-domains/backend-api.md +3 -2
- package/docs/loadout-domains/data-science.md +3 -2
- package/docs/loadout-domains/devops-infra.md +2 -1
- package/docs/loadout-domains/docs-writing.md +7 -2
- package/docs/loadout-domains/frontend-ui.md +42 -11
- package/docs/loadout-domains/korean-document.md +83 -0
- package/docs/loadout-domains/mobile.md +1 -1
- package/docs/loadout-domains/qa-testing.md +4 -2
- package/docs/loadout-domains/security-audit.md +2 -1
- package/docs/loadout-domains/visual-qa.md +94 -0
- package/docs/skills.md +4 -1
- package/docs/usage.md +36 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +123 -0
- package/examples/extensions/subagent/agent-capability-router.ts +274 -0
- package/examples/extensions/subagent/agents/planner.md +3 -0
- package/examples/extensions/subagent/agents/reviewer.md +3 -0
- package/examples/extensions/subagent/agents/scout.md +3 -0
- package/examples/extensions/subagent/agents/worker.md +2 -0
- package/examples/extensions/subagent/agents.ts +14 -0
- package/examples/extensions/subagent/capabilities.ts +368 -0
- package/examples/extensions/subagent/domain-profiles.ts +1089 -0
- package/examples/extensions/subagent/index.ts +71 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/dist/agents/jailbreak/index.d.ts +0 -7
- package/dist/agents/jailbreak/index.d.ts.map +0 -1
- package/dist/agents/jailbreak/index.js +0 -7
- package/dist/agents/jailbreak/index.js.map +0 -1
- package/dist/agents/jailbreak/subagent-f.d.ts +0 -44
- package/dist/agents/jailbreak/subagent-f.d.ts.map +0 -1
- package/dist/agents/jailbreak/subagent-f.js +0 -60
- package/dist/agents/jailbreak/subagent-f.js.map +0 -1
- package/dist/cli/jailbreak-args.d.ts +0 -26
- package/dist/cli/jailbreak-args.d.ts.map +0 -1
- package/dist/cli/jailbreak-args.js +0 -86
- package/dist/cli/jailbreak-args.js.map +0 -1
- package/dist/commands/jailbreak.d.ts +0 -49
- package/dist/commands/jailbreak.d.ts.map +0 -1
- package/dist/commands/jailbreak.js +0 -201
- package/dist/commands/jailbreak.js.map +0 -1
- package/dist/core/compactor.d.ts +0 -42
- package/dist/core/compactor.d.ts.map +0 -1
- package/dist/core/compactor.js +0 -128
- package/dist/core/compactor.js.map +0 -1
- package/dist/core/token-optimizer.d.ts +0 -58
- package/dist/core/token-optimizer.d.ts.map +0 -1
- package/dist/core/token-optimizer.js +0 -179
- package/dist/core/token-optimizer.js.map +0 -1
- package/dist/encoding/index.d.ts +0 -6
- package/dist/encoding/index.d.ts.map +0 -1
- package/dist/encoding/index.js +0 -6
- package/dist/encoding/index.js.map +0 -1
- package/dist/encoding/l4-encoder.d.ts +0 -96
- package/dist/encoding/l4-encoder.d.ts.map +0 -1
- package/dist/encoding/l4-encoder.js +0 -474
- package/dist/encoding/l4-encoder.js.map +0 -1
- package/dist/fuzzing/index.d.ts +0 -6
- package/dist/fuzzing/index.d.ts.map +0 -1
- package/dist/fuzzing/index.js +0 -6
- package/dist/fuzzing/index.js.map +0 -1
- package/dist/fuzzing/mutation-engine.d.ts +0 -93
- package/dist/fuzzing/mutation-engine.d.ts.map +0 -1
- package/dist/fuzzing/mutation-engine.js +0 -244
- package/dist/fuzzing/mutation-engine.js.map +0 -1
- package/dist/jailbreak-extension.d.ts +0 -48
- package/dist/jailbreak-extension.d.ts.map +0 -1
- package/dist/jailbreak-extension.js +0 -47
- package/dist/jailbreak-extension.js.map +0 -1
- package/dist/modules/index.d.ts +0 -8
- package/dist/modules/index.d.ts.map +0 -1
- package/dist/modules/index.js +0 -7
- package/dist/modules/index.js.map +0 -1
- package/dist/modules/lrl-bypass.d.ts +0 -40
- package/dist/modules/lrl-bypass.d.ts.map +0 -1
- package/dist/modules/lrl-bypass.js +0 -84
- package/dist/modules/lrl-bypass.js.map +0 -1
- package/dist/modules/lrl-deps.d.ts +0 -38
- package/dist/modules/lrl-deps.d.ts.map +0 -1
- package/dist/modules/lrl-deps.js +0 -128
- package/dist/modules/lrl-deps.js.map +0 -1
- package/dist/multiturn/easl-chain.d.ts +0 -113
- package/dist/multiturn/easl-chain.d.ts.map +0 -1
- package/dist/multiturn/easl-chain.js +0 -351
- package/dist/multiturn/easl-chain.js.map +0 -1
- package/dist/multiturn/index.d.ts +0 -6
- package/dist/multiturn/index.d.ts.map +0 -1
- package/dist/multiturn/index.js +0 -6
- package/dist/multiturn/index.js.map +0 -1
- package/dist/routing/fallback-router.d.ts +0 -123
- package/dist/routing/fallback-router.d.ts.map +0 -1
- package/dist/routing/fallback-router.js +0 -355
- package/dist/routing/fallback-router.js.map +0 -1
- package/dist/routing/index.d.ts +0 -6
- package/dist/routing/index.d.ts.map +0 -1
- package/dist/routing/index.js +0 -6
- package/dist/routing/index.js.map +0 -1
- package/dist/types/jailbreak.d.ts +0 -246
- package/dist/types/jailbreak.d.ts.map +0 -1
- package/dist/types/jailbreak.js +0 -8
- package/dist/types/jailbreak.js.map +0 -1
- package/dist/utils/jailbreak/common.d.ts +0 -63
- package/dist/utils/jailbreak/common.d.ts.map +0 -1
- package/dist/utils/jailbreak/common.js +0 -130
- package/dist/utils/jailbreak/common.js.map +0 -1
- package/dist/utils/jailbreak/index.d.ts +0 -7
- package/dist/utils/jailbreak/index.d.ts.map +0 -1
- package/dist/utils/jailbreak/index.js +0 -7
- package/dist/utils/jailbreak/index.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-sandbox",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-sandbox",
|
|
9
|
-
"version": "0.90.
|
|
9
|
+
"version": "0.90.5",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "0.0.26"
|
|
12
12
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic unit tests for the agent → capability router.
|
|
3
|
+
*
|
|
4
|
+
* The router is fully deterministic (no LLM, no randomness, no I/O inside the
|
|
5
|
+
* router itself), so every assertion here is an exact expected value. The only
|
|
6
|
+
* I/O is the one-time module-scope catalog build, which scans the live skill
|
|
7
|
+
* tree under the agent directory.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { auditCapabilities, classifyAgent, deriveCapabilities } from "./agent-capability-router.ts";
|
|
11
|
+
import { buildCapabilityCatalog } from "./capabilities.ts";
|
|
12
|
+
|
|
13
|
+
// Built once at module scope from the live skill tree under the agent
|
|
14
|
+
// directory. This is populated during local verification but empty in CI (the
|
|
15
|
+
// agent skill catalog is not checked out on the runner), so the skill-catalog
|
|
16
|
+
// -dependent assertions below are guarded with `skipIf(!hasSkillCatalog)`. The
|
|
17
|
+
// MCP/hooks sets are a fixed OMK list and stay populated everywhere.
|
|
18
|
+
const catalog = buildCapabilityCatalog({ agentDir: `${process.env.HOME ?? "/home/yu"}/.omk/agent` });
|
|
19
|
+
const hasSkillCatalog = catalog.skills.size > 0;
|
|
20
|
+
|
|
21
|
+
describe("classifyAgent", () => {
|
|
22
|
+
it("classifies by name token: seo-specialist → marketing-content", () => {
|
|
23
|
+
expect(classifyAgent("seo-specialist", "x")?.id).toBe("marketing-content");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("classifies smart-contract-auditor → security", () => {
|
|
27
|
+
expect(classifyAgent("smart-contract-auditor", "x")?.id).toBe("security");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("classifies react-developer → frontend-web", () => {
|
|
31
|
+
expect(classifyAgent("react-developer", "x")?.id).toBe("frontend-web");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("guards the gin→engineer substring regression: ml-engineer is NOT backend-api", () => {
|
|
35
|
+
// "engineer" contains "gin"; a naive first-match classifier would route
|
|
36
|
+
// this to backend-api. Boundary-anchored voting must not.
|
|
37
|
+
expect(classifyAgent("ml-engineer", "AI/ML")?.id).not.toBe("backend-api");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("guards the gis→strategist substring regression: account-strategist is NOT gis-spatial", () => {
|
|
41
|
+
// "strategist" contains "gis"; must not flip into the GIS domain.
|
|
42
|
+
expect(classifyAgent("account-strategist", "x")?.id).not.toBe("gis-spatial");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns null when no domain matches", () => {
|
|
46
|
+
expect(classifyAgent("zzz-nonexistent-xyz", "nothing relevant here")).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("deriveCapabilities", () => {
|
|
51
|
+
it.skipIf(!hasSkillCatalog)(
|
|
52
|
+
"weights the name token: react-developer's first derived skill is react-patterns",
|
|
53
|
+
() => {
|
|
54
|
+
const { skills } = deriveCapabilities("react-developer", "React frontend", catalog);
|
|
55
|
+
expect(skills[0]).toBe("react-patterns");
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
it.skipIf(!hasSkillCatalog)("includes the literal domain skill for seo-specialist", () => {
|
|
60
|
+
const { skills } = deriveCapabilities("seo-specialist", "SEO", catalog);
|
|
61
|
+
expect(skills).toContain("seo");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns empty skills for an unmatched domain", () => {
|
|
65
|
+
const { skills } = deriveCapabilities("zzz-unknown", "x", catalog);
|
|
66
|
+
expect(skills.length).toBe(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it.skipIf(!hasSkillCatalog)("only emits skills that exist in the live catalog", () => {
|
|
70
|
+
const { skills } = deriveCapabilities("react-developer", "React frontend", catalog);
|
|
71
|
+
expect(skills.length).toBeGreaterThan(0);
|
|
72
|
+
for (const skill of skills) {
|
|
73
|
+
expect(catalog.skills.has(skill)).toBe(true);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("derived MCP is a subset of the catalog MCP set", () => {
|
|
78
|
+
const { mcp } = deriveCapabilities("devops-automator", "kubernetes", catalog);
|
|
79
|
+
expect(mcp.length).toBeGreaterThan(0);
|
|
80
|
+
for (const server of mcp) {
|
|
81
|
+
expect(catalog.mcp.has(server)).toBe(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("auditCapabilities", () => {
|
|
87
|
+
it("identical sets → jaccard 1.0 and verdict match", () => {
|
|
88
|
+
const caps = { skills: ["react-patterns"], mcp: [], hooks: [] };
|
|
89
|
+
const result = auditCapabilities(caps, caps, catalog);
|
|
90
|
+
expect(result.jaccard).toBe(1.0);
|
|
91
|
+
expect(result.verdict).toBe("match");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("disjoint sets → jaccard 0 and verdict divergent", () => {
|
|
95
|
+
const declared = { skills: ["react-patterns"], mcp: [], hooks: [] };
|
|
96
|
+
const derived = { skills: ["seo"], mcp: [], hooks: [] };
|
|
97
|
+
const result = auditCapabilities(declared, derived, catalog);
|
|
98
|
+
expect(result.jaccard).toBe(0);
|
|
99
|
+
expect(result.verdict).toBe("divergent");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("half overlap → jaccard within [0,1] and verdict drift or match", () => {
|
|
103
|
+
const declared = { skills: ["react-patterns", "seo"], mcp: [], hooks: [] };
|
|
104
|
+
const derived = { skills: ["react-patterns"], mcp: [], hooks: [] };
|
|
105
|
+
const result = auditCapabilities(declared, derived, catalog);
|
|
106
|
+
expect(result.jaccard).toBeGreaterThanOrEqual(0);
|
|
107
|
+
expect(result.jaccard).toBeLessThanOrEqual(1);
|
|
108
|
+
expect(["drift", "match"]).toContain(result.verdict);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("surfaces declared-but-unknown skills in declaredUnknownSkills", () => {
|
|
112
|
+
const declared = { skills: ["fake-skill-xyz"], mcp: [], hooks: [] };
|
|
113
|
+
const derived = { skills: [], mcp: [], hooks: [] };
|
|
114
|
+
const result = auditCapabilities(declared, derived, catalog);
|
|
115
|
+
expect(result.declaredUnknownSkills).toContain("fake-skill-xyz");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("both-empty skills → jaccard 1.0", () => {
|
|
119
|
+
const empty = { skills: [], mcp: [], hooks: [] };
|
|
120
|
+
const result = auditCapabilities(empty, empty, catalog);
|
|
121
|
+
expect(result.jaccard).toBe(1.0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic agent → capability router.
|
|
3
|
+
*
|
|
4
|
+
* Classifies an agent (by name + description) into a {@link DomainProfile},
|
|
5
|
+
* derives a baseline capability set (skills / MCP / hooks) from the live
|
|
6
|
+
* {@link CapabilityCatalog}, and audits a declared capability set against the
|
|
7
|
+
* deterministic derivation (Jaccard overlap + drift verdict).
|
|
8
|
+
*
|
|
9
|
+
* Fully deterministic: same inputs always yield same outputs. No LLM calls,
|
|
10
|
+
* no randomness, no I/O. Depends only on {@link DOMAIN_PROFILES} and the
|
|
11
|
+
* passed-in catalog.
|
|
12
|
+
*
|
|
13
|
+
* Classification is **name-weighted vote scoring**: a domain's keywords are
|
|
14
|
+
* tested against the agent name and description; matches inside the name (the
|
|
15
|
+
* strongest identity signal) count triple. The domain with the highest score
|
|
16
|
+
* wins (ties resolved by array order, so specific domains should come first).
|
|
17
|
+
* This is robust to the substring false positives that plague first-match
|
|
18
|
+
* classification (e.g. `chem` matching "s**chem**a", `gis` matching
|
|
19
|
+
* "strate**gis**t"): a single accidental description hit cannot outvote a
|
|
20
|
+
* domain that matches the name.
|
|
21
|
+
*
|
|
22
|
+
* @module agent-capability-router
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { AgentCapabilities, CapabilityCatalog } from "./capabilities.ts";
|
|
26
|
+
import { validateCapabilities } from "./capabilities.ts";
|
|
27
|
+
import type { DomainProfile } from "./domain-profiles.ts";
|
|
28
|
+
import { DOMAIN_PROFILES } from "./domain-profiles.ts";
|
|
29
|
+
|
|
30
|
+
/** Maximum number of scored skills to grant. */
|
|
31
|
+
const MAX_SKILLS = 6;
|
|
32
|
+
/** Number of pool skills granted as a fallback when nothing scores above zero. */
|
|
33
|
+
const FALLBACK_SKILLS = 4;
|
|
34
|
+
/** Jaccard threshold (inclusive) for a "match" verdict. */
|
|
35
|
+
const JACCARD_MATCH = 0.5;
|
|
36
|
+
/** Jaccard threshold (inclusive) for a "drift" verdict. */
|
|
37
|
+
const JACCARD_DRIFT = 0.15;
|
|
38
|
+
/** Weight multiplier when a keyword or skill token matches an agent *name* token. */
|
|
39
|
+
const NAME_WEIGHT = 3;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generic role-suffix tokens that carry no domain signal and must not be
|
|
43
|
+
* counted as matches (otherwise e.g. "react-developer" would tie
|
|
44
|
+
* "angular-developer" on the "developer" token). These are stripped before
|
|
45
|
+
* both classification voting and derive scoring.
|
|
46
|
+
*/
|
|
47
|
+
const GENERIC_ROLE_TOKENS: ReadonlySet<string> = new Set([
|
|
48
|
+
"agent",
|
|
49
|
+
"analyst",
|
|
50
|
+
"architect",
|
|
51
|
+
"auditor",
|
|
52
|
+
"builder",
|
|
53
|
+
"coach",
|
|
54
|
+
"consultant",
|
|
55
|
+
"coordinator",
|
|
56
|
+
"creator",
|
|
57
|
+
"designer",
|
|
58
|
+
"developer",
|
|
59
|
+
"director",
|
|
60
|
+
"editor",
|
|
61
|
+
"engineer",
|
|
62
|
+
"expert",
|
|
63
|
+
"guardian",
|
|
64
|
+
"lead",
|
|
65
|
+
"manager",
|
|
66
|
+
"master",
|
|
67
|
+
"mentor",
|
|
68
|
+
"navigator",
|
|
69
|
+
"officer",
|
|
70
|
+
"operator",
|
|
71
|
+
"planner",
|
|
72
|
+
"producer",
|
|
73
|
+
"reviewer",
|
|
74
|
+
"specialist",
|
|
75
|
+
"steward",
|
|
76
|
+
"strategist",
|
|
77
|
+
"tester",
|
|
78
|
+
"writer",
|
|
79
|
+
"engineers",
|
|
80
|
+
"specialists",
|
|
81
|
+
"managers",
|
|
82
|
+
"developers",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Classify an agent by name + description against {@link DOMAIN_PROFILES} using
|
|
87
|
+
* name-weighted vote scoring. Returns the highest-scoring domain (ties resolve
|
|
88
|
+
* to the earliest in {@link DOMAIN_PROFILES}), or `null` when every domain
|
|
89
|
+
* scores zero — the explicit "No direct OMK skill match" sentinel.
|
|
90
|
+
*/
|
|
91
|
+
export function classifyAgent(name: string, description: string): DomainProfile | null {
|
|
92
|
+
const nameLower = name.toLowerCase();
|
|
93
|
+
const text = `${name} ${description}`.toLowerCase();
|
|
94
|
+
|
|
95
|
+
let best: DomainProfile | null = null;
|
|
96
|
+
let bestScore = 0;
|
|
97
|
+
for (const domain of DOMAIN_PROFILES) {
|
|
98
|
+
let score = 0;
|
|
99
|
+
for (const re of domain.keywords) {
|
|
100
|
+
// Boundary-anchored match: a keyword only counts when it lands on a
|
|
101
|
+
// word edge, so bare short keywords ("gin", "gis", "chem") cannot fire
|
|
102
|
+
// inside unrelated words ("engineer", "strategist", "schema").
|
|
103
|
+
if (!keywordHits(re, text)) continue;
|
|
104
|
+
// A keyword that also matches the agent name is a strong identity
|
|
105
|
+
// signal (the name is the most reliable classifier); otherwise it
|
|
106
|
+
// is a weaker description-only hit.
|
|
107
|
+
score += keywordHits(re, nameLower) ? NAME_WEIGHT : 1;
|
|
108
|
+
}
|
|
109
|
+
if (score > bestScore) {
|
|
110
|
+
bestScore = score;
|
|
111
|
+
best = domain;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return best;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Split lowercased text into identity tokens: non-word boundaries, length >= 2. */
|
|
118
|
+
function tokenize(lowerText: string): string[] {
|
|
119
|
+
const tokens: string[] = [];
|
|
120
|
+
for (const raw of lowerText.split(/[^a-z0-9+#]+/)) {
|
|
121
|
+
if (raw.length >= 2) tokens.push(raw);
|
|
122
|
+
}
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Identity tokens with generic role suffixes removed. */
|
|
127
|
+
function identityTokens(lowerText: string): string[] {
|
|
128
|
+
return tokenize(lowerText).filter((t) => !GENERIC_ROLE_TOKENS.has(t));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Boundary-anchored keyword test. A keyword must start at a left word edge
|
|
133
|
+
* (start-of-text or a non-`[a-z0-9]` neighbor). A **right** boundary is added
|
|
134
|
+
* only for short, all-alphabetic keywords (length <= 4, e.g. "gin", "gis",
|
|
135
|
+
* "chem", "seo"): those are the ones that cause substring false positives
|
|
136
|
+
* ("gin" -> "engineer", "gis" -> "strategist", "chem" -> "schema"). Longer and
|
|
137
|
+
* pattern keywords ("react", "3d-", "smart-?contract", "document-?generat")
|
|
138
|
+
* use prefix semantics (left edge only) so intentional prefix/partial matches
|
|
139
|
+
* still fire. Node >= 10 supports lookbehind.
|
|
140
|
+
*/
|
|
141
|
+
function keywordHits(re: RegExp, text: string): boolean {
|
|
142
|
+
const src = re.source;
|
|
143
|
+
const shortBare = src.length <= 4 && /^[a-z]+$/i.test(src);
|
|
144
|
+
const right = shortBare ? "(?![a-z0-9])" : "";
|
|
145
|
+
return new RegExp(`(?<![a-z0-9])${src}${right}`, "i").test(text);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Score a candidate skill against the agent's name and description tokens.
|
|
150
|
+
* The skill's **first** `-`-split token is the technology word (e.g. "react"
|
|
151
|
+
* in "react-patterns") and is weighted heaviest; a match in the agent name is
|
|
152
|
+
* worth {@link NAME_WEIGHT}x a description match. Generic tokens are skipped.
|
|
153
|
+
*/
|
|
154
|
+
function skillMatchScore(
|
|
155
|
+
skill: string,
|
|
156
|
+
nameTokens: ReadonlySet<string>,
|
|
157
|
+
descTokens: ReadonlySet<string>,
|
|
158
|
+
nameLower: string,
|
|
159
|
+
descLower: string,
|
|
160
|
+
): number {
|
|
161
|
+
const parts = skill.split("-");
|
|
162
|
+
let score = 0;
|
|
163
|
+
for (let i = 0; i < parts.length; i++) {
|
|
164
|
+
const part = parts[i];
|
|
165
|
+
if (part.length === 0 || GENERIC_ROLE_TOKENS.has(part)) continue;
|
|
166
|
+
const isTech = i === 0; // first token = the technology word
|
|
167
|
+
const nameHit = nameTokens.has(part);
|
|
168
|
+
const descHit = descTokens.has(part);
|
|
169
|
+
if (nameHit) score += (isTech ? 5 : 2) * NAME_WEIGHT;
|
|
170
|
+
else if (descHit) score += isTech ? 5 : 2;
|
|
171
|
+
}
|
|
172
|
+
// Full skill string appearing verbatim in name or description is a strong signal.
|
|
173
|
+
if (nameLower.includes(skill)) score += 3 * NAME_WEIGHT;
|
|
174
|
+
else if (descLower.includes(skill)) score += 3;
|
|
175
|
+
return score;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Dedupe a list preserving first-seen order. */
|
|
179
|
+
function dedupe(items: readonly string[]): string[] {
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
const out: string[] = [];
|
|
182
|
+
for (const item of items) {
|
|
183
|
+
if (seen.has(item)) continue;
|
|
184
|
+
seen.add(item);
|
|
185
|
+
out.push(item);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Deterministically derive a baseline capability set for an agent. When no
|
|
192
|
+
* domain matches, all three lists are empty. Otherwise skills are picked from
|
|
193
|
+
* the domain's pool by name/description relevance (top {@link MAX_SKILLS} with
|
|
194
|
+
* a positive score, falling back to the first {@link FALLBACK_SKILLS} pool
|
|
195
|
+
* entries when nothing scores above zero), and MCP/hooks are the domain
|
|
196
|
+
* defaults intersected with the live catalog.
|
|
197
|
+
*/
|
|
198
|
+
export function deriveCapabilities(name: string, description: string, catalog: CapabilityCatalog): AgentCapabilities {
|
|
199
|
+
const domain = classifyAgent(name, description);
|
|
200
|
+
if (domain === null) return { skills: [], mcp: [], hooks: [] };
|
|
201
|
+
|
|
202
|
+
const nameLower = name.toLowerCase();
|
|
203
|
+
const descLower = description.toLowerCase();
|
|
204
|
+
const nameTokens = new Set(identityTokens(nameLower));
|
|
205
|
+
const descTokens = new Set(identityTokens(`${nameLower} ${descLower}`));
|
|
206
|
+
|
|
207
|
+
const candidates = domain.skillPool.filter((s) => catalog.skills.has(s));
|
|
208
|
+
const scored = candidates.map((skill) => ({
|
|
209
|
+
skill,
|
|
210
|
+
score: skillMatchScore(skill, nameTokens, descTokens, nameLower, descLower),
|
|
211
|
+
}));
|
|
212
|
+
const positive = scored.filter((x) => x.score > 0);
|
|
213
|
+
|
|
214
|
+
let selected: string[];
|
|
215
|
+
if (positive.length > 0) {
|
|
216
|
+
positive.sort((a, b) => {
|
|
217
|
+
if (a.score !== b.score) return b.score - a.score;
|
|
218
|
+
if (a.skill < b.skill) return -1;
|
|
219
|
+
if (a.skill > b.skill) return 1;
|
|
220
|
+
return 0;
|
|
221
|
+
});
|
|
222
|
+
selected = positive.slice(0, MAX_SKILLS).map((x) => x.skill);
|
|
223
|
+
} else {
|
|
224
|
+
selected = candidates.slice(0, FALLBACK_SKILLS);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const mcp = domain.defaultMcp.filter((m) => catalog.mcp.has(m));
|
|
228
|
+
const hooks = domain.defaultHooks.filter((h) => catalog.hooks.has(h));
|
|
229
|
+
|
|
230
|
+
return { skills: dedupe(selected), mcp, hooks };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Result of comparing a declared capability set against the deterministic derivation. */
|
|
234
|
+
export interface CapabilityAudit {
|
|
235
|
+
/** Jaccard overlap of declared vs derived skill sets, 0..1 (1.0 when both empty). */
|
|
236
|
+
readonly jaccard: number;
|
|
237
|
+
/** Skills declared but not in the live catalog (from {@link validateCapabilities}). */
|
|
238
|
+
readonly declaredUnknownSkills: readonly string[];
|
|
239
|
+
readonly skillsOnlyInDeclared: readonly string[];
|
|
240
|
+
readonly skillsOnlyInDerived: readonly string[];
|
|
241
|
+
/** "match" (jaccard >= 0.5), "drift" (0.15 <= jaccard < 0.5), "divergent" (jaccard < 0.15). */
|
|
242
|
+
readonly verdict: "match" | "drift" | "divergent";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Compare a declared (e.g. LLM-assigned or embedded) capability set against the
|
|
247
|
+
* deterministic derivation. The Jaccard coefficient is over the two skill lists
|
|
248
|
+
* treated as sets; both-empty is defined as 1.0.
|
|
249
|
+
*/
|
|
250
|
+
export function auditCapabilities(
|
|
251
|
+
declared: AgentCapabilities,
|
|
252
|
+
derived: AgentCapabilities,
|
|
253
|
+
catalog: CapabilityCatalog,
|
|
254
|
+
): CapabilityAudit {
|
|
255
|
+
const declaredSet = new Set(declared.skills);
|
|
256
|
+
const derivedSet = new Set(derived.skills);
|
|
257
|
+
|
|
258
|
+
let intersection = 0;
|
|
259
|
+
for (const s of declaredSet) if (derivedSet.has(s)) intersection++;
|
|
260
|
+
const unionSize = new Set([...declaredSet, ...derivedSet]).size;
|
|
261
|
+
const jaccard = unionSize === 0 ? 1.0 : intersection / unionSize;
|
|
262
|
+
|
|
263
|
+
const declaredUnknownSkills = validateCapabilities(declared, catalog).unknownSkills;
|
|
264
|
+
|
|
265
|
+
const skillsOnlyInDeclared: string[] = [];
|
|
266
|
+
for (const s of declaredSet) if (!derivedSet.has(s)) skillsOnlyInDeclared.push(s);
|
|
267
|
+
const skillsOnlyInDerived: string[] = [];
|
|
268
|
+
for (const s of derivedSet) if (!declaredSet.has(s)) skillsOnlyInDerived.push(s);
|
|
269
|
+
|
|
270
|
+
const verdict: "match" | "drift" | "divergent" =
|
|
271
|
+
jaccard >= JACCARD_MATCH ? "match" : jaccard >= JACCARD_DRIFT ? "drift" : "divergent";
|
|
272
|
+
|
|
273
|
+
return { jaccard, declaredUnknownSkills, skillsOnlyInDeclared, skillsOnlyInDerived, verdict };
|
|
274
|
+
}
|
|
@@ -9,6 +9,9 @@ You are a planning specialist. You receive context (from a scout) and requiremen
|
|
|
9
9
|
|
|
10
10
|
You must NOT make any changes. Only read, analyze, and plan.
|
|
11
11
|
|
|
12
|
+
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
13
|
+
- Skills: ulw-plan (explore-first planning, decision-complete plans), packages (if the codebase spans multiple packages), deep-interview (only if requirements are still fuzzy after reading the scout's context). Load only what the task needs; do not bulk-load the catalog.
|
|
14
|
+
|
|
12
15
|
Input format you'll receive:
|
|
13
16
|
- Context/findings from a scout agent
|
|
14
17
|
- Original query or requirements
|
|
@@ -10,6 +10,9 @@ You are a senior code reviewer. Analyze code for quality, security, and maintain
|
|
|
10
10
|
Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
|
|
11
11
|
Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
|
|
12
12
|
|
|
13
|
+
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
14
|
+
- Skills: review-work (structured multi-angle review pass), security-review (when the diff touches auth, secrets, or user input). Load only what the task needs.
|
|
15
|
+
|
|
13
16
|
Strategy:
|
|
14
17
|
1. Run `git diff` to see recent changes (if applicable)
|
|
15
18
|
2. Read the modified files
|
|
@@ -9,6 +9,9 @@ You are a scout. Quickly investigate a codebase and return structured findings t
|
|
|
9
9
|
|
|
10
10
|
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
11
11
|
|
|
12
|
+
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
13
|
+
- Skills: understand-anything (if a knowledge-graph/dependency question, not just a file lookup), ast-grep (structural search when grep-by-text isn't precise enough), headroom (if findings would otherwise be too large to hand off compactly). Load only what the task needs.
|
|
14
|
+
|
|
12
15
|
Thoroughness (infer from task, default medium):
|
|
13
16
|
- Quick: Targeted lookups, key files only
|
|
14
17
|
- Medium: Follow imports, read critical sections
|
|
@@ -8,6 +8,8 @@ You are a worker agent with full capabilities. You operate in an isolated contex
|
|
|
8
8
|
|
|
9
9
|
Work autonomously to complete the assigned task. Use all available tools as needed.
|
|
10
10
|
|
|
11
|
+
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference): no fixed skill set — you have the full catalog. Self-route: classify the task, pick the most specific matching skill, load only that one, and never browse the catalog speculatively. For general coding work the most common picks are programming, debugging, git-master, ast-grep, and lsp; for anything else, match by the skill's own description.
|
|
12
|
+
|
|
11
13
|
Output format when finished:
|
|
12
14
|
|
|
13
15
|
## Completed
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { getAgentDir, parseFrontmatter } from "open-multi-agent-kit";
|
|
8
|
+
import { type AgentCapabilities, parseCapabilities } from "./capabilities.ts";
|
|
8
9
|
|
|
9
10
|
export type AgentScope = "user" | "project" | "both";
|
|
10
11
|
|
|
@@ -13,6 +14,11 @@ export interface AgentConfig {
|
|
|
13
14
|
description: string;
|
|
14
15
|
tools?: string[];
|
|
15
16
|
model?: string;
|
|
17
|
+
/** Optional capabilities declared in frontmatter (canonical source). */
|
|
18
|
+
capabilities?: AgentCapabilities;
|
|
19
|
+
/** When true, the dispatcher restricts the spawned subprocess to the declared
|
|
20
|
+
* skills via `--no-skills` + `--skill <path>`. Default off (guidance only). */
|
|
21
|
+
enforceCapabilities?: boolean;
|
|
16
22
|
systemPrompt: string;
|
|
17
23
|
source: "user" | "project";
|
|
18
24
|
filePath: string;
|
|
@@ -60,11 +66,19 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
|
|
|
60
66
|
.map((t: string) => t.trim())
|
|
61
67
|
.filter(Boolean);
|
|
62
68
|
|
|
69
|
+
const capabilities = parseCapabilities(frontmatter);
|
|
70
|
+
const enforceCapabilities =
|
|
71
|
+
typeof frontmatter.enforceCapabilities === "string"
|
|
72
|
+
? frontmatter.enforceCapabilities.trim().toLowerCase() === "true"
|
|
73
|
+
: undefined;
|
|
74
|
+
|
|
63
75
|
agents.push({
|
|
64
76
|
name: frontmatter.name,
|
|
65
77
|
description: frontmatter.description,
|
|
66
78
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
67
79
|
model: frontmatter.model,
|
|
80
|
+
capabilities,
|
|
81
|
+
enforceCapabilities,
|
|
68
82
|
systemPrompt: body,
|
|
69
83
|
source,
|
|
70
84
|
filePath,
|