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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd data-retention gate.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task's prompt, attachments, and relevant files may be sent
|
|
5
|
+
* to a provider given its retention profile and the org/user policy. The gate
|
|
6
|
+
* returns one of {allow, redact, downgrade, block} and never leaks secret-like
|
|
7
|
+
* spans to a retained provider.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentRuntime, AgentTask } from "./agent-runtime.js";
|
|
10
|
+
import type { ProviderSovereigntyProfile } from "./provider-sovereignty.js";
|
|
11
|
+
export type DataBoundary = "public" | "internal" | "customer" | "secret";
|
|
12
|
+
export type RetentionDecision = "allow" | "redact" | "downgrade" | "block";
|
|
13
|
+
export interface DataSensitivity {
|
|
14
|
+
readonly boundary: DataBoundary;
|
|
15
|
+
readonly containsSecret: boolean;
|
|
16
|
+
readonly containsCustomerData: boolean;
|
|
17
|
+
readonly sensitiveSpans: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export interface ProviderRetentionProfile {
|
|
20
|
+
readonly retentionDays: number;
|
|
21
|
+
readonly zeroDataRetention: boolean;
|
|
22
|
+
readonly trainingUse: boolean;
|
|
23
|
+
readonly auditAccessUnknown: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface DataRetentionGateInput {
|
|
26
|
+
readonly task: AgentTask;
|
|
27
|
+
readonly runtime: AgentRuntime;
|
|
28
|
+
readonly providerProfile: ProviderSovereigntyProfile;
|
|
29
|
+
readonly orgMaxRetentionDays: number;
|
|
30
|
+
readonly allowRedaction: boolean;
|
|
31
|
+
readonly relevantFiles?: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
export interface DataRetentionGateResult {
|
|
34
|
+
readonly decision: RetentionDecision;
|
|
35
|
+
readonly reason: string;
|
|
36
|
+
readonly sensitivity: DataSensitivity;
|
|
37
|
+
readonly redactedSpans?: readonly string[];
|
|
38
|
+
readonly downgradeMode?: "local-only-or-zdr-provider" | "read-only-local-review";
|
|
39
|
+
}
|
|
40
|
+
export declare function classifyDataSensitivity(task: AgentTask, relevantFiles?: readonly string[]): DataSensitivity;
|
|
41
|
+
export declare function providerRetentionProfileFromSovereignty(profile: ProviderSovereigntyProfile): ProviderRetentionProfile;
|
|
42
|
+
export declare function evaluateDataRetentionGate(input: DataRetentionGateInput): DataRetentionGateResult;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd data-retention gate.
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a task's prompt, attachments, and relevant files may be sent
|
|
5
|
+
* to a provider given its retention profile and the org/user policy. The gate
|
|
6
|
+
* returns one of {allow, redact, downgrade, block} and never leaks secret-like
|
|
7
|
+
* spans to a retained provider.
|
|
8
|
+
*/
|
|
9
|
+
import { maskSensitiveText } from "../util/secret-mask.js";
|
|
10
|
+
import { runtimeProviderId, runtimeModeOf } from "./authority-matrix.js";
|
|
11
|
+
const SECRET_KEYWORDS = [
|
|
12
|
+
"password", "secret", "token", "api_key", "apikey", "private_key",
|
|
13
|
+
"credential", "bearer", "authorization", "session", "cookie",
|
|
14
|
+
];
|
|
15
|
+
const CUSTOMER_DATA_KEYWORDS = [
|
|
16
|
+
"customer", "user_id", "email", "phone", "ssn", "personal",
|
|
17
|
+
"gdpr", "hipaa", "pci", "pii",
|
|
18
|
+
];
|
|
19
|
+
function boundaryForRisk(risk) {
|
|
20
|
+
if (risk === "shell" || risk === "merge")
|
|
21
|
+
return "secret";
|
|
22
|
+
if (risk === "write" || risk === "patch")
|
|
23
|
+
return "internal";
|
|
24
|
+
return "public";
|
|
25
|
+
}
|
|
26
|
+
function classifyTextSensitivity(text) {
|
|
27
|
+
const lower = text.toLowerCase();
|
|
28
|
+
const sensitiveSpans = [];
|
|
29
|
+
let containsSecret = false;
|
|
30
|
+
let containsCustomerData = false;
|
|
31
|
+
for (const keyword of SECRET_KEYWORDS) {
|
|
32
|
+
if (lower.includes(keyword)) {
|
|
33
|
+
containsSecret = true;
|
|
34
|
+
sensitiveSpans.push(`keyword:${keyword}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const keyword of CUSTOMER_DATA_KEYWORDS) {
|
|
38
|
+
if (lower.includes(keyword)) {
|
|
39
|
+
containsCustomerData = true;
|
|
40
|
+
sensitiveSpans.push(`keyword:${keyword}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const masked = maskSensitiveText(text);
|
|
44
|
+
if (masked !== text) {
|
|
45
|
+
containsSecret = true;
|
|
46
|
+
sensitiveSpans.push("pattern:secret-like");
|
|
47
|
+
}
|
|
48
|
+
return { containsSecret, containsCustomerData, sensitiveSpans: [...new Set(sensitiveSpans)] };
|
|
49
|
+
}
|
|
50
|
+
export function classifyDataSensitivity(task, relevantFiles) {
|
|
51
|
+
const text = [
|
|
52
|
+
task.prompt,
|
|
53
|
+
task.context.goal ?? "",
|
|
54
|
+
task.context.system ?? "",
|
|
55
|
+
...(task.attachments ?? []).map((a) => a.name),
|
|
56
|
+
...(relevantFiles ?? []),
|
|
57
|
+
].join("\n");
|
|
58
|
+
const classified = classifyTextSensitivity(text);
|
|
59
|
+
const boundary = boundaryForRisk(task.safety?.risk);
|
|
60
|
+
return {
|
|
61
|
+
boundary,
|
|
62
|
+
containsSecret: classified.containsSecret,
|
|
63
|
+
containsCustomerData: classified.containsCustomerData,
|
|
64
|
+
sensitiveSpans: classified.sensitiveSpans,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function providerRetentionProfileFromSovereignty(profile) {
|
|
68
|
+
return {
|
|
69
|
+
retentionDays: profile.retentionDays,
|
|
70
|
+
zeroDataRetention: profile.zeroDataRetention,
|
|
71
|
+
trainingUse: profile.trainingUse,
|
|
72
|
+
auditAccessUnknown: !profile.zeroDataRetention && profile.retentionDays > 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function evaluateDataRetentionGate(input) {
|
|
76
|
+
const { task, runtime, providerProfile, orgMaxRetentionDays, allowRedaction } = input;
|
|
77
|
+
const sensitivity = classifyDataSensitivity(task, input.relevantFiles);
|
|
78
|
+
if (sensitivity.containsSecret) {
|
|
79
|
+
return {
|
|
80
|
+
decision: "block",
|
|
81
|
+
reason: "secret-like data cannot be sent to retained provider",
|
|
82
|
+
sensitivity,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const retention = providerRetentionProfileFromSovereignty(providerProfile);
|
|
86
|
+
if (sensitivity.containsCustomerData && !retention.zeroDataRetention) {
|
|
87
|
+
if (allowRedaction) {
|
|
88
|
+
return {
|
|
89
|
+
decision: "redact",
|
|
90
|
+
reason: `customer data redacted before sending to ${runtimeProviderId(runtime)}:${runtimeModeOf(runtime)}`,
|
|
91
|
+
sensitivity,
|
|
92
|
+
redactedSpans: sensitivity.sensitiveSpans,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
decision: "downgrade",
|
|
97
|
+
reason: "customer data with non-zero retention provider; downgrade required",
|
|
98
|
+
sensitivity,
|
|
99
|
+
downgradeMode: "local-only-or-zdr-provider",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (retention.retentionDays > orgMaxRetentionDays) {
|
|
103
|
+
return {
|
|
104
|
+
decision: "block",
|
|
105
|
+
reason: `provider retention ${retention.retentionDays}d exceeds policy ${orgMaxRetentionDays}d`,
|
|
106
|
+
sensitivity,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const risk = task.safety?.risk;
|
|
110
|
+
if ((risk === "write" || risk === "shell" || risk === "merge") && retention.auditAccessUnknown) {
|
|
111
|
+
return {
|
|
112
|
+
decision: "downgrade",
|
|
113
|
+
reason: "high-risk task with unclear retention/audit boundary",
|
|
114
|
+
sensitivity,
|
|
115
|
+
downgradeMode: "read-only-local-review",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
decision: "allow",
|
|
120
|
+
reason: "retention policy acceptable for data boundary",
|
|
121
|
+
sensitivity,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Supports SSE streaming, tool calling, and reasoning_content.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentRuntime, AgentRunResult, AgentResult, AgentTask, RuntimeCapabilities, RuntimeHealth } from "./agent-runtime.js";
|
|
8
|
+
import type { RuntimeHealthProbeRequest } from "./contracts/shared.js";
|
|
8
9
|
import type { ContextCapsule } from "./context-capsule.js";
|
|
9
10
|
export interface DeepSeekRuntimeOptions {
|
|
10
11
|
apiKey?: string;
|
|
@@ -13,6 +14,9 @@ export interface DeepSeekRuntimeOptions {
|
|
|
13
14
|
}
|
|
14
15
|
export declare class DeepSeekRuntime implements AgentRuntime {
|
|
15
16
|
readonly id = "deepseek-api";
|
|
17
|
+
readonly providerId = "deepseek";
|
|
18
|
+
readonly advisory = true;
|
|
19
|
+
readonly runtimeMode = "api";
|
|
16
20
|
readonly kind = "api";
|
|
17
21
|
readonly priority = 40;
|
|
18
22
|
readonly capabilities: RuntimeCapabilities;
|
|
@@ -21,7 +25,7 @@ export declare class DeepSeekRuntime implements AgentRuntime {
|
|
|
21
25
|
private readonly baseUrl;
|
|
22
26
|
constructor(options?: DeepSeekRuntimeOptions);
|
|
23
27
|
supports(capsule: ContextCapsule): boolean;
|
|
24
|
-
health(): Promise<RuntimeHealth>;
|
|
28
|
+
health(input?: RuntimeHealthProbeRequest): Promise<RuntimeHealth>;
|
|
25
29
|
runNode(capsule: ContextCapsule, signal: AbortSignal): Promise<AgentRunResult>;
|
|
26
30
|
execute(task: AgentTask): Promise<AgentResult>;
|
|
27
31
|
private parseStreamResponse;
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { capsuleToTask } from "./context-broker-converter.js";
|
|
8
8
|
import { buildProviderToolPayload } from "./provider-tool-contracts.js";
|
|
9
|
+
import { probeOpenAiCompatibleModels } from "./runtime-health-probes.js";
|
|
9
10
|
export class DeepSeekRuntime {
|
|
10
11
|
id = "deepseek-api";
|
|
12
|
+
providerId = "deepseek";
|
|
13
|
+
advisory = true;
|
|
14
|
+
runtimeMode = "api";
|
|
11
15
|
kind = "api";
|
|
12
16
|
priority = 40;
|
|
13
17
|
capabilities = {
|
|
@@ -42,14 +46,16 @@ export class DeepSeekRuntime {
|
|
|
42
46
|
return false;
|
|
43
47
|
return true;
|
|
44
48
|
}
|
|
45
|
-
async health() {
|
|
46
|
-
|
|
47
|
-
return {
|
|
49
|
+
async health(input = { probeKind: "static", highRisk: false }) {
|
|
50
|
+
return probeOpenAiCompatibleModels({
|
|
48
51
|
runtimeId: this.id,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
baseUrl: this.baseUrl,
|
|
53
|
+
apiKey: this.apiKey,
|
|
54
|
+
apiKeyName: "DEEPSEEK_API_KEY",
|
|
55
|
+
model: this.model,
|
|
56
|
+
providerName: "DeepSeek",
|
|
57
|
+
probeKind: input.probeKind,
|
|
58
|
+
});
|
|
53
59
|
}
|
|
54
60
|
async runNode(capsule, signal) {
|
|
55
61
|
try {
|
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
-
import { checkCommand, } from "../util/shell.js";
|
|
8
|
+
import { checkCommand, runShell, } from "../util/shell.js";
|
|
9
9
|
import { runtimeMetadataEnv } from "./child-env.js";
|
|
10
10
|
import { runProcessSession } from "./process-session.js";
|
|
11
11
|
import { createRuntimeSandboxProfile } from "./sandbox-profile.js";
|
|
12
|
+
import { staticRuntimeHealth } from "./runtime-health-probes.js";
|
|
12
13
|
export function createExternalCliAdapter(options) {
|
|
13
14
|
let pendingOnOutput;
|
|
14
15
|
return {
|
|
15
16
|
id: options.id,
|
|
17
|
+
providerId: options.id.split("-")[0],
|
|
18
|
+
runtimeMode: "cli",
|
|
16
19
|
displayName: options.displayName,
|
|
17
20
|
kind: "cli",
|
|
18
21
|
priority: options.priority,
|
|
@@ -20,14 +23,24 @@ export function createExternalCliAdapter(options) {
|
|
|
20
23
|
supports(capsule) {
|
|
21
24
|
return runtimeCapabilitiesSupportCapsule(options.capabilities, capsule);
|
|
22
25
|
},
|
|
23
|
-
async health() {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
+
async health(input = { probeKind: "static", highRisk: false }) {
|
|
27
|
+
const started = Date.now();
|
|
28
|
+
const available = input.probeKind === "static"
|
|
29
|
+
? await checkCommand(options.bin)
|
|
30
|
+
: (await runShell(options.bin, ["--version"], { cwd: options.cwd ?? process.cwd(), timeout: 5000 }).catch(() => ({ exitCode: 1 }))).exitCode === 0;
|
|
31
|
+
return staticRuntimeHealth({
|
|
26
32
|
runtimeId: options.id,
|
|
27
33
|
available,
|
|
28
|
-
reason: available ? undefined : `Command not found: ${options.bin}`,
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
reason: available ? undefined : `Command not found or failed --version probe: ${options.bin}`,
|
|
35
|
+
runtimeOk: available,
|
|
36
|
+
authOk: available,
|
|
37
|
+
modelOk: true,
|
|
38
|
+
quotaOk: true,
|
|
39
|
+
rateLimitOk: true,
|
|
40
|
+
latencyMs: input.probeKind === "static" ? undefined : Date.now() - started,
|
|
41
|
+
probeKind: input.probeKind,
|
|
42
|
+
ttlMs: input.probeKind === "static" ? 60_000 : 30_000,
|
|
43
|
+
});
|
|
31
44
|
},
|
|
32
45
|
async execute(task) {
|
|
33
46
|
pendingOnOutput = task.context.onOutput;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-first evidence envelope for Freedomd.
|
|
3
|
+
*
|
|
4
|
+
* Provider responses and evidence observations are persisted outside the
|
|
5
|
+
* provider runtime so that runs remain replayable and auditable even when a
|
|
6
|
+
* provider becomes unavailable or changes policy.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentRuntime, AgentRunResult, AgentTask, AgentTaskSovereignty } from "./agent-runtime.js";
|
|
9
|
+
import type { EvidenceObservation, EvidenceRequirement } from "./contracts/evidence.js";
|
|
10
|
+
import type { LocalGraphMemoryStore } from "../memory/local-graph-memory-store.js";
|
|
11
|
+
export interface EvidenceEnvelopeArtifact {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly sha256: string;
|
|
14
|
+
readonly sizeBytes: number;
|
|
15
|
+
readonly redacted: boolean;
|
|
16
|
+
readonly replayable: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface EvidenceEnvelopeObservation {
|
|
19
|
+
readonly kind: string;
|
|
20
|
+
readonly verdict: "pass" | "fail" | "partial" | "pending";
|
|
21
|
+
readonly artifactRef?: string;
|
|
22
|
+
readonly sha256?: string;
|
|
23
|
+
readonly replayable: boolean;
|
|
24
|
+
readonly redacted: boolean;
|
|
25
|
+
readonly confidence: number;
|
|
26
|
+
readonly timestamp: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FreedomdEvidenceEnvelope {
|
|
29
|
+
schemaVersion: "omk.freedomd.evidence-envelope.v1";
|
|
30
|
+
runId: string;
|
|
31
|
+
nodeId: string;
|
|
32
|
+
taskHash: string;
|
|
33
|
+
selectedRuntime: string;
|
|
34
|
+
providerId: string;
|
|
35
|
+
runtimeMode: string;
|
|
36
|
+
sovereignty: AgentTaskSovereignty;
|
|
37
|
+
evidenceRequirements: readonly EvidenceRequirement[];
|
|
38
|
+
evidenceObservations: readonly EvidenceEnvelopeObservation[];
|
|
39
|
+
providerResponseRef?: string;
|
|
40
|
+
localArtifacts: readonly EvidenceEnvelopeArtifact[];
|
|
41
|
+
createdAt: string;
|
|
42
|
+
}
|
|
43
|
+
export interface BuildEvidenceEnvelopeOptions {
|
|
44
|
+
readonly task: AgentTask;
|
|
45
|
+
readonly selectedRuntime: AgentRuntime;
|
|
46
|
+
readonly runContext: {
|
|
47
|
+
readonly runId: string;
|
|
48
|
+
readonly nodeId: string;
|
|
49
|
+
readonly projectRoot?: string;
|
|
50
|
+
};
|
|
51
|
+
readonly evidenceObservations?: readonly EvidenceObservation[];
|
|
52
|
+
readonly providerResponse?: AgentRunResult;
|
|
53
|
+
readonly sovereignty: AgentTaskSovereignty;
|
|
54
|
+
}
|
|
55
|
+
export declare function hashTaskPrompt(prompt: string): string;
|
|
56
|
+
export declare function buildLocalFirstEvidenceEnvelope(options: BuildEvidenceEnvelopeOptions): Promise<FreedomdEvidenceEnvelope>;
|
|
57
|
+
export declare function materializeEvidenceEnvelopeInGraph(store: LocalGraphMemoryStore, envelope: FreedomdEvidenceEnvelope): Promise<void>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-first evidence envelope for Freedomd.
|
|
3
|
+
*
|
|
4
|
+
* Provider responses and evidence observations are persisted outside the
|
|
5
|
+
* provider runtime so that runs remain replayable and auditable even when a
|
|
6
|
+
* provider becomes unavailable or changes policy.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
9
|
+
import { dirname, join, resolve } from "path";
|
|
10
|
+
import { sha256Hex } from "../util/hash.js";
|
|
11
|
+
function envelopeDir(root, runId) {
|
|
12
|
+
return join(resolve(root), ".omk", "runs", runId, "freedomd");
|
|
13
|
+
}
|
|
14
|
+
function artifactPath(root, runId, nodeId, kind) {
|
|
15
|
+
const safeNodeId = nodeId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
16
|
+
return join(envelopeDir(root, runId), `${safeNodeId}-${kind}.json`);
|
|
17
|
+
}
|
|
18
|
+
export function hashTaskPrompt(prompt) {
|
|
19
|
+
return sha256Hex(prompt);
|
|
20
|
+
}
|
|
21
|
+
async function persistArtifact(root, runId, nodeId, kind, payload) {
|
|
22
|
+
const path = artifactPath(root, runId, nodeId, kind);
|
|
23
|
+
await mkdir(dirname(path), { recursive: true });
|
|
24
|
+
const text = JSON.stringify(payload, null, 2);
|
|
25
|
+
await writeFile(path, text, "utf-8");
|
|
26
|
+
return {
|
|
27
|
+
path,
|
|
28
|
+
sha256: sha256Hex(text),
|
|
29
|
+
sizeBytes: Buffer.byteLength(text, "utf-8"),
|
|
30
|
+
redacted: false,
|
|
31
|
+
replayable: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function buildLocalFirstEvidenceEnvelope(options) {
|
|
35
|
+
const { task, selectedRuntime, runContext, evidenceObservations, providerResponse, sovereignty } = options;
|
|
36
|
+
const root = runContext.projectRoot ?? process.cwd();
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
const localArtifacts = [];
|
|
39
|
+
const evidenceObservationList = [];
|
|
40
|
+
for (const observation of evidenceObservations ?? []) {
|
|
41
|
+
const artifact = await persistArtifact(root, runContext.runId, runContext.nodeId, `observation-${observation.kind}`, observation);
|
|
42
|
+
localArtifacts.push(artifact);
|
|
43
|
+
evidenceObservationList.push({
|
|
44
|
+
kind: observation.kind,
|
|
45
|
+
verdict: observation.confidence >= 0.8 ? "pass" : observation.confidence >= 0.4 ? "partial" : "fail",
|
|
46
|
+
artifactRef: artifact.path,
|
|
47
|
+
sha256: artifact.sha256,
|
|
48
|
+
replayable: observation.replayable,
|
|
49
|
+
redacted: observation.redacted,
|
|
50
|
+
confidence: observation.confidence,
|
|
51
|
+
timestamp: observation.timestamp,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (providerResponse) {
|
|
55
|
+
const safeResponse = {
|
|
56
|
+
success: providerResponse.success,
|
|
57
|
+
exitCode: providerResponse.exitCode,
|
|
58
|
+
stdout: providerResponse.stdout,
|
|
59
|
+
stderr: providerResponse.stderr,
|
|
60
|
+
tokenUsage: providerResponse.tokenUsage,
|
|
61
|
+
metadata: providerResponse.metadata,
|
|
62
|
+
};
|
|
63
|
+
const artifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "provider-response", safeResponse);
|
|
64
|
+
localArtifacts.push(artifact);
|
|
65
|
+
const envelope = {
|
|
66
|
+
schemaVersion: "omk.freedomd.evidence-envelope.v1",
|
|
67
|
+
runId: runContext.runId,
|
|
68
|
+
nodeId: runContext.nodeId,
|
|
69
|
+
taskHash: hashTaskPrompt(task.prompt),
|
|
70
|
+
selectedRuntime: selectedRuntime.id,
|
|
71
|
+
providerId: selectedRuntime.providerId ?? selectedRuntime.id.split("-")[0] ?? selectedRuntime.id,
|
|
72
|
+
runtimeMode: selectedRuntime.runtimeMode,
|
|
73
|
+
sovereignty,
|
|
74
|
+
evidenceRequirements: [],
|
|
75
|
+
evidenceObservations: evidenceObservationList,
|
|
76
|
+
providerResponseRef: artifact.path,
|
|
77
|
+
localArtifacts,
|
|
78
|
+
createdAt: now,
|
|
79
|
+
};
|
|
80
|
+
const envelopeArtifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "evidence-envelope", envelope);
|
|
81
|
+
return {
|
|
82
|
+
...envelope,
|
|
83
|
+
localArtifacts: [...localArtifacts, envelopeArtifact],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const envelope = {
|
|
87
|
+
schemaVersion: "omk.freedomd.evidence-envelope.v1",
|
|
88
|
+
runId: runContext.runId,
|
|
89
|
+
nodeId: runContext.nodeId,
|
|
90
|
+
taskHash: hashTaskPrompt(task.prompt),
|
|
91
|
+
selectedRuntime: selectedRuntime.id,
|
|
92
|
+
providerId: selectedRuntime.providerId ?? selectedRuntime.id.split("-")[0] ?? selectedRuntime.id,
|
|
93
|
+
runtimeMode: selectedRuntime.runtimeMode,
|
|
94
|
+
sovereignty,
|
|
95
|
+
evidenceRequirements: [],
|
|
96
|
+
evidenceObservations: evidenceObservationList,
|
|
97
|
+
localArtifacts,
|
|
98
|
+
createdAt: now,
|
|
99
|
+
};
|
|
100
|
+
const envelopeArtifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "evidence-envelope", envelope);
|
|
101
|
+
return {
|
|
102
|
+
...envelope,
|
|
103
|
+
localArtifacts: [...localArtifacts, envelopeArtifact],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export async function materializeEvidenceEnvelopeInGraph(store, envelope) {
|
|
107
|
+
const runId = envelope.runId;
|
|
108
|
+
const nodeId = envelope.nodeId;
|
|
109
|
+
await store.write(join(".omk", "runs", runId, "freedomd", `${nodeId}-evidence-envelope.json`), JSON.stringify(envelope, null, 2));
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd provider exception approval.
|
|
3
|
+
*
|
|
4
|
+
* Interactive approval flow for blocked providers. Records user decisions as
|
|
5
|
+
* local artifacts and graph nodes so every override is auditable.
|
|
6
|
+
*/
|
|
7
|
+
import type { LocalGraphMemoryStore } from "../memory/local-graph-memory-store.js";
|
|
8
|
+
export type ProviderExceptionScope = "once" | "run" | "deny";
|
|
9
|
+
export interface ProviderExceptionRequest {
|
|
10
|
+
readonly providerId: string;
|
|
11
|
+
readonly runtimeMode?: string;
|
|
12
|
+
readonly blockedReason: string;
|
|
13
|
+
readonly retentionDays: number;
|
|
14
|
+
readonly jurisdictionRisk: number;
|
|
15
|
+
readonly localFallbackAvailable: boolean;
|
|
16
|
+
readonly taskSensitivity: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ProviderExceptionResult {
|
|
19
|
+
readonly decision: ProviderExceptionScope;
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
readonly artifactPath?: string;
|
|
22
|
+
readonly policyOverridePath?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ProviderExceptionApproverOptions {
|
|
25
|
+
readonly runId: string;
|
|
26
|
+
readonly nodeId: string;
|
|
27
|
+
readonly projectRoot?: string;
|
|
28
|
+
readonly graphStore?: LocalGraphMemoryStore;
|
|
29
|
+
readonly isTty?: boolean;
|
|
30
|
+
readonly promptUser?: (message: string) => Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export declare function requestProviderException(request: ProviderExceptionRequest, options: ProviderExceptionApproverOptions): Promise<ProviderExceptionResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd provider exception approval.
|
|
3
|
+
*
|
|
4
|
+
* Interactive approval flow for blocked providers. Records user decisions as
|
|
5
|
+
* local artifacts and graph nodes so every override is auditable.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
|
+
export async function requestProviderException(request, options) {
|
|
10
|
+
const root = resolve(options.projectRoot ?? process.cwd());
|
|
11
|
+
if (options.isTty === false) {
|
|
12
|
+
return { decision: "deny", reason: "non-interactive provider exception denied" };
|
|
13
|
+
}
|
|
14
|
+
const summary = [
|
|
15
|
+
`Provider: ${request.providerId}${request.runtimeMode ? ` (${request.runtimeMode})` : ""}`,
|
|
16
|
+
`Blocked reason: ${request.blockedReason}`,
|
|
17
|
+
`Retention: ${request.retentionDays} days`,
|
|
18
|
+
`Jurisdiction risk: ${request.jurisdictionRisk.toFixed(2)}`,
|
|
19
|
+
`Local fallback available: ${request.localFallbackAvailable}`,
|
|
20
|
+
`Task sensitivity: ${request.taskSensitivity}`,
|
|
21
|
+
].join("\n");
|
|
22
|
+
const choice = await (options.promptUser?.(summary) ?? Promise.resolve("deny"));
|
|
23
|
+
const normalized = choice.trim().toLowerCase();
|
|
24
|
+
if (normalized.startsWith("allow once")) {
|
|
25
|
+
const artifactPath = await writeExceptionArtifact(root, options.runId, options.nodeId, request, "once");
|
|
26
|
+
await materializeExceptionInGraph(options, request, "once", artifactPath);
|
|
27
|
+
return { decision: "once", reason: "user approved once", artifactPath };
|
|
28
|
+
}
|
|
29
|
+
if (normalized.startsWith("allow for run") || normalized.startsWith("allow run")) {
|
|
30
|
+
const artifactPath = await writeExceptionArtifact(root, options.runId, options.nodeId, request, "run");
|
|
31
|
+
const policyOverridePath = await writeRunPolicyOverride(root, options.runId, request);
|
|
32
|
+
await materializeExceptionInGraph(options, request, "run", artifactPath);
|
|
33
|
+
return { decision: "run", reason: "user approved for this run", artifactPath, policyOverridePath };
|
|
34
|
+
}
|
|
35
|
+
return { decision: "deny", reason: "user denied provider exception" };
|
|
36
|
+
}
|
|
37
|
+
async function writeExceptionArtifact(root, runId, nodeId, request, scope) {
|
|
38
|
+
const dir = join(root, ".omk", "runs", runId, "freedomd");
|
|
39
|
+
const path = join(dir, `${nodeId}-provider-exception.json`);
|
|
40
|
+
await mkdir(dirname(path), { recursive: true });
|
|
41
|
+
const payload = {
|
|
42
|
+
schemaVersion: "omk.freedomd.provider-exception.v1",
|
|
43
|
+
runId,
|
|
44
|
+
nodeId,
|
|
45
|
+
providerId: request.providerId,
|
|
46
|
+
runtimeMode: request.runtimeMode,
|
|
47
|
+
scope,
|
|
48
|
+
requestedAt: new Date().toISOString(),
|
|
49
|
+
request,
|
|
50
|
+
};
|
|
51
|
+
await writeFile(path, JSON.stringify(payload, null, 2), "utf-8");
|
|
52
|
+
return path;
|
|
53
|
+
}
|
|
54
|
+
async function writeRunPolicyOverride(root, runId, request) {
|
|
55
|
+
const dir = join(root, ".omk", "runs", runId, "freedomd");
|
|
56
|
+
const path = join(dir, "policy-overrides.json");
|
|
57
|
+
await mkdir(dirname(path), { recursive: true });
|
|
58
|
+
const payload = {
|
|
59
|
+
schemaVersion: "omk.freedomd.policy-overrides.v1",
|
|
60
|
+
runId,
|
|
61
|
+
providerOverrides: {
|
|
62
|
+
[request.providerId]: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
reason: request.blockedReason,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
createdAt: new Date().toISOString(),
|
|
68
|
+
};
|
|
69
|
+
await writeFile(path, JSON.stringify(payload, null, 2), "utf-8");
|
|
70
|
+
return path;
|
|
71
|
+
}
|
|
72
|
+
async function materializeExceptionInGraph(options, request, scope, _artifactPath) {
|
|
73
|
+
if (!options.graphStore)
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
await options.graphStore.materializeFreedomdSovereignty({
|
|
77
|
+
runId: options.runId,
|
|
78
|
+
nodeId: options.nodeId,
|
|
79
|
+
providerId: request.providerId,
|
|
80
|
+
runtimeMode: request.runtimeMode ?? "api",
|
|
81
|
+
sovereignty: {
|
|
82
|
+
mode: "freedomd",
|
|
83
|
+
dataBoundary: "internal",
|
|
84
|
+
retentionDecision: "allow",
|
|
85
|
+
jurisdictionDecision: "allow",
|
|
86
|
+
providerCutoffRisk: 0,
|
|
87
|
+
localFallbackAvailable: request.localFallbackAvailable,
|
|
88
|
+
reason: `provider exception approved: ${scope}`,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Best-effort graph materialization.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freedomd provider incident monitor.
|
|
3
|
+
*
|
|
4
|
+
* Reads local incident overrides from `.omk/provider-incidents.json` and
|
|
5
|
+
* optional per-project policy files. This avoids scraping public web pages by
|
|
6
|
+
* default while still letting operators react to events like Fable 5 quickly.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProviderIncidentState } from "./provider-sovereignty.js";
|
|
9
|
+
export interface ProviderIncidentFile {
|
|
10
|
+
readonly schemaVersion?: string;
|
|
11
|
+
readonly updatedAt?: string;
|
|
12
|
+
readonly incidents: readonly ProviderIncidentState[];
|
|
13
|
+
}
|
|
14
|
+
export interface LoadIncidentsOptions {
|
|
15
|
+
readonly projectRoot?: string;
|
|
16
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
17
|
+
readonly feedUrls?: readonly string[];
|
|
18
|
+
readonly fetch?: (url: string) => Promise<{
|
|
19
|
+
ok: boolean;
|
|
20
|
+
text: () => Promise<string>;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare function loadProviderIncidents(options?: LoadIncidentsOptions): Promise<ProviderIncidentState[]>;
|