open-multi-agent-kit 0.78.1 → 0.78.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 +12 -0
- package/MATURITY.md +4 -0
- package/README.md +70 -1
- package/dist/cli/register-spec-agent-goal-commands.js +45 -0
- package/dist/cli/release-promotion-gate.d.ts +14 -0
- package/dist/cli/release-promotion-gate.js +71 -0
- package/dist/cli/v2/release-commands.d.ts +29 -0
- package/dist/cli/v2/release-commands.js +95 -0
- package/dist/commands/chat/native-root-loop.js +14 -1
- package/dist/commands/chat/slash/commands/session.js +19 -1
- package/dist/commands/goal-interview.d.ts +18 -0
- package/dist/commands/goal-interview.js +396 -0
- package/dist/contracts/interview.d.ts +106 -0
- package/dist/contracts/interview.js +9 -0
- package/dist/evidence/index.d.ts +4 -0
- package/dist/evidence/index.js +2 -0
- package/dist/evidence/proof-trust-cli.d.ts +8 -0
- package/dist/evidence/proof-trust-cli.js +27 -0
- package/dist/evidence/proof-trust.d.ts +14 -0
- package/dist/evidence/proof-trust.js +381 -0
- package/dist/evidence/regression-proof-matrix.d.ts +42 -0
- package/dist/evidence/regression-proof-matrix.js +72 -0
- package/dist/goal/intent-frame.d.ts +6 -0
- package/dist/goal/intent-frame.js +21 -9
- package/dist/goal/interview-assimilation.d.ts +13 -0
- package/dist/goal/interview-assimilation.js +383 -0
- package/dist/goal/interview-question-bank.d.ts +11 -0
- package/dist/goal/interview-question-bank.js +225 -0
- package/dist/goal/interview-scoring.d.ts +31 -0
- package/dist/goal/interview-scoring.js +187 -0
- package/dist/goal/interview-session.d.ts +25 -0
- package/dist/goal/interview-session.js +116 -0
- package/dist/input/input-envelope.d.ts +22 -0
- package/dist/input/input-envelope.js +1 -0
- package/dist/runtime/advanced-control-loop.d.ts +60 -0
- package/dist/runtime/advanced-control-loop.js +136 -0
- package/dist/runtime/agent-runtime.d.ts +10 -0
- package/dist/runtime/blast-radius.d.ts +10 -0
- package/dist/runtime/blast-radius.js +14 -0
- package/dist/runtime/contracts/evidence.d.ts +87 -0
- package/dist/runtime/contracts/evidence.js +7 -0
- package/dist/runtime/contracts/router-v2.d.ts +44 -0
- package/dist/runtime/contracts/router-v2.js +4 -0
- package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
- package/dist/runtime/contracts/weakness-remediation.js +36 -0
- package/dist/runtime/kimi-api-runtime.js +59 -1
- package/dist/runtime/proof-bundle-trust.d.ts +74 -0
- package/dist/runtime/proof-bundle-trust.js +100 -0
- package/dist/runtime/provider-maturity-gate.d.ts +41 -0
- package/dist/runtime/provider-maturity-gate.js +101 -0
- package/dist/runtime/public-surface.d.ts +93 -0
- package/dist/runtime/public-surface.js +146 -0
- package/dist/runtime/router-v2-scoring.d.ts +11 -0
- package/dist/runtime/router-v2-scoring.js +151 -0
- package/dist/runtime/weakness-remediation-index.d.ts +27 -0
- package/dist/runtime/weakness-remediation-index.js +37 -0
- package/dist/schema/proof-bundle.schema.d.ts +26 -26
- package/dist/util/clipboard-image.d.ts +49 -0
- package/dist/util/clipboard-image.js +263 -0
- package/docs/2026-06-09/critical-issues.md +20 -0
- package/docs/2026-06-09/improvements.md +14 -0
- package/docs/2026-06-09/init-checklist.md +25 -0
- package/docs/2026-06-09/plan.md +20 -0
- package/docs/github-organic-promotion.md +127 -0
- package/docs/native-root-runtime-algorithms.md +301 -0
- package/package.json +4 -3
- package/readmeasset/ASSET_INDEX.md +1 -0
- package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
- package/templates/skills/kimi/omk-agent-reach-websearch/SKILL.md +55 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Module: src/goal/interview-scoring.ts
|
|
2
|
+
// OMK Deep Interview — pure deterministic scoring/ranking.
|
|
3
|
+
//
|
|
4
|
+
// 100% deterministic: no I/O, no Date.now, no randomness, no network/LLM calls.
|
|
5
|
+
// All 0..1 outputs are clamped to [0,1] and scores rounded to 2 decimals.
|
|
6
|
+
const clamp01 = (x) => Math.min(1, Math.max(0, x));
|
|
7
|
+
const round2 = (x) => Math.round(x * 100) / 100;
|
|
8
|
+
/** Max questions surfaced per interview depth. */
|
|
9
|
+
export const DEPTH_LIMITS = {
|
|
10
|
+
light: 5,
|
|
11
|
+
standard: 10,
|
|
12
|
+
deep: 18,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Derive the ranking score for a candidate question.
|
|
16
|
+
* score = informationGain*0.35 + riskReduction*0.25 + dagImpact*0.20
|
|
17
|
+
* + evidenceImpact*0.15 - userCost*0.05, clamped to [0,1].
|
|
18
|
+
*/
|
|
19
|
+
export function scoreInterviewQuestion(input) {
|
|
20
|
+
const raw = input.informationGain * 0.35 +
|
|
21
|
+
input.riskReduction * 0.25 +
|
|
22
|
+
input.dagImpact * 0.2 +
|
|
23
|
+
input.evidenceImpact * 0.15 -
|
|
24
|
+
input.userCost * 0.05;
|
|
25
|
+
const score = round2(clamp01(raw));
|
|
26
|
+
return { ...input, score };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Select the highest-value questions for a depth, dropping questions whose
|
|
30
|
+
* target field is already answered by the goal (unless marked required).
|
|
31
|
+
*/
|
|
32
|
+
export function selectInterviewQuestions(goal, candidates, depth, maxQuestions) {
|
|
33
|
+
const limit = maxQuestions != null ? Math.min(maxQuestions, DEPTH_LIMITS[depth]) : DEPTH_LIMITS[depth];
|
|
34
|
+
const answeredTargets = new Set();
|
|
35
|
+
if (goal) {
|
|
36
|
+
if (goal.objective?.trim())
|
|
37
|
+
answeredTargets.add("objective");
|
|
38
|
+
if (goal.successCriteria.length > 0)
|
|
39
|
+
answeredTargets.add("successCriteria");
|
|
40
|
+
if (goal.expectedArtifacts.length > 0)
|
|
41
|
+
answeredTargets.add("expectedArtifacts");
|
|
42
|
+
if (goal.constraints.length > 0)
|
|
43
|
+
answeredTargets.add("constraints");
|
|
44
|
+
if (goal.nonGoals.length > 0)
|
|
45
|
+
answeredTargets.add("nonGoals");
|
|
46
|
+
if (goal.risks.length > 0)
|
|
47
|
+
answeredTargets.add("risks");
|
|
48
|
+
}
|
|
49
|
+
const filtered = candidates.filter((q) => q.required || !answeredTargets.has(q.targetField));
|
|
50
|
+
const byScoreThenRequired = (a, b) => {
|
|
51
|
+
const byScore = b.score - a.score;
|
|
52
|
+
if (byScore !== 0)
|
|
53
|
+
return byScore;
|
|
54
|
+
return Number(b.required) - Number(a.required);
|
|
55
|
+
};
|
|
56
|
+
// Pin required questions so a small --max-questions/limit can never drop a
|
|
57
|
+
// required axis; optional questions fill the remaining slots by score.
|
|
58
|
+
const required = filtered.filter((q) => q.required).sort(byScoreThenRequired);
|
|
59
|
+
const optional = filtered.filter((q) => !q.required).sort(byScoreThenRequired);
|
|
60
|
+
const remaining = Math.max(0, limit - required.length);
|
|
61
|
+
return [...required, ...optional.slice(0, remaining)].sort(byScoreThenRequired);
|
|
62
|
+
}
|
|
63
|
+
/** Keyword/regex heuristics describing one ambiguity axis. */
|
|
64
|
+
const AMBIGUITY_AXES = [
|
|
65
|
+
{
|
|
66
|
+
weight: 0.18,
|
|
67
|
+
pattern: /\b(objective|goal|implement|build|create|add|fix|refactor|design|develop|generate|support|enable)\b/,
|
|
68
|
+
goalPresent: (s) => !!s.goal?.objective?.trim(),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
weight: 0.18,
|
|
72
|
+
pattern: /\b(acceptance|success criteria|criteria|criterion|definition of done|done when|must pass|requirement|expected result|should)\b/,
|
|
73
|
+
goalPresent: (s) => (s.goal?.successCriteria.length ?? 0) > 0,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
weight: 0.13,
|
|
77
|
+
pattern: /\b(test|verify|verification|check|lint|typecheck|build|coverage|assert|validate)\b/,
|
|
78
|
+
goalPresent: (s) => (s.goal?.expectedArtifacts.some((a) => a.gate === "command-pass") ?? false),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
weight: 0.12,
|
|
82
|
+
pattern: /(\.[a-z]{2,4}\b|\bfile\b|\bpath\b|\bmodule\b|\bcomponent\b|\bdirectory\b|\bartifact\b|\boutput\b)/,
|
|
83
|
+
goalPresent: (s) => (s.goal?.expectedArtifacts.length ?? 0) > 0,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
weight: 0.1,
|
|
87
|
+
pattern: /\b(constraint|must not|only|do not|don't|limit|restrict|without|avoid|never|forbidden)\b/,
|
|
88
|
+
goalPresent: (s) => (s.goal?.constraints.length ?? 0) > 0,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
weight: 0.08,
|
|
92
|
+
pattern: /\b(risk|danger|safe|safety|destructive|irreversible|production|rollback|backup|prod)\b/,
|
|
93
|
+
goalPresent: (s) => (s.goal?.risks.length ?? 0) > 0 || s.riskLevel != null,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
weight: 0.08,
|
|
97
|
+
pattern: /\b(write|edit|modify|delete|shell|command|merge|commit|push|deploy|permission|read.only|authority|scope)\b/,
|
|
98
|
+
goalPresent: (s) => !!s.goal?.intentFrame?.directives?.some((d) => d.kind === "read-only" || d.kind === "no-edits" || d.kind === "scope"),
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
weight: 0.07,
|
|
102
|
+
pattern: /\b(non-goal|out of scope|exclude|except|not (include|touch|modify|change|do)|do not (edit|modify|change|touch))\b/,
|
|
103
|
+
goalPresent: (s) => (s.goal?.nonGoals.length ?? 0) > 0,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
weight: 0.06,
|
|
107
|
+
pattern: /\b(depend|dependenc|requires|require|prerequisite|blocked by|relies on|integrat|import)\b/,
|
|
108
|
+
goalPresent: () => false,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
/**
|
|
112
|
+
* Ambiguity 0..1 — higher means more interview is needed. Computed as the
|
|
113
|
+
* weighted sum of axes that are neither described in the prompt nor present on
|
|
114
|
+
* the goal. Axis weights sum to 1.
|
|
115
|
+
*/
|
|
116
|
+
export function computeAmbiguity(seed) {
|
|
117
|
+
const prompt = (seed.rawPrompt ?? "").toLowerCase();
|
|
118
|
+
let missing = 0;
|
|
119
|
+
for (const axis of AMBIGUITY_AXES) {
|
|
120
|
+
const present = axis.pattern.test(prompt) || axis.goalPresent(seed);
|
|
121
|
+
if (!present)
|
|
122
|
+
missing += axis.weight;
|
|
123
|
+
}
|
|
124
|
+
return round2(clamp01(missing));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Map an ambiguity score to a recommended depth.
|
|
128
|
+
* <0.25 light (caller may skip) · <0.50 light · <0.75 standard · else deep.
|
|
129
|
+
*/
|
|
130
|
+
export function recommendDepth(ambiguity) {
|
|
131
|
+
if (ambiguity < 0.5)
|
|
132
|
+
return "light";
|
|
133
|
+
if (ambiguity < 0.75)
|
|
134
|
+
return "standard";
|
|
135
|
+
return "deep";
|
|
136
|
+
}
|
|
137
|
+
/** Per-axis completeness: 0.6 if goal field populated, +0.4 if a finding matches. */
|
|
138
|
+
function axisScore(goalPopulated, fields, findings) {
|
|
139
|
+
const hasFinding = findings.some((f) => fields.includes(String(f.field)));
|
|
140
|
+
let score = 0;
|
|
141
|
+
if (goalPopulated)
|
|
142
|
+
score += 0.6;
|
|
143
|
+
if (hasFinding)
|
|
144
|
+
score += 0.4;
|
|
145
|
+
return round2(clamp01(score));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Completeness across required and supporting axes, blending populated goal
|
|
149
|
+
* fields with assimilated interview findings.
|
|
150
|
+
*/
|
|
151
|
+
export function computeCompleteness(goal, findings) {
|
|
152
|
+
const objective = axisScore(!!goal?.objective?.trim(), ["objective"], findings);
|
|
153
|
+
const successCriteria = axisScore((goal?.successCriteria.length ?? 0) > 0, ["successCriteria"], findings);
|
|
154
|
+
const evidence = axisScore((goal?.expectedArtifacts.some((a) => !!a.gate) ?? false) ||
|
|
155
|
+
(goal?.successCriteria.some((c) => c.requirement === "required") ?? false), ["evidence"], findings);
|
|
156
|
+
const artifacts = axisScore((goal?.expectedArtifacts.length ?? 0) > 0, ["expectedArtifacts", "artifact", "artifacts"], findings);
|
|
157
|
+
const constraints = axisScore((goal?.constraints.length ?? 0) > 0, ["constraints"], findings);
|
|
158
|
+
const risks = axisScore((goal?.risks.length ?? 0) > 0, ["risks", "riskLevel"], findings);
|
|
159
|
+
const authority = axisScore(!!goal?.intentFrame?.directives?.some((d) => d.kind === "read-only" || d.kind === "no-edits" || d.kind === "scope"), ["authority", "intentFrame"], findings);
|
|
160
|
+
const overall = round2(clamp01(objective * 0.15 +
|
|
161
|
+
successCriteria * 0.25 +
|
|
162
|
+
evidence * 0.2 +
|
|
163
|
+
artifacts * 0.15 +
|
|
164
|
+
constraints * 0.1 +
|
|
165
|
+
risks * 0.1 +
|
|
166
|
+
authority * 0.05));
|
|
167
|
+
const criticalMissing = [];
|
|
168
|
+
if (objective < 0.5)
|
|
169
|
+
criticalMissing.push("objective");
|
|
170
|
+
if (successCriteria < 0.5)
|
|
171
|
+
criticalMissing.push("successCriteria");
|
|
172
|
+
if (evidence < 0.5)
|
|
173
|
+
criticalMissing.push("evidence");
|
|
174
|
+
const contradictions = findings.filter((f) => f.conflict).map((f) => String(f.field));
|
|
175
|
+
return {
|
|
176
|
+
overall,
|
|
177
|
+
objective,
|
|
178
|
+
successCriteria,
|
|
179
|
+
evidence,
|
|
180
|
+
artifacts,
|
|
181
|
+
constraints,
|
|
182
|
+
risks,
|
|
183
|
+
authority,
|
|
184
|
+
criticalMissing,
|
|
185
|
+
contradictions,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InterviewAnswer, InterviewDepth, InterviewMode, InterviewSeed, InterviewSession } from "../contracts/interview.js";
|
|
2
|
+
/** Completeness threshold above which the interview is considered done. */
|
|
3
|
+
export declare const COMPLETENESS_THRESHOLD = 0.82;
|
|
4
|
+
export interface BuildInterviewSessionInput {
|
|
5
|
+
seed: InterviewSeed;
|
|
6
|
+
mode: InterviewMode;
|
|
7
|
+
depth?: InterviewDepth;
|
|
8
|
+
maxQuestions?: number;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
goalId?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build a fresh interview session: score ambiguity, rank deterministic
|
|
14
|
+
* questions, and emit an empty spec delta. No answers are applied yet.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildInterviewSession(input: BuildInterviewSessionInput): InterviewSession;
|
|
17
|
+
/**
|
|
18
|
+
* Apply a batch of answers to an open session: assimilate into findings and a
|
|
19
|
+
* spec delta, recompute completeness, and decide the terminal status.
|
|
20
|
+
*
|
|
21
|
+
* Termination (spec): complete when completeness >= 0.82 AND no critical
|
|
22
|
+
* missing field AND no unresolved contradiction. Contradictions => blocked.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ingestAnswers(session: InterviewSession, seed: InterviewSeed, newAnswers: InterviewAnswer[]): InterviewSession;
|
|
25
|
+
export declare function decideStatus(completeness: InterviewSession["completeness"]): InterviewSession["status"];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/goal/interview-session.ts
|
|
2
|
+
// OMK Deep Interview — session orchestrator.
|
|
3
|
+
//
|
|
4
|
+
// Composes the three deterministic modules (question-bank, scoring,
|
|
5
|
+
// assimilation) into an InterviewSession lifecycle:
|
|
6
|
+
// seed -> ambiguity -> question ranking -> answers -> findings
|
|
7
|
+
// -> spec delta -> completeness -> status
|
|
8
|
+
//
|
|
9
|
+
// This module owns the critical integration path and is intentionally
|
|
10
|
+
// deterministic except for timestamps and the session id.
|
|
11
|
+
import { INTERVIEW_SCHEMA_VERSION, INTERVIEW_DELTA_SCHEMA_VERSION } from "../contracts/interview.js";
|
|
12
|
+
import { computeAmbiguity, computeCompleteness, recommendDepth, scoreInterviewQuestion, selectInterviewQuestions, } from "./interview-scoring.js";
|
|
13
|
+
import { buildInterviewQuestionBank } from "./interview-question-bank.js";
|
|
14
|
+
import { assimilateAnswers, applyInterviewDelta } from "./interview-assimilation.js";
|
|
15
|
+
import { createGoalSpec } from "./intake.js";
|
|
16
|
+
import { redactSecretText } from "./intent-frame.js";
|
|
17
|
+
/** Completeness threshold above which the interview is considered done. */
|
|
18
|
+
export const COMPLETENESS_THRESHOLD = 0.82;
|
|
19
|
+
function generateSessionId() {
|
|
20
|
+
return `iv-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a fresh interview session: score ambiguity, rank deterministic
|
|
24
|
+
* questions, and emit an empty spec delta. No answers are applied yet.
|
|
25
|
+
*/
|
|
26
|
+
export function buildInterviewSession(input) {
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const ambiguity = computeAmbiguity(input.seed);
|
|
29
|
+
const depth = input.depth ?? recommendDepth(ambiguity);
|
|
30
|
+
const candidates = buildInterviewQuestionBank(input.seed);
|
|
31
|
+
const scored = candidates.map((candidate) => scoreInterviewQuestion(candidate));
|
|
32
|
+
const questions = selectInterviewQuestions(input.seed.goal, scored, depth, input.maxQuestions);
|
|
33
|
+
const completeness = computeCompleteness(input.seed.goal, []);
|
|
34
|
+
return {
|
|
35
|
+
schemaVersion: INTERVIEW_SCHEMA_VERSION,
|
|
36
|
+
sessionId: input.sessionId ?? generateSessionId(),
|
|
37
|
+
goalId: input.goalId ?? input.seed.goal?.goalId,
|
|
38
|
+
mode: input.mode,
|
|
39
|
+
depth,
|
|
40
|
+
createdAt: now,
|
|
41
|
+
updatedAt: now,
|
|
42
|
+
rawPrompt: redactSecretText(input.seed.rawPrompt),
|
|
43
|
+
ambiguity,
|
|
44
|
+
questions,
|
|
45
|
+
answers: [],
|
|
46
|
+
findings: [],
|
|
47
|
+
completeness,
|
|
48
|
+
specDelta: {
|
|
49
|
+
schemaVersion: INTERVIEW_DELTA_SCHEMA_VERSION,
|
|
50
|
+
goalId: input.goalId ?? input.seed.goal?.goalId,
|
|
51
|
+
changes: [],
|
|
52
|
+
},
|
|
53
|
+
status: "open",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Apply a batch of answers to an open session: assimilate into findings and a
|
|
58
|
+
* spec delta, recompute completeness, and decide the terminal status.
|
|
59
|
+
*
|
|
60
|
+
* Termination (spec): complete when completeness >= 0.82 AND no critical
|
|
61
|
+
* missing field AND no unresolved contradiction. Contradictions => blocked.
|
|
62
|
+
*/
|
|
63
|
+
export function ingestAnswers(session, seed, newAnswers) {
|
|
64
|
+
// Redact secrets from answers before they are assimilated, persisted, or
|
|
65
|
+
// echoed so tokens never reach interview artifacts or the GoalSpec.
|
|
66
|
+
const redactedAnswers = newAnswers.map((answer) => ({
|
|
67
|
+
...answer,
|
|
68
|
+
answer: redactSecretText(answer.answer),
|
|
69
|
+
}));
|
|
70
|
+
const mergedAnswers = mergeAnswers(session.answers, redactedAnswers);
|
|
71
|
+
const { findings, specDelta, contradictions } = assimilateAnswers({
|
|
72
|
+
seed,
|
|
73
|
+
questions: session.questions,
|
|
74
|
+
answers: mergedAnswers,
|
|
75
|
+
goal: seed.goal,
|
|
76
|
+
});
|
|
77
|
+
// Completeness is measured against the PROJECTED goal (base + delta) so the
|
|
78
|
+
// score reflects what the answers actually contribute to the GoalSpec, not
|
|
79
|
+
// the empty seed goal. This makes the 0.82 termination threshold meaningful.
|
|
80
|
+
const baseGoal = seed.goal ?? createGoalSpec(seed.rawPrompt);
|
|
81
|
+
const projected = applyInterviewDelta(baseGoal, specDelta).goal;
|
|
82
|
+
const completeness = computeCompleteness(projected, findings);
|
|
83
|
+
completeness.contradictions = uniqueStrings([...completeness.contradictions, ...contradictions]);
|
|
84
|
+
const status = decideStatus(completeness);
|
|
85
|
+
return {
|
|
86
|
+
...session,
|
|
87
|
+
updatedAt: new Date().toISOString(),
|
|
88
|
+
answers: mergedAnswers,
|
|
89
|
+
findings,
|
|
90
|
+
completeness,
|
|
91
|
+
specDelta: {
|
|
92
|
+
...specDelta,
|
|
93
|
+
goalId: session.goalId ?? specDelta.goalId,
|
|
94
|
+
},
|
|
95
|
+
status,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function decideStatus(completeness) {
|
|
99
|
+
if (completeness.contradictions.length > 0)
|
|
100
|
+
return "blocked";
|
|
101
|
+
if (completeness.overall >= COMPLETENESS_THRESHOLD && completeness.criticalMissing.length === 0) {
|
|
102
|
+
return "complete";
|
|
103
|
+
}
|
|
104
|
+
return "open";
|
|
105
|
+
}
|
|
106
|
+
function mergeAnswers(existing, incoming) {
|
|
107
|
+
const byId = new Map();
|
|
108
|
+
for (const answer of existing)
|
|
109
|
+
byId.set(answer.questionId, answer);
|
|
110
|
+
for (const answer of incoming)
|
|
111
|
+
byId.set(answer.questionId, answer);
|
|
112
|
+
return [...byId.values()];
|
|
113
|
+
}
|
|
114
|
+
function uniqueStrings(values) {
|
|
115
|
+
return [...new Set(values.filter((value) => value.length > 0))];
|
|
116
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
export type InputKind = "plain-prompt" | "slash-command" | "goal-form" | "resume" | "verify" | "replan";
|
|
2
|
+
/**
|
|
3
|
+
* Image or file attached to a prompt (clipboard paste, --image flag, drag).
|
|
4
|
+
* Carries both the on-disk path and the base64 data URI for multimodal
|
|
5
|
+
* wire protocol use (image_url parts).
|
|
6
|
+
*/
|
|
7
|
+
export interface InputAttachment {
|
|
8
|
+
/** Original file name or "clipboard-image.png". */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Absolute or project-relative path to the saved file. */
|
|
11
|
+
path: string;
|
|
12
|
+
/** MIME type: image/png, image/jpeg, image/webp, image/gif. */
|
|
13
|
+
mimeType: string;
|
|
14
|
+
/** Base64 data URI (data:image/png;base64,...) for wire protocol. */
|
|
15
|
+
dataUri: string;
|
|
16
|
+
/** Detected extension: png, jpg, webp, gif. */
|
|
17
|
+
ext: string;
|
|
18
|
+
/** Source of the attachment. */
|
|
19
|
+
source: "clipboard" | "file" | "drag";
|
|
20
|
+
}
|
|
2
21
|
export type InputSource = "chat" | "parallel" | "run" | "goal" | "api";
|
|
3
22
|
export type InputMcpScope = "all" | "project" | "none";
|
|
4
23
|
export interface InputRequestedArtifact {
|
|
@@ -32,6 +51,8 @@ export interface InputEnvelope {
|
|
|
32
51
|
theme?: string;
|
|
33
52
|
constraints: string[];
|
|
34
53
|
requestedArtifacts: InputRequestedArtifact[];
|
|
54
|
+
/** Images/files attached to this input (clipboard paste, --image, drag). */
|
|
55
|
+
attachments: InputAttachment[];
|
|
35
56
|
slashCommand?: InputSlashCommandEnvelope;
|
|
36
57
|
createdAt: string;
|
|
37
58
|
}
|
|
@@ -52,6 +73,7 @@ export interface BuildInputEnvelopeInput {
|
|
|
52
73
|
theme?: string;
|
|
53
74
|
constraints?: readonly string[];
|
|
54
75
|
requestedArtifacts?: readonly InputRequestedArtifact[];
|
|
76
|
+
attachments?: readonly InputAttachment[];
|
|
55
77
|
slashCommand?: InputSlashCommandEnvelope;
|
|
56
78
|
now?: () => Date;
|
|
57
79
|
}
|
|
@@ -60,6 +60,7 @@ export function buildInputEnvelope(input) {
|
|
|
60
60
|
theme: input.theme,
|
|
61
61
|
constraints: [...(input.constraints ?? [])],
|
|
62
62
|
requestedArtifacts: input.requestedArtifacts?.map((artifact) => ({ ...artifact })) ?? [],
|
|
63
|
+
attachments: input.attachments?.map((a) => ({ ...a })) ?? [],
|
|
63
64
|
slashCommand: input.slashCommand
|
|
64
65
|
? cloneSlashCommand(input.slashCommand)
|
|
65
66
|
: undefined,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Control Loop — Phase 6 of OMK Weakness Remediation (Algorithm 12).
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates Phases 1–5 into a single turn-gating engine:
|
|
5
|
+
* 1. Public surface compression
|
|
6
|
+
* 2. Proof bundle trust evaluation
|
|
7
|
+
* 3. Provider maturity gating
|
|
8
|
+
* 4. Evidence-calibrated router v2
|
|
9
|
+
* 5. Release gate advisory
|
|
10
|
+
*/
|
|
11
|
+
import type { CompressionResult, SurfaceItem } from "./public-surface.js";
|
|
12
|
+
import type { ProofBundleScores, TrustScoreResult } from "./proof-bundle-trust.js";
|
|
13
|
+
import type { MaturityResult } from "./provider-maturity-gate.js";
|
|
14
|
+
import type { RuntimeRouterDecisionV2, EvidenceHistoryEntry, NodeIntent } from "./contracts/router-v2.js";
|
|
15
|
+
import type { ReleasePromotionInputs, ReleasePromotionResult } from "./contracts/weakness-remediation.js";
|
|
16
|
+
import type { ReleasePromotionGate } from "../cli/release-promotion-gate.js";
|
|
17
|
+
import type { AdapterTestResult } from "./contracts/evidence.js";
|
|
18
|
+
import type { AgentRuntime } from "./agent-runtime.js";
|
|
19
|
+
export type IntegrationResultKind = "verified" | "blocked" | "handoff";
|
|
20
|
+
export interface WeaknessRemediationState {
|
|
21
|
+
readonly publicSurface: CompressionResult;
|
|
22
|
+
readonly proofTrust: TrustScoreResult;
|
|
23
|
+
readonly providerMaturity: MaturityResult;
|
|
24
|
+
readonly routerV2Decision: RuntimeRouterDecisionV2 | null;
|
|
25
|
+
readonly releaseGate: ReleasePromotionResult | null;
|
|
26
|
+
}
|
|
27
|
+
export interface AdvancedControlLoopInput {
|
|
28
|
+
readonly turnId: string;
|
|
29
|
+
readonly intent: NodeIntent;
|
|
30
|
+
readonly surfaceItems: readonly SurfaceItem[];
|
|
31
|
+
readonly proofScores: ProofBundleScores;
|
|
32
|
+
readonly providerTests: readonly AdapterTestResult[];
|
|
33
|
+
readonly candidates: readonly AgentRuntime[];
|
|
34
|
+
readonly evidenceHistory: readonly EvidenceHistoryEntry[];
|
|
35
|
+
readonly releaseInputs?: ReleasePromotionInputs;
|
|
36
|
+
readonly retryBudget?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface AdvancedControlLoopResult {
|
|
39
|
+
readonly kind: IntegrationResultKind;
|
|
40
|
+
readonly turnId: string;
|
|
41
|
+
readonly state: WeaknessRemediationState;
|
|
42
|
+
readonly replan?: {
|
|
43
|
+
readonly triggered: boolean;
|
|
44
|
+
readonly reason: string;
|
|
45
|
+
readonly retryBudgetRemaining: number;
|
|
46
|
+
};
|
|
47
|
+
readonly evidenceContractPath: string;
|
|
48
|
+
}
|
|
49
|
+
export interface AdvancedControlLoop {
|
|
50
|
+
readonly run: (input: AdvancedControlLoopInput) => Promise<AdvancedControlLoopResult>;
|
|
51
|
+
}
|
|
52
|
+
export interface AdvancedControlLoopOptions {
|
|
53
|
+
readonly publicSurfaceBudget?: number;
|
|
54
|
+
readonly tauProof?: number;
|
|
55
|
+
readonly tauEvidence?: number;
|
|
56
|
+
readonly releaseGate?: ReleasePromotionGate;
|
|
57
|
+
readonly releaseGateEnabled?: boolean;
|
|
58
|
+
readonly evidenceContractDir?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function createAdvancedControlLoop(options?: AdvancedControlLoopOptions): AdvancedControlLoop;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Control Loop — Phase 6 of OMK Weakness Remediation (Algorithm 12).
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates Phases 1–5 into a single turn-gating engine:
|
|
5
|
+
* 1. Public surface compression
|
|
6
|
+
* 2. Proof bundle trust evaluation
|
|
7
|
+
* 3. Provider maturity gating
|
|
8
|
+
* 4. Evidence-calibrated router v2
|
|
9
|
+
* 5. Release gate advisory
|
|
10
|
+
*/
|
|
11
|
+
import { PublicSurfaceCompressor } from "./public-surface.js";
|
|
12
|
+
import { createProofBundleTrustEngine } from "./proof-bundle-trust.js";
|
|
13
|
+
import { TAU_PROOF } from "./contracts/weakness-remediation.js";
|
|
14
|
+
import { createProviderMaturityGate } from "./provider-maturity-gate.js";
|
|
15
|
+
import { createRouterV2ScoringEngine } from "./router-v2-scoring.js";
|
|
16
|
+
import { TAU_EVIDENCE } from "./contracts/weakness-remediation.js";
|
|
17
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
18
|
+
function clamp01(n) {
|
|
19
|
+
return Math.max(0, Math.min(1, n));
|
|
20
|
+
}
|
|
21
|
+
function buildState(publicSurface, proofTrust, providerMaturity, routerV2Decision, releaseGate) {
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
publicSurface,
|
|
24
|
+
proofTrust: proofTrust ??
|
|
25
|
+
Object.freeze({
|
|
26
|
+
score: 0,
|
|
27
|
+
permissionLevel: "no-claim",
|
|
28
|
+
passed: false,
|
|
29
|
+
breakdown: Object.freeze({
|
|
30
|
+
schema: 0,
|
|
31
|
+
hashes: 0,
|
|
32
|
+
commands: 0,
|
|
33
|
+
stdout: 0,
|
|
34
|
+
decisions: 0,
|
|
35
|
+
evidence: 0,
|
|
36
|
+
limitations: 0,
|
|
37
|
+
replay: 0,
|
|
38
|
+
}),
|
|
39
|
+
}),
|
|
40
|
+
providerMaturity: providerMaturity ??
|
|
41
|
+
Object.freeze({
|
|
42
|
+
score: 0,
|
|
43
|
+
authorityClass: "disabled",
|
|
44
|
+
passed: false,
|
|
45
|
+
subScores: Object.freeze({
|
|
46
|
+
auth: 0,
|
|
47
|
+
read: 0,
|
|
48
|
+
write: 0,
|
|
49
|
+
shell: 0,
|
|
50
|
+
mcp: 0,
|
|
51
|
+
merge: 0,
|
|
52
|
+
evidence: 0,
|
|
53
|
+
fallback: 0,
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
routerV2Decision,
|
|
57
|
+
releaseGate,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
61
|
+
export function createAdvancedControlLoop(options = {}) {
|
|
62
|
+
const { publicSurfaceBudget, tauProof = TAU_PROOF, tauEvidence = TAU_EVIDENCE, releaseGate, releaseGateEnabled = true, evidenceContractDir = ".omk/evidence", } = options;
|
|
63
|
+
const compressorOptions = publicSurfaceBudget
|
|
64
|
+
? { budget: publicSurfaceBudget }
|
|
65
|
+
: {};
|
|
66
|
+
const compressor = new PublicSurfaceCompressor(compressorOptions);
|
|
67
|
+
const proofEngine = createProofBundleTrustEngine();
|
|
68
|
+
const maturityGate = createProviderMaturityGate();
|
|
69
|
+
const routerV2Engine = createRouterV2ScoringEngine();
|
|
70
|
+
return {
|
|
71
|
+
async run(input) {
|
|
72
|
+
// Phase 1 — Public surface compression
|
|
73
|
+
const publicSurface = compressor.compress(input.surfaceItems);
|
|
74
|
+
if (!publicSurface.invariantPassed) {
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
kind: "blocked",
|
|
77
|
+
turnId: input.turnId,
|
|
78
|
+
state: buildState(publicSurface, null, null, null, null),
|
|
79
|
+
evidenceContractPath: `${evidenceContractDir}/${input.turnId}-blocked.json`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Phase 2 — Proof bundle trust evaluation
|
|
83
|
+
const proofTrust = proofEngine.evaluate(input.proofScores);
|
|
84
|
+
if (proofTrust.score < tauProof) {
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
kind: "handoff",
|
|
87
|
+
turnId: input.turnId,
|
|
88
|
+
state: buildState(publicSurface, proofTrust, null, null, null),
|
|
89
|
+
replan: {
|
|
90
|
+
triggered: false,
|
|
91
|
+
reason: `Proof trust ${clamp01(proofTrust.score).toFixed(3)} < τ_proof ${tauProof}`,
|
|
92
|
+
retryBudgetRemaining: input.retryBudget ?? 0,
|
|
93
|
+
},
|
|
94
|
+
evidenceContractPath: `${evidenceContractDir}/${input.turnId}-handoff.json`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Phase 3 — Provider maturity gating
|
|
98
|
+
const providerMaturity = maturityGate.evaluate(input.providerTests);
|
|
99
|
+
if (!providerMaturity.passed || providerMaturity.authorityClass === "disabled") {
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
kind: "blocked",
|
|
102
|
+
turnId: input.turnId,
|
|
103
|
+
state: buildState(publicSurface, proofTrust, providerMaturity, null, null),
|
|
104
|
+
evidenceContractPath: `${evidenceContractDir}/${input.turnId}-blocked.json`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Phase 4 — Evidence-calibrated router v2
|
|
108
|
+
const routerV2Decision = routerV2Engine.select([...input.candidates], input.intent, [...input.evidenceHistory]);
|
|
109
|
+
const bestScore = routerV2Decision.scores[0];
|
|
110
|
+
if (!bestScore || bestScore.bayesianEvidenceScore < tauEvidence) {
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
kind: "handoff",
|
|
113
|
+
turnId: input.turnId,
|
|
114
|
+
state: buildState(publicSurface, proofTrust, providerMaturity, routerV2Decision, null),
|
|
115
|
+
replan: {
|
|
116
|
+
triggered: true,
|
|
117
|
+
reason: `Router v2 bayesian evidence ${bestScore ? clamp01(bestScore.bayesianEvidenceScore).toFixed(3) : "n/a"} < τ_evidence ${tauEvidence}`,
|
|
118
|
+
retryBudgetRemaining: Math.max(0, (input.retryBudget ?? 1) - 1),
|
|
119
|
+
},
|
|
120
|
+
evidenceContractPath: `${evidenceContractDir}/${input.turnId}-handoff.json`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Phase 5 — Release gate advisory (optional)
|
|
124
|
+
let releaseGateResult = null;
|
|
125
|
+
if (releaseGateEnabled && releaseGate && input.releaseInputs) {
|
|
126
|
+
releaseGateResult = releaseGate.evaluate(input.releaseInputs);
|
|
127
|
+
}
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
kind: "verified",
|
|
130
|
+
turnId: input.turnId,
|
|
131
|
+
state: buildState(publicSurface, proofTrust, providerMaturity, routerV2Decision, releaseGateResult),
|
|
132
|
+
evidenceContractPath: `${evidenceContractDir}/${input.turnId}-verified.json`,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -91,12 +91,22 @@ export interface ProviderPolicy {
|
|
|
91
91
|
export interface CapabilityManifest extends RuntimeCapabilities {
|
|
92
92
|
readonly structuredOutput?: boolean;
|
|
93
93
|
}
|
|
94
|
+
export interface AgentTaskAttachment {
|
|
95
|
+
readonly name: string;
|
|
96
|
+
readonly path?: string;
|
|
97
|
+
readonly mimeType: string;
|
|
98
|
+
readonly dataUri: string;
|
|
99
|
+
readonly ext: string;
|
|
100
|
+
readonly source: "clipboard" | "file" | "drag";
|
|
101
|
+
}
|
|
94
102
|
export interface AgentTask {
|
|
95
103
|
readonly prompt: string;
|
|
96
104
|
readonly context: AgentContext;
|
|
97
105
|
readonly tools: ToolManifest;
|
|
98
106
|
readonly providerPolicy: ProviderPolicy;
|
|
99
107
|
readonly capabilities: CapabilityManifest;
|
|
108
|
+
/** Images/files attached to this task (clipboard paste, --image, drag). */
|
|
109
|
+
readonly attachments?: readonly AgentTaskAttachment[];
|
|
100
110
|
}
|
|
101
111
|
export interface AgentResult {
|
|
102
112
|
readonly output: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast radius penalty — penalizes runtimes for high-risk, wide-impact tasks.
|
|
3
|
+
*
|
|
4
|
+
* Higher downstream dependency counts, larger affected file surfaces, and
|
|
5
|
+
* global side-effects increase the penalty, nudging the router toward
|
|
6
|
+
* more mature or lower-blast-radius runtimes.
|
|
7
|
+
*/
|
|
8
|
+
import type { BlastRadiusParams } from "./contracts/router-v2.js";
|
|
9
|
+
export type { BlastRadiusParams } from "./contracts/router-v2.js";
|
|
10
|
+
export declare function computeBlastRadiusPenalty(params: BlastRadiusParams): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blast radius penalty — penalizes runtimes for high-risk, wide-impact tasks.
|
|
3
|
+
*
|
|
4
|
+
* Higher downstream dependency counts, larger affected file surfaces, and
|
|
5
|
+
* global side-effects increase the penalty, nudging the router toward
|
|
6
|
+
* more mature or lower-blast-radius runtimes.
|
|
7
|
+
*/
|
|
8
|
+
export function computeBlastRadiusPenalty(params) {
|
|
9
|
+
const { downstreamNodeCount, affectedFileCount, hasGlobalSideEffects } = params;
|
|
10
|
+
const downstreamPenalty = Math.min(0.15, downstreamNodeCount * 0.03);
|
|
11
|
+
const filePenalty = Math.min(0.10, affectedFileCount * 0.01);
|
|
12
|
+
const sideEffectPenalty = hasGlobalSideEffects ? 0.10 : 0.0;
|
|
13
|
+
return Math.min(0.30, downstreamPenalty + filePenalty + sideEffectPenalty);
|
|
14
|
+
}
|