open-multi-agent-kit 0.79.3 → 0.80.2
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/MATURITY.md +2 -2
- package/README.md +86 -39
- package/ROADMAP.md +20 -17
- package/dist/brand/night-city.theme.json +122 -24
- package/dist/brand/rust-forge.theme.json +1 -1
- package/dist/commands/chat/native-root-loop.d.ts +27 -3
- package/dist/commands/chat/native-root-loop.js +370 -49
- package/dist/commands/dag.js +45 -0
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/verify.js +59 -1
- package/dist/contracts/dag.d.ts +9 -1
- package/dist/evidence/graph-ontology-relation-gate.d.ts +10 -0
- package/dist/evidence/graph-ontology-relation-gate.js +46 -0
- package/dist/evidence/headroom-replay-verifier.d.ts +19 -0
- package/dist/evidence/headroom-replay-verifier.js +86 -0
- package/dist/memory/local-graph-memory-store.d.ts +75 -0
- package/dist/memory/local-graph-memory-store.js +570 -0
- package/dist/orchestration/dag.d.ts +22 -1
- package/dist/orchestration/evidence-gate.js +12 -0
- package/dist/orchestration/executor.js +111 -15
- package/dist/orchestration/loop-controller-headroom.d.ts +6 -0
- package/dist/orchestration/loop-controller-headroom.js +33 -0
- package/dist/orchestration/scheduler.d.ts +2 -0
- package/dist/orchestration/scheduler.js +3 -0
- package/dist/orchestration/task-graph.d.ts +17 -0
- package/dist/orchestration/task-graph.js +38 -0
- package/dist/providers/codex-cli-runner.js +17 -2
- package/dist/runtime/agent-runtime.d.ts +32 -3
- package/dist/runtime/agent-runtime.js +20 -7
- package/dist/runtime/authority-matrix.d.ts +23 -0
- package/dist/runtime/authority-matrix.js +171 -0
- package/dist/runtime/chat-advisory-runtime.js +3 -0
- package/dist/runtime/codex-cli-runtime.js +2 -0
- package/dist/runtime/codex-runtime.d.ts +4 -1
- package/dist/runtime/codex-runtime.js +34 -12
- package/dist/runtime/context-broker-converter.js +32 -2
- package/dist/runtime/context-broker.js +18 -0
- package/dist/runtime/contracts/evidence.d.ts +52 -0
- package/dist/runtime/contracts/evidence.js +138 -1
- package/dist/runtime/contracts/shared.d.ts +27 -0
- package/dist/runtime/data-retention-gate.d.ts +42 -0
- package/dist/runtime/data-retention-gate.js +123 -0
- package/dist/runtime/deepseek-runtime.d.ts +5 -1
- package/dist/runtime/deepseek-runtime.js +13 -7
- package/dist/runtime/external-cli-adapter.js +20 -7
- package/dist/runtime/freedomd-evidence-envelope.d.ts +57 -0
- package/dist/runtime/freedomd-evidence-envelope.js +110 -0
- package/dist/runtime/freedomd-exception.d.ts +32 -0
- package/dist/runtime/freedomd-exception.js +95 -0
- package/dist/runtime/freedomd-incidents.d.ts +23 -0
- package/dist/runtime/freedomd-incidents.js +103 -0
- package/dist/runtime/freedomd-policy.d.ts +35 -0
- package/dist/runtime/freedomd-policy.js +114 -0
- package/dist/runtime/freedomd-router.d.ts +66 -0
- package/dist/runtime/freedomd-router.js +433 -0
- package/dist/runtime/headroom-aware-loop-decision.d.ts +21 -0
- package/dist/runtime/headroom-aware-loop-decision.js +67 -0
- package/dist/runtime/headroom-loop-risk.d.ts +19 -0
- package/dist/runtime/headroom-loop-risk.js +43 -0
- package/dist/runtime/headroom-policy.d.ts +14 -1
- package/dist/runtime/headroom-policy.js +74 -12
- package/dist/runtime/kimi-api-runtime.d.ts +4 -1
- package/dist/runtime/kimi-api-runtime.js +35 -11
- package/dist/runtime/kimi-print-runtime.js +21 -0
- package/dist/runtime/kimi-wire-protocol-runtime.js +17 -1
- package/dist/runtime/local-llm-runtime.d.ts +5 -1
- package/dist/runtime/local-llm-runtime.js +14 -23
- package/dist/runtime/private-stderr.d.ts +13 -0
- package/dist/runtime/private-stderr.js +69 -0
- package/dist/runtime/prompt-envelope.d.ts +1 -1
- package/dist/runtime/provider-sovereignty.d.ts +81 -0
- package/dist/runtime/provider-sovereignty.js +285 -0
- package/dist/runtime/runtime-backed-task-runner.d.ts +1 -0
- package/dist/runtime/runtime-backed-task-runner.js +282 -10
- package/dist/runtime/runtime-bootstrap.d.ts +12 -0
- package/dist/runtime/runtime-bootstrap.js +54 -11
- package/dist/runtime/runtime-failure-classifier.d.ts +15 -0
- package/dist/runtime/runtime-failure-classifier.js +107 -0
- package/dist/runtime/runtime-health-probes.d.ts +30 -0
- package/dist/runtime/runtime-health-probes.js +102 -0
- package/dist/runtime/runtime-router.d.ts +6 -2
- package/dist/runtime/runtime-router.js +575 -53
- package/dist/runtime/structured-compaction.d.ts +107 -0
- package/dist/runtime/structured-compaction.js +414 -0
- package/dist/runtime/tool-dispatch-contracts.d.ts +7 -5
- package/dist/runtime/tool-dispatch-contracts.js +15 -4
- package/dist/runtime/tool-plane.d.ts +1 -1
- package/dist/runtime/tool-plane.js +49 -1
- package/dist/runtime/worker-manifest.js +1 -1
- package/dist/schema/evidence-bundle.schema.d.ts +8 -8
- package/dist/schema/proof-bundle.schema.d.ts +2 -2
- package/dist/util/hash.d.ts +2 -0
- package/dist/util/hash.js +13 -0
- package/dist/util/session.js +5 -4
- package/docs/2026-06-16/critical-issues.md +19 -0
- package/docs/2026-06-16/improvements.md +15 -0
- package/docs/2026-06-16/init-checklist.md +25 -0
- package/docs/2026-06-16/plan.md +20 -0
- package/docs/2026-06-17/critical-issues.md +19 -0
- package/docs/2026-06-17/improvements.md +15 -0
- package/docs/2026-06-17/init-checklist.md +25 -0
- package/docs/2026-06-17/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +2 -1
- package/docs/GSTACK_MIGRATION.md +1 -1
- package/docs/algorithm-hardening-playbook.md +219 -0
- package/docs/claims.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/native-root-runtime-hardening.md +42 -32
- package/docs/post-0793-hardening-roadmap.md +123 -0
- package/docs/provider-maturity.md +18 -16
- package/docs/versioning.md +3 -3
- package/docs/what-is-omk.md +1 -1
- package/package.json +8 -4
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +2 -0
- package/readmeasset/ASSET_PROVENANCE.md +40 -20
- package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +1 -1
- package/readmeasset/omk-control-surfaces.svg +1 -1
- package/readmeasset/omk-core-loop.svg +1 -1
- package/readmeasset/omk-freedomd-control-plane.svg +59 -0
- package/readmeasset/omk-freedomd-control-plane.webp +0 -0
- package/readmeasset/omk-init-control-loop.svg +1 -1
- package/readmeasset/omk-logo-mark.svg +1 -1
- package/readmeasset/omk-parallel-subagents.svg +1 -1
- package/readmeasset/omk-release-assertions.svg +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ContextCapsule } from "./context-capsule.js";
|
|
2
|
+
export interface StructuredCompactionContractV1 {
|
|
3
|
+
readonly schemaVersion: "omk.structured-compaction.v1";
|
|
4
|
+
readonly requiredSections: readonly string[];
|
|
5
|
+
readonly minTaskPrefixChars: number;
|
|
6
|
+
readonly safetyMarkers: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface StructuredCompactionContractV2 {
|
|
9
|
+
readonly schemaVersion: "omk.structured-compaction.v2";
|
|
10
|
+
readonly node: {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly role: string;
|
|
13
|
+
};
|
|
14
|
+
readonly routing: {
|
|
15
|
+
readonly provider: string;
|
|
16
|
+
readonly risk: string;
|
|
17
|
+
readonly sandboxMode: string;
|
|
18
|
+
readonly readOnly: boolean;
|
|
19
|
+
readonly approvalPolicy: string;
|
|
20
|
+
};
|
|
21
|
+
readonly evidence: {
|
|
22
|
+
readonly required: readonly {
|
|
23
|
+
readonly gate: string;
|
|
24
|
+
readonly ref?: string;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
readonly capabilities: readonly string[];
|
|
28
|
+
readonly safety: {
|
|
29
|
+
readonly evidenceRequired: boolean;
|
|
30
|
+
readonly preserve: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export type StructuredCompactionContract = StructuredCompactionContractV1 | StructuredCompactionContractV2;
|
|
34
|
+
export interface StructuredCompactionValidationResult {
|
|
35
|
+
readonly ok: boolean;
|
|
36
|
+
readonly missing: readonly string[];
|
|
37
|
+
readonly contract: StructuredCompactionContract;
|
|
38
|
+
}
|
|
39
|
+
export interface StructuredCompactionBuildOptions {
|
|
40
|
+
/** Approximate target budget for the generated fallback text. */
|
|
41
|
+
readonly maxTokens?: number;
|
|
42
|
+
/** Apply secret redaction to all non-contract free text. Default true. */
|
|
43
|
+
readonly redact?: boolean;
|
|
44
|
+
readonly maxMemoryFacts?: number;
|
|
45
|
+
readonly maxDependencySummaries?: number;
|
|
46
|
+
readonly estimator?: TokenEstimationOptions;
|
|
47
|
+
}
|
|
48
|
+
export interface TokenEstimationCalibration {
|
|
49
|
+
readonly multiplier?: number;
|
|
50
|
+
readonly bias?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface TokenEstimationOptions {
|
|
53
|
+
readonly provider?: string;
|
|
54
|
+
readonly model?: string;
|
|
55
|
+
readonly calibration?: TokenEstimationCalibration;
|
|
56
|
+
}
|
|
57
|
+
export interface CompactionQualityInput {
|
|
58
|
+
readonly applied: boolean;
|
|
59
|
+
readonly validated: boolean;
|
|
60
|
+
readonly beforeTokens?: number | null;
|
|
61
|
+
readonly afterTokens?: number | null;
|
|
62
|
+
readonly missingSections?: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
export interface CompactionQualityScore {
|
|
65
|
+
readonly qualityScore: number;
|
|
66
|
+
readonly compressionRatio: number | null;
|
|
67
|
+
readonly contractScore: number;
|
|
68
|
+
readonly compressionScore: number;
|
|
69
|
+
readonly evidenceScore: number;
|
|
70
|
+
readonly safetyScore: number;
|
|
71
|
+
readonly capabilityScore: number;
|
|
72
|
+
}
|
|
73
|
+
export declare const DEFAULT_STRUCTURED_COMPACTION_CONTRACT: StructuredCompactionContractV1;
|
|
74
|
+
export declare const STRUCTURED_COMPACTION_V2_SCHEMA_VERSION: "omk.structured-compaction.v2";
|
|
75
|
+
export interface CompactionQualityGateInput {
|
|
76
|
+
readonly applied: boolean;
|
|
77
|
+
readonly validated: boolean;
|
|
78
|
+
readonly qualityScore: number;
|
|
79
|
+
readonly contractScore: number;
|
|
80
|
+
readonly risk?: string;
|
|
81
|
+
readonly capabilities?: {
|
|
82
|
+
readonly write?: boolean;
|
|
83
|
+
readonly patch?: boolean;
|
|
84
|
+
readonly shell?: boolean;
|
|
85
|
+
readonly merge?: boolean;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export type CompactionGateDecision = "not-attempted" | "not-applied" | "accept" | "accept-with-warning" | "reject";
|
|
89
|
+
export interface CompactionQualityGateResult {
|
|
90
|
+
readonly gateDecision: CompactionGateDecision;
|
|
91
|
+
readonly warning?: string;
|
|
92
|
+
readonly threshold: number;
|
|
93
|
+
}
|
|
94
|
+
export declare function resolveCompactionQualityThreshold(input: {
|
|
95
|
+
readonly risk?: string;
|
|
96
|
+
readonly capabilities?: CompactionQualityGateInput["capabilities"];
|
|
97
|
+
}): number;
|
|
98
|
+
export declare function evaluateCompactionQualityGate(input: CompactionQualityGateInput): CompactionQualityGateResult;
|
|
99
|
+
export declare function validateStructuredCompaction(compactedText: string, capsule: ContextCapsule, contract?: StructuredCompactionContractV1): StructuredCompactionValidationResult;
|
|
100
|
+
export declare function buildTypedStructuredCompactionContract(capsule: ContextCapsule): StructuredCompactionContractV2;
|
|
101
|
+
export declare function renderTypedStructuredCompactionContract(contract: StructuredCompactionContractV2): string;
|
|
102
|
+
export declare function parseTypedStructuredCompactionContract(compactedText: string): StructuredCompactionContractV2 | null;
|
|
103
|
+
export declare function structuredCompactionGuardNote(result: StructuredCompactionValidationResult): string;
|
|
104
|
+
export declare function structuredCompactionInstruction(contract?: StructuredCompactionContract): string;
|
|
105
|
+
export declare function buildStructuredCompactionText(capsule: ContextCapsule, _contract?: StructuredCompactionContractV1, options?: StructuredCompactionBuildOptions): string;
|
|
106
|
+
export declare function estimateTextTokens(text: string, options?: TokenEstimationOptions): number;
|
|
107
|
+
export declare function computeCompactionQualityScore(input: CompactionQualityInput): CompactionQualityScore;
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { maskSensitiveText } from "../util/secret-mask.js";
|
|
2
|
+
export const DEFAULT_STRUCTURED_COMPACTION_CONTRACT = {
|
|
3
|
+
schemaVersion: "omk.structured-compaction.v1",
|
|
4
|
+
requiredSections: [
|
|
5
|
+
"task",
|
|
6
|
+
"node routing",
|
|
7
|
+
"evidence requirements",
|
|
8
|
+
"safety constraints",
|
|
9
|
+
"capabilities",
|
|
10
|
+
],
|
|
11
|
+
minTaskPrefixChars: 40,
|
|
12
|
+
safetyMarkers: ["preserve", "safety", "constraints", "evidence required", "capabilities"],
|
|
13
|
+
};
|
|
14
|
+
export const STRUCTURED_COMPACTION_V2_SCHEMA_VERSION = "omk.structured-compaction.v2";
|
|
15
|
+
export function resolveCompactionQualityThreshold(input) {
|
|
16
|
+
const risk = (input.risk ?? "").toLowerCase();
|
|
17
|
+
const caps = input.capabilities ?? {};
|
|
18
|
+
if (caps.merge === true || caps.shell === true)
|
|
19
|
+
return 0.85;
|
|
20
|
+
if (caps.write === true || caps.patch === true)
|
|
21
|
+
return 0.75;
|
|
22
|
+
if (risk.includes("shell") || risk.includes("merge") || risk.includes("release"))
|
|
23
|
+
return 0.85;
|
|
24
|
+
if (risk.includes("write") || risk.includes("patch"))
|
|
25
|
+
return 0.75;
|
|
26
|
+
return 0.60;
|
|
27
|
+
}
|
|
28
|
+
function isStrictGuardrailMode() {
|
|
29
|
+
const raw = process.env.OMK_STRICT_GUARDRAIL ?? "";
|
|
30
|
+
const normalized = raw.trim().toLowerCase();
|
|
31
|
+
return normalized === "1" || normalized === "true" || normalized === "on";
|
|
32
|
+
}
|
|
33
|
+
export function evaluateCompactionQualityGate(input) {
|
|
34
|
+
if (!input.applied) {
|
|
35
|
+
return { gateDecision: input.validated ? "not-applied" : "not-attempted", threshold: resolveCompactionQualityThreshold(input) };
|
|
36
|
+
}
|
|
37
|
+
const threshold = resolveCompactionQualityThreshold(input);
|
|
38
|
+
if (input.qualityScore >= threshold) {
|
|
39
|
+
return { gateDecision: "accept", threshold };
|
|
40
|
+
}
|
|
41
|
+
if (input.qualityScore >= Math.max(0, threshold - 0.20) && input.contractScore === 1) {
|
|
42
|
+
return { gateDecision: "accept-with-warning", warning: "low compaction quality", threshold };
|
|
43
|
+
}
|
|
44
|
+
if (!isStrictGuardrailMode()) {
|
|
45
|
+
return { gateDecision: "accept-with-warning", warning: "low compaction quality accepted under default freedom mode", threshold };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
gateDecision: "reject",
|
|
49
|
+
warning: "compaction quality below safety threshold",
|
|
50
|
+
threshold,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function validateStructuredCompaction(compactedText, capsule, contract = DEFAULT_STRUCTURED_COMPACTION_CONTRACT) {
|
|
54
|
+
const typed = parseTypedStructuredCompactionContract(compactedText);
|
|
55
|
+
if (typed)
|
|
56
|
+
return validateTypedStructuredCompaction(compactedText, capsule, typed);
|
|
57
|
+
return validateLegacyStructuredCompaction(compactedText, capsule, contract);
|
|
58
|
+
}
|
|
59
|
+
function validateLegacyStructuredCompaction(compactedText, capsule, contract) {
|
|
60
|
+
const lower = compactedText.toLowerCase();
|
|
61
|
+
const missing = [];
|
|
62
|
+
if (contract.requiredSections.includes("task")) {
|
|
63
|
+
const taskLower = capsule.task.toLowerCase().trim();
|
|
64
|
+
const prefixLength = Math.min(contract.minTaskPrefixChars, taskLower.length);
|
|
65
|
+
if (taskLower.length > 0 && !lower.includes(taskLower.slice(0, prefixLength))) {
|
|
66
|
+
missing.push("task");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const routing = capsule.node.routing;
|
|
70
|
+
if (routing && contract.requiredSections.includes("node routing")) {
|
|
71
|
+
if (routing.provider && !lower.includes(routing.provider.toLowerCase()))
|
|
72
|
+
missing.push("node routing provider");
|
|
73
|
+
if (routing.risk && !lower.includes(routing.risk.toLowerCase()))
|
|
74
|
+
missing.push("node routing risk");
|
|
75
|
+
if (routing.sandboxMode && !lower.includes(routing.sandboxMode.toLowerCase()))
|
|
76
|
+
missing.push("node routing sandboxMode");
|
|
77
|
+
if (routing.readOnly === true && !lower.includes("read-only") && !lower.includes("readonly")) {
|
|
78
|
+
missing.push("node routing readOnly");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (contract.requiredSections.includes("evidence requirements")) {
|
|
82
|
+
const requiredGates = capsule.evidenceRequirements
|
|
83
|
+
.filter((e) => e.required)
|
|
84
|
+
.map((e) => e.gate.toLowerCase());
|
|
85
|
+
if (requiredGates.length > 0 && !requiredGates.some((gate) => lower.includes(gate))) {
|
|
86
|
+
missing.push("evidence requirements");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (contract.requiredSections.includes("safety constraints")) {
|
|
90
|
+
const systemLower = capsule.system.toLowerCase();
|
|
91
|
+
const hasSafetyMarker = contract.safetyMarkers.some((marker) => lower.includes(marker));
|
|
92
|
+
const hasSystemOverlap = systemLower.length > 0 && lower.includes(systemLower.slice(0, Math.min(60, systemLower.length)));
|
|
93
|
+
if (!hasSafetyMarker && !hasSystemOverlap)
|
|
94
|
+
missing.push("safety constraints");
|
|
95
|
+
}
|
|
96
|
+
if (contract.requiredSections.includes("capabilities")) {
|
|
97
|
+
const assignedCaps = new Set(routing?.assignedProviderCapabilities?.map((c) => c.toLowerCase()) ?? []);
|
|
98
|
+
if (assignedCaps.size > 0 && !Array.from(assignedCaps).some((cap) => lower.includes(cap))) {
|
|
99
|
+
missing.push("capabilities");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { ok: missing.length === 0, missing, contract };
|
|
103
|
+
}
|
|
104
|
+
export function buildTypedStructuredCompactionContract(capsule) {
|
|
105
|
+
const routing = capsule.node.routing;
|
|
106
|
+
const requiredEvidence = capsule.evidenceRequirements
|
|
107
|
+
.filter((e) => e.required)
|
|
108
|
+
.map((e) => (e.ref ? { gate: e.gate, ref: e.ref } : { gate: e.gate }));
|
|
109
|
+
return {
|
|
110
|
+
schemaVersion: STRUCTURED_COMPACTION_V2_SCHEMA_VERSION,
|
|
111
|
+
node: {
|
|
112
|
+
id: capsule.nodeId,
|
|
113
|
+
role: String(capsule.node.role ?? "worker"),
|
|
114
|
+
},
|
|
115
|
+
routing: {
|
|
116
|
+
provider: routing?.provider ?? "auto",
|
|
117
|
+
risk: routing?.risk ?? "read",
|
|
118
|
+
sandboxMode: routing?.sandboxMode ?? "unknown",
|
|
119
|
+
readOnly: routing?.readOnly === true,
|
|
120
|
+
approvalPolicy: routing?.approvalPolicy ?? "unknown",
|
|
121
|
+
},
|
|
122
|
+
evidence: {
|
|
123
|
+
required: requiredEvidence,
|
|
124
|
+
},
|
|
125
|
+
capabilities: routing?.assignedProviderCapabilities ?? [],
|
|
126
|
+
safety: {
|
|
127
|
+
evidenceRequired: routing?.evidenceRequired === true || requiredEvidence.length > 0,
|
|
128
|
+
preserve: DEFAULT_STRUCTURED_COMPACTION_CONTRACT.requiredSections,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export function renderTypedStructuredCompactionContract(contract) {
|
|
133
|
+
return [
|
|
134
|
+
"```json omk.structured-compaction.v2",
|
|
135
|
+
JSON.stringify(contract, null, 2),
|
|
136
|
+
"```",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
export function parseTypedStructuredCompactionContract(compactedText) {
|
|
140
|
+
const match = compactedText.match(/```json omk\.structured-compaction\.v2\s*\n([\s\S]*?)\n```/i);
|
|
141
|
+
if (!match)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(match[1]);
|
|
145
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== STRUCTURED_COMPACTION_V2_SCHEMA_VERSION)
|
|
146
|
+
return null;
|
|
147
|
+
if (!isRecord(parsed.node) || typeof parsed.node.id !== "string" || typeof parsed.node.role !== "string")
|
|
148
|
+
return null;
|
|
149
|
+
if (!isRecord(parsed.routing))
|
|
150
|
+
return null;
|
|
151
|
+
if (!isRecord(parsed.evidence) || !Array.isArray(parsed.evidence.required))
|
|
152
|
+
return null;
|
|
153
|
+
if (!Array.isArray(parsed.capabilities))
|
|
154
|
+
return null;
|
|
155
|
+
if (!isRecord(parsed.safety) || !Array.isArray(parsed.safety.preserve))
|
|
156
|
+
return null;
|
|
157
|
+
return parsed;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function validateTypedStructuredCompaction(compactedText, capsule, contract) {
|
|
164
|
+
const missing = [];
|
|
165
|
+
const expected = buildTypedStructuredCompactionContract(capsule);
|
|
166
|
+
const lower = compactedText.toLowerCase();
|
|
167
|
+
for (const section of DEFAULT_STRUCTURED_COMPACTION_CONTRACT.requiredSections) {
|
|
168
|
+
if (!lower.includes(`## ${section}`))
|
|
169
|
+
missing.push(section);
|
|
170
|
+
}
|
|
171
|
+
if (contract.node.id !== expected.node.id)
|
|
172
|
+
missing.push("typed node id");
|
|
173
|
+
if (contract.node.role !== expected.node.role)
|
|
174
|
+
missing.push("typed node role");
|
|
175
|
+
if (contract.routing.provider !== expected.routing.provider)
|
|
176
|
+
missing.push("typed routing provider");
|
|
177
|
+
if (contract.routing.risk !== expected.routing.risk)
|
|
178
|
+
missing.push("typed routing risk");
|
|
179
|
+
if (contract.routing.sandboxMode !== expected.routing.sandboxMode)
|
|
180
|
+
missing.push("typed routing sandboxMode");
|
|
181
|
+
if (contract.routing.readOnly !== expected.routing.readOnly)
|
|
182
|
+
missing.push("typed routing readOnly");
|
|
183
|
+
if (contract.routing.approvalPolicy !== expected.routing.approvalPolicy)
|
|
184
|
+
missing.push("typed routing approvalPolicy");
|
|
185
|
+
if (!sameStringSet(contract.capabilities, expected.capabilities))
|
|
186
|
+
missing.push("typed capabilities");
|
|
187
|
+
if (!sameEvidence(contract.evidence.required, expected.evidence.required))
|
|
188
|
+
missing.push("typed evidence requirements");
|
|
189
|
+
if (contract.safety.evidenceRequired !== expected.safety.evidenceRequired)
|
|
190
|
+
missing.push("typed safety evidenceRequired");
|
|
191
|
+
if (!DEFAULT_STRUCTURED_COMPACTION_CONTRACT.requiredSections.every((section) => contract.safety.preserve.includes(section))) {
|
|
192
|
+
missing.push("typed safety preserve");
|
|
193
|
+
}
|
|
194
|
+
return { ok: missing.length === 0, missing, contract };
|
|
195
|
+
}
|
|
196
|
+
export function structuredCompactionGuardNote(result) {
|
|
197
|
+
return `Headroom compaction removed required sections: ${result.missing.join(", ")}; using original capsule.`;
|
|
198
|
+
}
|
|
199
|
+
export function structuredCompactionInstruction(contract = DEFAULT_STRUCTURED_COMPACTION_CONTRACT) {
|
|
200
|
+
if (contract.schemaVersion === STRUCTURED_COMPACTION_V2_SCHEMA_VERSION) {
|
|
201
|
+
return [
|
|
202
|
+
`${contract.schemaVersion}: the original context capsule was compacted before runtime dispatch.`,
|
|
203
|
+
"The typed JSON contract below is authoritative for routing, evidence, safety, and capabilities.",
|
|
204
|
+
"Preserve task, node routing, evidence requirements, safety constraints, and capability grants below.",
|
|
205
|
+
].join(" ");
|
|
206
|
+
}
|
|
207
|
+
return [
|
|
208
|
+
`${contract.schemaVersion}: the original context capsule was compacted before runtime dispatch.`,
|
|
209
|
+
`Required sections: ${contract.requiredSections.join(", ")}.`,
|
|
210
|
+
"Preserve task, node routing, evidence requirements, safety constraints, and capability grants below.",
|
|
211
|
+
].join(" ");
|
|
212
|
+
}
|
|
213
|
+
export function buildStructuredCompactionText(capsule, _contract = DEFAULT_STRUCTURED_COMPACTION_CONTRACT, options = {}) {
|
|
214
|
+
const typedContract = buildTypedStructuredCompactionContract(capsule);
|
|
215
|
+
const routing = capsule.node.routing;
|
|
216
|
+
const redact = options.redact !== false;
|
|
217
|
+
const safe = (value, maxChars = 1_200) => truncateChars(redactText(String(value ?? ""), redact), maxChars);
|
|
218
|
+
const requiredEvidence = capsule.evidenceRequirements
|
|
219
|
+
.filter((e) => e.required)
|
|
220
|
+
.map((e) => `${safe(e.gate, 120)}${e.ref ? `:${safe(e.ref, 240)}` : ""}`)
|
|
221
|
+
.join(", ") || "none";
|
|
222
|
+
const capabilities = routing?.assignedProviderCapabilities?.map((cap) => safe(cap, 120)).join(", ") || "none";
|
|
223
|
+
const hardSections = [
|
|
224
|
+
structuredCompactionInstruction(typedContract),
|
|
225
|
+
renderTypedStructuredCompactionContract(typedContract),
|
|
226
|
+
"",
|
|
227
|
+
"## task",
|
|
228
|
+
safe(capsule.task, 1_600),
|
|
229
|
+
"",
|
|
230
|
+
"## node routing",
|
|
231
|
+
[
|
|
232
|
+
`node=${safe(capsule.nodeId, 160)}`,
|
|
233
|
+
`role=${safe(capsule.node.role, 120)}`,
|
|
234
|
+
`provider=${safe(routing?.provider ?? "auto", 120)}`,
|
|
235
|
+
`risk=${safe(routing?.risk ?? "read", 120)}`,
|
|
236
|
+
`sandboxMode=${safe(routing?.sandboxMode ?? "unknown", 120)}`,
|
|
237
|
+
`readOnly=${routing?.readOnly === true ? "read-only" : "write-capable"}`,
|
|
238
|
+
`approvalPolicy=${safe(routing?.approvalPolicy ?? "unknown", 120)}`,
|
|
239
|
+
].join("; "),
|
|
240
|
+
"",
|
|
241
|
+
"## evidence requirements",
|
|
242
|
+
requiredEvidence,
|
|
243
|
+
"",
|
|
244
|
+
"## safety constraints",
|
|
245
|
+
[
|
|
246
|
+
"preserve safety constraints",
|
|
247
|
+
routing?.evidenceRequired ? "evidence required" : "evidence optional",
|
|
248
|
+
`goal=${safe(capsule.goal, 400)}`,
|
|
249
|
+
safe(capsule.system, 700),
|
|
250
|
+
].filter(Boolean).join("; "),
|
|
251
|
+
"",
|
|
252
|
+
"## capabilities",
|
|
253
|
+
capabilities,
|
|
254
|
+
].join("\n");
|
|
255
|
+
const targetTokens = Math.max(256, Math.floor(options.maxTokens ?? (capsule.budget.maxInputTokens - capsule.budget.reservedOutputTokens)));
|
|
256
|
+
const estimator = options.estimator;
|
|
257
|
+
const remainingTokens = Math.max(0, targetTokens - estimateTextTokens(hardSections, estimator));
|
|
258
|
+
const selected = selectCandidateSections(capsule, remainingTokens, options, redact);
|
|
259
|
+
return [hardSections, ...selected].join("\n");
|
|
260
|
+
}
|
|
261
|
+
function selectCandidateSections(capsule, budgetTokens, options, redact) {
|
|
262
|
+
const candidates = [];
|
|
263
|
+
const maxDeps = Math.max(0, options.maxDependencySummaries ?? 5);
|
|
264
|
+
for (const [index, summary] of capsule.dependencySummaries.slice(0, maxDeps).entries()) {
|
|
265
|
+
candidates.push({
|
|
266
|
+
heading: "dependency summaries",
|
|
267
|
+
text: redactText(summary, redact),
|
|
268
|
+
priority: 0.78 - index * 0.02,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const maxMemoryFacts = Math.max(0, options.maxMemoryFacts ?? capsule.budget.maxMemoryFacts);
|
|
272
|
+
for (const [index, fact] of capsule.graphMemory.slice(0, maxMemoryFacts).entries()) {
|
|
273
|
+
const text = redactText(`${fact.key}=${fact.value}`, redact);
|
|
274
|
+
candidates.push({
|
|
275
|
+
heading: "graph memory",
|
|
276
|
+
text,
|
|
277
|
+
priority: memoryPriority(fact.kind) - index * 0.01,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
for (const [index, file] of capsule.relevantFiles.slice(0, 5).entries()) {
|
|
281
|
+
candidates.push({
|
|
282
|
+
heading: "file summaries",
|
|
283
|
+
text: redactText(`${file.path}:${file.startLine}-${file.endLine}\n${truncateChars(file.content, 1_000)}`, redact),
|
|
284
|
+
priority: 0.62 - index * 0.02,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
for (const [index, attempt] of capsule.priorAttempts.slice(0, 5).entries()) {
|
|
288
|
+
candidates.push({
|
|
289
|
+
heading: "prior attempts",
|
|
290
|
+
text: redactText(`attempt=${attempt.attempt} provider=${attempt.provider} status=${attempt.status} durationMs=${attempt.durationMs ?? "?"} failure=${attempt.failureSummary ?? "none"}`, redact),
|
|
291
|
+
priority: 0.58 - index * 0.02,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const selected = {};
|
|
295
|
+
const sorted = candidates
|
|
296
|
+
.map((candidate) => ({ ...candidate, cost: Math.max(1, estimateTextTokens(candidate.text, options.estimator)) }))
|
|
297
|
+
.sort((a, b) => (b.priority / b.cost) - (a.priority / a.cost));
|
|
298
|
+
let remaining = budgetTokens;
|
|
299
|
+
for (const candidate of sorted) {
|
|
300
|
+
if (remaining <= 0)
|
|
301
|
+
break;
|
|
302
|
+
const cost = Math.max(1, estimateTextTokens(candidate.text, options.estimator));
|
|
303
|
+
const text = cost <= remaining
|
|
304
|
+
? candidate.text
|
|
305
|
+
: truncateToTokenBudget(candidate.text, remaining);
|
|
306
|
+
if (!text.trim())
|
|
307
|
+
continue;
|
|
308
|
+
selected[candidate.heading] ??= [];
|
|
309
|
+
selected[candidate.heading].push(text);
|
|
310
|
+
remaining -= estimateTextTokens(text, options.estimator);
|
|
311
|
+
}
|
|
312
|
+
const headings = ["dependency summaries", "graph memory", "file summaries", "prior attempts"];
|
|
313
|
+
return headings.map((heading) => {
|
|
314
|
+
const lines = selected[heading];
|
|
315
|
+
return ["", `## ${heading}`, lines?.length ? lines.join(" | ") : "none"].join("\n");
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function memoryPriority(kind) {
|
|
319
|
+
switch (kind) {
|
|
320
|
+
case "failure_pattern":
|
|
321
|
+
case "provider_behavior":
|
|
322
|
+
return 0.72;
|
|
323
|
+
case "architecture_decision":
|
|
324
|
+
case "api_contract":
|
|
325
|
+
return 0.66;
|
|
326
|
+
default:
|
|
327
|
+
return 0.54;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export function estimateTextTokens(text, options = {}) {
|
|
331
|
+
const base = Math.ceil(text.length / 4);
|
|
332
|
+
if (base <= 0)
|
|
333
|
+
return 0;
|
|
334
|
+
const hangulChars = text.match(/[\u3131-\u318E\uAC00-\uD7A3]/g)?.length ?? 0;
|
|
335
|
+
const hangulRatio = hangulChars / Math.max(1, text.length);
|
|
336
|
+
const hasJsonOrCode = /```|[{}[\]\s\S]*?:[\s\S]*?[}]|\b(function|class|interface|const|let|import|export)\b/.test(text);
|
|
337
|
+
const languageFactor = hangulRatio > 0.1 ? 1.25 : 1;
|
|
338
|
+
const structureFactor = hasJsonOrCode ? 1.10 : 1;
|
|
339
|
+
const multiplier = options.calibration?.multiplier ?? 1;
|
|
340
|
+
const bias = options.calibration?.bias ?? 0;
|
|
341
|
+
return Math.max(0, Math.ceil(base * languageFactor * structureFactor * multiplier + bias));
|
|
342
|
+
}
|
|
343
|
+
export function computeCompactionQualityScore(input) {
|
|
344
|
+
if (!input.applied) {
|
|
345
|
+
return {
|
|
346
|
+
qualityScore: 0,
|
|
347
|
+
compressionRatio: null,
|
|
348
|
+
contractScore: 0,
|
|
349
|
+
compressionScore: 0,
|
|
350
|
+
evidenceScore: 0,
|
|
351
|
+
safetyScore: 0,
|
|
352
|
+
capabilityScore: 0,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const before = Math.max(1, input.beforeTokens ?? 0);
|
|
356
|
+
const after = Math.max(0, input.afterTokens ?? 0);
|
|
357
|
+
const compressionRatio = after / before;
|
|
358
|
+
const compressionScore = clamp01(1 - compressionRatio);
|
|
359
|
+
const missing = new Set(input.missingSections ?? []);
|
|
360
|
+
const contractScore = input.validated ? 1 : 0;
|
|
361
|
+
const evidenceScore = missing.has("evidence requirements") || missing.has("typed evidence requirements") ? 0 : 1;
|
|
362
|
+
const safetyScore = missing.has("safety constraints") || missing.has("typed safety evidenceRequired") || missing.has("typed safety preserve") ? 0 : 1;
|
|
363
|
+
const capabilityScore = missing.has("capabilities") || missing.has("typed capabilities") ? 0 : 1;
|
|
364
|
+
const qualityScore = clamp01(0.35 * contractScore
|
|
365
|
+
+ 0.25 * compressionScore
|
|
366
|
+
+ 0.15 * evidenceScore
|
|
367
|
+
+ 0.15 * safetyScore
|
|
368
|
+
+ 0.10 * capabilityScore);
|
|
369
|
+
return {
|
|
370
|
+
qualityScore,
|
|
371
|
+
compressionRatio,
|
|
372
|
+
contractScore,
|
|
373
|
+
compressionScore,
|
|
374
|
+
evidenceScore,
|
|
375
|
+
safetyScore,
|
|
376
|
+
capabilityScore,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function clamp01(value) {
|
|
380
|
+
return Math.max(0, Math.min(1, value));
|
|
381
|
+
}
|
|
382
|
+
function truncateToTokenBudget(text, budgetTokens) {
|
|
383
|
+
const maxChars = Math.max(0, budgetTokens * 4);
|
|
384
|
+
return truncateChars(text, maxChars);
|
|
385
|
+
}
|
|
386
|
+
function truncateChars(text, maxChars) {
|
|
387
|
+
if (maxChars <= 0)
|
|
388
|
+
return "";
|
|
389
|
+
if (text.length <= maxChars)
|
|
390
|
+
return text;
|
|
391
|
+
if (maxChars <= 16)
|
|
392
|
+
return text.slice(0, maxChars);
|
|
393
|
+
return `${text.slice(0, maxChars - 16)}…[truncated]`;
|
|
394
|
+
}
|
|
395
|
+
function redactText(text, redact) {
|
|
396
|
+
return redact ? maskSensitiveText(text) : text;
|
|
397
|
+
}
|
|
398
|
+
function sameStringSet(a, b) {
|
|
399
|
+
const left = new Set(a);
|
|
400
|
+
const right = new Set(b);
|
|
401
|
+
if (left.size !== right.size)
|
|
402
|
+
return false;
|
|
403
|
+
for (const value of left)
|
|
404
|
+
if (!right.has(value))
|
|
405
|
+
return false;
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
function sameEvidence(a, b) {
|
|
409
|
+
const key = (item) => `${item.gate}\u0000${item.ref ?? ""}`;
|
|
410
|
+
return sameStringSet(a.map(key), b.map(key));
|
|
411
|
+
}
|
|
412
|
+
function isRecord(value) {
|
|
413
|
+
return typeof value === "object" && value !== null;
|
|
414
|
+
}
|
|
@@ -8,8 +8,8 @@ export interface ToolDispatchResult<R = unknown> {
|
|
|
8
8
|
readonly value?: R;
|
|
9
9
|
readonly reason?: unknown;
|
|
10
10
|
}
|
|
11
|
-
/** Shadow = record only; enforce = a block/ask(non-TTY) verdict rejects the call. */
|
|
12
|
-
export type ToolAuthorityMode = "shadow" | "enforce";
|
|
11
|
+
/** Shadow = record only; warn = visible diagnostic; enforce = a block/ask(non-TTY) verdict rejects the call. */
|
|
12
|
+
export type ToolAuthorityMode = "shadow" | "warn" | "enforce";
|
|
13
13
|
/**
|
|
14
14
|
* Per-call verdict recorded at the dispatch checkpoint. Carries only coarse,
|
|
15
15
|
* non-secret signals (op class, authority levels, policy) — never tool args.
|
|
@@ -68,10 +68,12 @@ export interface ToolAuthorityWiring {
|
|
|
68
68
|
readonly resolveWritePath?: (call: OmkToolCall) => string | undefined;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Resolve
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* Resolve staged authority mode from the environment. Default OFF means the gate
|
|
72
|
+
* runs in shadow mode (record only). `warn` emits visible diagnostics without
|
|
73
|
+
* blocking. `enforce` fail-closes block/ask(non-TTY) verdicts.
|
|
74
74
|
*/
|
|
75
|
+
export declare function resolveToolAuthorityMode(env?: Record<string, string | undefined>): ToolAuthorityMode;
|
|
76
|
+
/** Backward-compatible boolean resolver for existing dispatch call sites. */
|
|
75
77
|
export declare function resolveToolAuthorityEnforcement(env?: Record<string, string | undefined>): boolean;
|
|
76
78
|
/** Error used to reject a tool call rejected by the authority gate (enforce mode). */
|
|
77
79
|
export declare class ToolAuthorityBlockedError extends Error {
|
|
@@ -3,12 +3,23 @@ import { decideToolAuthority, decideToolAuthorityV2, mapToolNameToOp, } from "..
|
|
|
3
3
|
import { assertWritable } from "./sandbox-profile.js";
|
|
4
4
|
const ENFORCE_PATTERN = /^(1|true|yes|on)$/i;
|
|
5
5
|
/**
|
|
6
|
-
* Resolve
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Resolve staged authority mode from the environment. Default OFF means the gate
|
|
7
|
+
* runs in shadow mode (record only). `warn` emits visible diagnostics without
|
|
8
|
+
* blocking. `enforce` fail-closes block/ask(non-TTY) verdicts.
|
|
9
9
|
*/
|
|
10
|
+
export function resolveToolAuthorityMode(env = process.env) {
|
|
11
|
+
const rawMode = (env.OMK_TOOL_AUTHORITY_MODE ?? "").trim().toLowerCase();
|
|
12
|
+
if (rawMode === "enforce" || rawMode === "warn" || rawMode === "shadow")
|
|
13
|
+
return rawMode;
|
|
14
|
+
if (ENFORCE_PATTERN.test((env.OMK_TOOL_AUTHORITY_ENFORCE ?? "").trim()))
|
|
15
|
+
return "enforce";
|
|
16
|
+
if (ENFORCE_PATTERN.test((env.OMK_TOOL_AUTHORITY_WARN ?? "").trim()))
|
|
17
|
+
return "warn";
|
|
18
|
+
return "shadow";
|
|
19
|
+
}
|
|
20
|
+
/** Backward-compatible boolean resolver for existing dispatch call sites. */
|
|
10
21
|
export function resolveToolAuthorityEnforcement(env = process.env) {
|
|
11
|
-
return
|
|
22
|
+
return resolveToolAuthorityMode(env) === "enforce";
|
|
12
23
|
}
|
|
13
24
|
/** Build a redacted reason string from non-secret authority signals only. */
|
|
14
25
|
function redactedAuthorityReason(op, decision, wiring) {
|
|
@@ -2,7 +2,7 @@ import type { OmkRuntimeScope } from "../util/resource-profile.js";
|
|
|
2
2
|
import { type OmkToolPrefixSpec } from "./tool-registry-contract.js";
|
|
3
3
|
export interface OmkToolPlaneDiagnostic {
|
|
4
4
|
readonly level: "warning" | "error";
|
|
5
|
-
readonly code: "mcp_config_parse_failed" | "mcp_config_read_failed" | "runtime_mcp_config_parse_failed" | "runtime_mcp_required_unavailable";
|
|
5
|
+
readonly code: "mcp_config_parse_failed" | "mcp_config_read_failed" | "runtime_mcp_config_parse_failed" | "runtime_mcp_required_unavailable" | "skill_reference_unknown" | "skill_config_read_failed" | "hook_reference_unknown" | "hook_config_read_failed";
|
|
6
6
|
readonly path?: string;
|
|
7
7
|
readonly message: string;
|
|
8
8
|
}
|
|
@@ -7,7 +7,9 @@ export async function buildOmkToolPlaneManifest(input) {
|
|
|
7
7
|
const requiresRuntimeMcp = input.requiresRuntimeMcp === true;
|
|
8
8
|
const resolved = await resolveRuntimeMcpConfigFile(input.mcpScope, input.mcpAllowlist);
|
|
9
9
|
const runtimeRead = resolved.mcpConfigFile ? await readMcpServerNames(resolved.mcpConfigFile) : { servers: [], diagnostics: [] };
|
|
10
|
-
const
|
|
10
|
+
const skillDiagnostics = await validateSkillReferences(input.skills ?? []);
|
|
11
|
+
const hookDiagnostics = await validateHookReferences(input.hooks ?? []);
|
|
12
|
+
const diagnostics = [...resolved.diagnostics, ...runtimeRead.diagnostics, ...skillDiagnostics, ...hookDiagnostics];
|
|
11
13
|
if (requiresRuntimeMcp && (diagnostics.some((diagnostic) => diagnostic.level === "error") || !resolved.mcpConfigFile || runtimeRead.servers.length === 0)) {
|
|
12
14
|
throw new Error(formatRequiredRuntimeMcpError(diagnostics, resolved.mcpConfigFile, runtimeRead.servers));
|
|
13
15
|
}
|
|
@@ -97,6 +99,52 @@ function mcpDiagnosticMessage(err) {
|
|
|
97
99
|
}
|
|
98
100
|
return "invalid MCP config";
|
|
99
101
|
}
|
|
102
|
+
async function validateSkillReferences(skills) {
|
|
103
|
+
if (skills.length === 0)
|
|
104
|
+
return [];
|
|
105
|
+
const diagnostics = [];
|
|
106
|
+
const projectRoot = resolve(await getProjectRootAsync());
|
|
107
|
+
for (const skill of skills) {
|
|
108
|
+
const safeName = skill.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
109
|
+
if (safeName !== skill) {
|
|
110
|
+
diagnostics.push({ level: "warning", code: "skill_reference_unknown", message: `skill name contains unsafe characters: ${skill}` });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const paths = [
|
|
114
|
+
resolve(projectRoot, ".agents/skills", safeName, "SKILL.md"),
|
|
115
|
+
resolve(projectRoot, ".kimi/skills", safeName, "SKILL.md"),
|
|
116
|
+
resolve(getUserHome(), ".omk/agent/skills", safeName, "SKILL.md"),
|
|
117
|
+
];
|
|
118
|
+
const found = await Promise.all(paths.map((p) => readFile(p, "utf-8").then(() => true).catch(() => false)));
|
|
119
|
+
if (!found.some(Boolean)) {
|
|
120
|
+
diagnostics.push({ level: "warning", code: "skill_reference_unknown", message: `skill not found in project or user skill paths: ${skill}` });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return diagnostics;
|
|
124
|
+
}
|
|
125
|
+
async function validateHookReferences(hooks) {
|
|
126
|
+
if (hooks.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const diagnostics = [];
|
|
129
|
+
const projectRoot = resolve(await getProjectRootAsync());
|
|
130
|
+
for (const hook of hooks) {
|
|
131
|
+
const safeName = hook.replace(/[^a-zA-Z0-9_.-]/g, "");
|
|
132
|
+
if (safeName !== hook) {
|
|
133
|
+
diagnostics.push({ level: "warning", code: "hook_reference_unknown", message: `hook name contains unsafe characters: ${hook}` });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const paths = [
|
|
137
|
+
resolve(projectRoot, ".agents/hooks", safeName),
|
|
138
|
+
resolve(projectRoot, ".kimi/hooks", safeName),
|
|
139
|
+
resolve(getUserHome(), ".omk/agent/hooks", safeName),
|
|
140
|
+
];
|
|
141
|
+
const found = await Promise.all(paths.map((p) => readFile(p, "utf-8").then(() => true).catch(() => false)));
|
|
142
|
+
if (!found.some(Boolean)) {
|
|
143
|
+
diagnostics.push({ level: "warning", code: "hook_reference_unknown", message: `hook not found in project or user hook paths: ${hook}` });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return diagnostics;
|
|
147
|
+
}
|
|
100
148
|
function formatRequiredRuntimeMcpError(diagnostics, mcpConfigFile, servers) {
|
|
101
149
|
const errors = diagnostics
|
|
102
150
|
.filter((diagnostic) => diagnostic.level === "error")
|
|
@@ -138,7 +138,7 @@ function deriveWorkerCapabilities(node, toolPlane) {
|
|
|
138
138
|
const role = node.role?.toLowerCase() ?? "";
|
|
139
139
|
const gates = node.outputs?.map((output) => output.gate).filter(Boolean) ?? [];
|
|
140
140
|
const assigned = new Set(routing?.assignedProviderCapabilities ?? []);
|
|
141
|
-
const readOnly = routing?.readOnly === true;
|
|
141
|
+
const readOnly = routing?.readOnly === true || routing?.freedomd?.degradedMode === "read-only-local-review";
|
|
142
142
|
const merge = !readOnly && (assigned.has("merge") || role === "merger" || role === "integrator" || role === "orchestrator");
|
|
143
143
|
const write = !readOnly && (assigned.has("write") || merge || role === "coder" || role === "executor" || role === "refactorer");
|
|
144
144
|
const shell = !readOnly && (assigned.has("shell") || routing?.requiresToolCalling === true || gates.includes("command-pass") || gates.includes("test-pass"));
|