nemoris 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +49 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/SECURITY.md +119 -0
- package/bin/nemoris +46 -0
- package/config/agents/agent.toml.example +28 -0
- package/config/agents/default.toml +22 -0
- package/config/agents/orchestrator.toml +18 -0
- package/config/delivery.toml +73 -0
- package/config/embeddings.toml +5 -0
- package/config/identity/default-purpose.md +1 -0
- package/config/identity/default-soul.md +3 -0
- package/config/identity/orchestrator-purpose.md +1 -0
- package/config/identity/orchestrator-soul.md +1 -0
- package/config/improvement-targets.toml +15 -0
- package/config/jobs/heartbeat-check.toml +30 -0
- package/config/jobs/memory-rollup.toml +46 -0
- package/config/jobs/workspace-health.toml +63 -0
- package/config/mcp.toml +16 -0
- package/config/output-contracts.toml +17 -0
- package/config/peers.toml +32 -0
- package/config/peers.toml.example +32 -0
- package/config/policies/memory-default.toml +10 -0
- package/config/policies/memory-heartbeat.toml +5 -0
- package/config/policies/memory-ops.toml +10 -0
- package/config/policies/tools-heartbeat-minimal.toml +8 -0
- package/config/policies/tools-interactive-safe.toml +8 -0
- package/config/policies/tools-ops-bounded.toml +8 -0
- package/config/policies/tools-orchestrator.toml +7 -0
- package/config/providers/anthropic.toml +15 -0
- package/config/providers/ollama.toml +5 -0
- package/config/providers/openai-codex.toml +9 -0
- package/config/providers/openrouter.toml +5 -0
- package/config/router.toml +22 -0
- package/config/runtime.toml +114 -0
- package/config/skills/self-improvement.toml +15 -0
- package/config/skills/telegram-onboarding-spec.md +240 -0
- package/config/skills/workspace-monitor.toml +15 -0
- package/config/task-router.toml +42 -0
- package/install.sh +50 -0
- package/package.json +90 -0
- package/src/auth/auth-profiles.js +169 -0
- package/src/auth/openai-codex-oauth.js +285 -0
- package/src/battle.js +449 -0
- package/src/cli/help.js +265 -0
- package/src/cli/output-filter.js +49 -0
- package/src/cli/runtime-control.js +704 -0
- package/src/cli-main.js +2763 -0
- package/src/cli.js +78 -0
- package/src/config/loader.js +332 -0
- package/src/config/schema-validator.js +214 -0
- package/src/config/toml-lite.js +8 -0
- package/src/daemon/action-handlers.js +71 -0
- package/src/daemon/healing-tick.js +87 -0
- package/src/daemon/health-probes.js +90 -0
- package/src/daemon/notifier.js +57 -0
- package/src/daemon/nurse.js +218 -0
- package/src/daemon/repair-log.js +106 -0
- package/src/daemon/rule-staging.js +90 -0
- package/src/daemon/rules.js +29 -0
- package/src/daemon/telegram-commands.js +54 -0
- package/src/daemon/updater.js +85 -0
- package/src/jobs/job-runner.js +78 -0
- package/src/mcp/consumer.js +129 -0
- package/src/memory/active-recall.js +171 -0
- package/src/memory/backend-manager.js +97 -0
- package/src/memory/backends/file-backend.js +38 -0
- package/src/memory/backends/qmd-backend.js +219 -0
- package/src/memory/embedding-guards.js +24 -0
- package/src/memory/embedding-index.js +118 -0
- package/src/memory/embedding-service.js +179 -0
- package/src/memory/file-index.js +177 -0
- package/src/memory/memory-signature.js +5 -0
- package/src/memory/memory-store.js +648 -0
- package/src/memory/retrieval-planner.js +66 -0
- package/src/memory/scoring.js +145 -0
- package/src/memory/simhash.js +78 -0
- package/src/memory/sqlite-active-store.js +824 -0
- package/src/memory/write-policy.js +36 -0
- package/src/onboarding/aliases.js +33 -0
- package/src/onboarding/auth/api-key.js +224 -0
- package/src/onboarding/auth/ollama-detect.js +42 -0
- package/src/onboarding/clack-prompter.js +77 -0
- package/src/onboarding/doctor.js +530 -0
- package/src/onboarding/lock.js +42 -0
- package/src/onboarding/model-catalog.js +344 -0
- package/src/onboarding/phases/auth.js +589 -0
- package/src/onboarding/phases/build.js +130 -0
- package/src/onboarding/phases/choose.js +82 -0
- package/src/onboarding/phases/detect.js +98 -0
- package/src/onboarding/phases/hatch.js +216 -0
- package/src/onboarding/phases/identity.js +79 -0
- package/src/onboarding/phases/ollama.js +345 -0
- package/src/onboarding/phases/scaffold.js +99 -0
- package/src/onboarding/phases/telegram.js +377 -0
- package/src/onboarding/phases/validate.js +204 -0
- package/src/onboarding/phases/verify.js +206 -0
- package/src/onboarding/platform.js +482 -0
- package/src/onboarding/status-bar.js +95 -0
- package/src/onboarding/templates.js +794 -0
- package/src/onboarding/toml-writer.js +38 -0
- package/src/onboarding/tui.js +250 -0
- package/src/onboarding/uninstall.js +153 -0
- package/src/onboarding/wizard.js +499 -0
- package/src/providers/anthropic.js +168 -0
- package/src/providers/base.js +247 -0
- package/src/providers/circuit-breaker.js +136 -0
- package/src/providers/ollama.js +163 -0
- package/src/providers/openai-codex.js +149 -0
- package/src/providers/openrouter.js +136 -0
- package/src/providers/registry.js +36 -0
- package/src/providers/router.js +16 -0
- package/src/runtime/bootstrap-cache.js +47 -0
- package/src/runtime/capabilities-prompt.js +25 -0
- package/src/runtime/completion-ping.js +99 -0
- package/src/runtime/config-validator.js +121 -0
- package/src/runtime/context-ledger.js +360 -0
- package/src/runtime/cutover-readiness.js +42 -0
- package/src/runtime/daemon.js +729 -0
- package/src/runtime/delivery-ack.js +195 -0
- package/src/runtime/delivery-adapters/local-file.js +41 -0
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -0
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -0
- package/src/runtime/delivery-adapters/shadow.js +13 -0
- package/src/runtime/delivery-adapters/standalone-http.js +98 -0
- package/src/runtime/delivery-adapters/telegram.js +104 -0
- package/src/runtime/delivery-adapters/tui.js +128 -0
- package/src/runtime/delivery-manager.js +807 -0
- package/src/runtime/delivery-store.js +168 -0
- package/src/runtime/dependency-health.js +118 -0
- package/src/runtime/envelope.js +114 -0
- package/src/runtime/evaluation.js +1089 -0
- package/src/runtime/exec-approvals.js +216 -0
- package/src/runtime/executor.js +500 -0
- package/src/runtime/failure-ping.js +67 -0
- package/src/runtime/flows.js +83 -0
- package/src/runtime/guards.js +45 -0
- package/src/runtime/handoff.js +51 -0
- package/src/runtime/identity-cache.js +28 -0
- package/src/runtime/improvement-engine.js +109 -0
- package/src/runtime/improvement-harness.js +581 -0
- package/src/runtime/input-sanitiser.js +72 -0
- package/src/runtime/interaction-contract.js +347 -0
- package/src/runtime/lane-readiness.js +226 -0
- package/src/runtime/migration.js +323 -0
- package/src/runtime/model-resolution.js +78 -0
- package/src/runtime/network.js +64 -0
- package/src/runtime/notification-store.js +97 -0
- package/src/runtime/notifier.js +256 -0
- package/src/runtime/orchestrator.js +53 -0
- package/src/runtime/orphan-reaper.js +41 -0
- package/src/runtime/output-contract-schema.js +139 -0
- package/src/runtime/output-contract-validator.js +439 -0
- package/src/runtime/peer-readiness.js +69 -0
- package/src/runtime/peer-registry.js +133 -0
- package/src/runtime/pilot-status.js +108 -0
- package/src/runtime/prompt-builder.js +261 -0
- package/src/runtime/provider-attempt.js +582 -0
- package/src/runtime/report-fallback.js +71 -0
- package/src/runtime/result-normalizer.js +183 -0
- package/src/runtime/retention.js +74 -0
- package/src/runtime/review.js +244 -0
- package/src/runtime/route-job.js +15 -0
- package/src/runtime/run-store.js +38 -0
- package/src/runtime/schedule.js +88 -0
- package/src/runtime/scheduler-state.js +434 -0
- package/src/runtime/scheduler.js +656 -0
- package/src/runtime/session-compactor.js +182 -0
- package/src/runtime/session-search.js +155 -0
- package/src/runtime/slack-inbound.js +249 -0
- package/src/runtime/ssrf.js +102 -0
- package/src/runtime/status-aggregator.js +330 -0
- package/src/runtime/task-contract.js +140 -0
- package/src/runtime/task-packet.js +107 -0
- package/src/runtime/task-router.js +140 -0
- package/src/runtime/telegram-inbound.js +1565 -0
- package/src/runtime/token-counter.js +134 -0
- package/src/runtime/token-estimator.js +59 -0
- package/src/runtime/tool-loop.js +200 -0
- package/src/runtime/transport-server.js +311 -0
- package/src/runtime/tui-server.js +411 -0
- package/src/runtime/ulid.js +44 -0
- package/src/security/ssrf-check.js +197 -0
- package/src/setup.js +369 -0
- package/src/shadow/bridge.js +303 -0
- package/src/skills/loader.js +84 -0
- package/src/tools/catalog.json +49 -0
- package/src/tools/cli-delegate.js +44 -0
- package/src/tools/mcp-client.js +106 -0
- package/src/tools/micro/cancel-task.js +6 -0
- package/src/tools/micro/complete-task.js +6 -0
- package/src/tools/micro/fail-task.js +6 -0
- package/src/tools/micro/http-fetch.js +74 -0
- package/src/tools/micro/index.js +36 -0
- package/src/tools/micro/lcm-recall.js +60 -0
- package/src/tools/micro/list-dir.js +17 -0
- package/src/tools/micro/list-skills.js +46 -0
- package/src/tools/micro/load-skill.js +38 -0
- package/src/tools/micro/memory-search.js +45 -0
- package/src/tools/micro/read-file.js +11 -0
- package/src/tools/micro/session-search.js +54 -0
- package/src/tools/micro/shell-exec.js +43 -0
- package/src/tools/micro/trigger-job.js +79 -0
- package/src/tools/micro/web-search.js +58 -0
- package/src/tools/micro/workspace-paths.js +39 -0
- package/src/tools/micro/write-file.js +14 -0
- package/src/tools/micro/write-memory.js +41 -0
- package/src/tools/registry.js +348 -0
- package/src/tools/tool-result-contract.js +36 -0
- package/src/tui/chat.js +835 -0
- package/src/tui/renderer.js +175 -0
- package/src/tui/socket-client.js +217 -0
- package/src/utils/canonical-json.js +29 -0
- package/src/utils/compaction.js +30 -0
- package/src/utils/env-loader.js +5 -0
- package/src/utils/errors.js +80 -0
- package/src/utils/fs.js +101 -0
- package/src/utils/ids.js +5 -0
- package/src/utils/model-context-limits.js +30 -0
- package/src/utils/token-budget.js +74 -0
- package/src/utils/usage-cost.js +25 -0
- package/src/utils/usage-metrics.js +14 -0
- package/vendor/smol-toml-1.5.2.tgz +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
function normalizeText(value) {
|
|
2
|
+
return String(value || "")
|
|
3
|
+
.replace(/\s+/g, " ")
|
|
4
|
+
.trim();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeInline(value) {
|
|
8
|
+
return normalizeText(value) || "None";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeSectionContent(value) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
const items = value.map((item) => normalizeText(item)).filter(Boolean);
|
|
14
|
+
return items.length ? items.join("; ") : "None";
|
|
15
|
+
}
|
|
16
|
+
if (value && typeof value === "object") {
|
|
17
|
+
const text = Object.values(value).map((item) => normalizeText(item)).filter(Boolean).join("; ");
|
|
18
|
+
return text || "None";
|
|
19
|
+
}
|
|
20
|
+
return normalizeInline(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stripOuterFence(text) {
|
|
24
|
+
const raw = String(text || "").trim();
|
|
25
|
+
const fencedMatch = raw.match(/^```(?:json|markdown|md|text)?\s*([\s\S]*?)\s*```$/i);
|
|
26
|
+
return fencedMatch ? fencedMatch[1].trim() : raw;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseJsonCandidate(text) {
|
|
30
|
+
const candidate = stripOuterFence(text);
|
|
31
|
+
if (!(candidate.startsWith("{") && candidate.endsWith("}"))) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(candidate);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeSectionKey(value) {
|
|
43
|
+
return String(value || "")
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/^#+\s*/, "")
|
|
46
|
+
.replace(/[*:_-]+/g, " ")
|
|
47
|
+
.replace(/\s+/g, " ")
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const SECTION_ALIASES = new Map([
|
|
52
|
+
["project", "projects"],
|
|
53
|
+
["next action", "next actions"]
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
function canonicalSectionKey(value) {
|
|
57
|
+
const normalized = normalizeSectionKey(value);
|
|
58
|
+
return SECTION_ALIASES.get(normalized) || normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getContractRules(contract = null) {
|
|
62
|
+
const profile = contract?.profile || null;
|
|
63
|
+
return {
|
|
64
|
+
requireStatus: profile?.requireStatus ?? (contract?.format === "bulleted_briefing"),
|
|
65
|
+
sectionStyle: profile?.sectionStyle || (contract?.format === "structured_rollup" ? "headings" : "bullets"),
|
|
66
|
+
requireSectionItems: profile?.requireSectionItems ?? true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseObjectOutput(value, contract) {
|
|
71
|
+
const entries = Object.entries(value || {});
|
|
72
|
+
const sections = new Map();
|
|
73
|
+
let status = null;
|
|
74
|
+
const rules = getContractRules(contract);
|
|
75
|
+
|
|
76
|
+
for (const [key, entryValue] of entries) {
|
|
77
|
+
const normalizedKey = canonicalSectionKey(key);
|
|
78
|
+
const normalizedValue = normalizeSectionContent(entryValue);
|
|
79
|
+
if (normalizedKey === "status") {
|
|
80
|
+
status = normalizedValue;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
sections.set(normalizedKey, normalizedValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (rules.requireStatus && !status) {
|
|
87
|
+
status = "None";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
source: "object",
|
|
92
|
+
status,
|
|
93
|
+
sections,
|
|
94
|
+
bulletSections: rules.sectionStyle === "bullets" ? sections.size : 0,
|
|
95
|
+
headingSections: rules.sectionStyle === "headings" ? sections.size : 0
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildCanonicalSections(contract, value, options = {}) {
|
|
100
|
+
const parsed = parseContractOutput(value, contract);
|
|
101
|
+
const requiredSections = contract?.requiredSections || [];
|
|
102
|
+
const sections = requiredSections.map((section) => {
|
|
103
|
+
const key = canonicalSectionKey(section);
|
|
104
|
+
return {
|
|
105
|
+
key,
|
|
106
|
+
label: section,
|
|
107
|
+
value: normalizeSectionContent(parsed.sections.get(key) || "None")
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
contract?.format === "structured_rollup" &&
|
|
113
|
+
options.shadowOnlyEvidence &&
|
|
114
|
+
sections.every((section) => section.key === "update" || section.value === "None")
|
|
115
|
+
) {
|
|
116
|
+
const updateSection = sections.find((section) => section.key === "update");
|
|
117
|
+
if (updateSection && updateSection.value === "None") {
|
|
118
|
+
updateSection.value = "Shadow import completed; monitor for new docs or memory before the next rollup.";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
parsed,
|
|
124
|
+
sections
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function renderContractOutput(contract, value, options = {}) {
|
|
129
|
+
if (!contract) return typeof value === "string" ? stripOuterFence(value) : normalizeSectionContent(value);
|
|
130
|
+
|
|
131
|
+
const { parsed, sections } = buildCanonicalSections(contract, value, options);
|
|
132
|
+
const rules = getContractRules(contract);
|
|
133
|
+
|
|
134
|
+
if (rules.sectionStyle === "headings") {
|
|
135
|
+
return sections
|
|
136
|
+
.map((section) => `## ${section.label.replace(/\b\w/g, (char) => char.toUpperCase())}\n- ${section.value}`)
|
|
137
|
+
.join("\n\n")
|
|
138
|
+
.trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lines = [];
|
|
142
|
+
if (rules.requireStatus) {
|
|
143
|
+
lines.push(`Status: ${normalizeSectionContent(parsed.status || options.status || "None")}`);
|
|
144
|
+
}
|
|
145
|
+
for (const section of sections) {
|
|
146
|
+
lines.push(`- ${section.label.replace(/\b\w/g, (char) => char.toUpperCase())}: ${section.value}`);
|
|
147
|
+
}
|
|
148
|
+
return lines.join("\n").trim();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseBulletedBriefing(text) {
|
|
152
|
+
const lines = String(text || "")
|
|
153
|
+
.split("\n")
|
|
154
|
+
.map((line) => line.trim())
|
|
155
|
+
.filter(Boolean);
|
|
156
|
+
const sections = new Map();
|
|
157
|
+
let status = null;
|
|
158
|
+
let bulletSections = 0;
|
|
159
|
+
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
const statusMatch = line.match(/^status:\s*(.+)$/i);
|
|
162
|
+
if (statusMatch) {
|
|
163
|
+
status = normalizeInline(statusMatch[1]);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const bulletMatch = line.match(/^[-*]\s+\**([^:*]+?)\**:\s*(.+)$/);
|
|
168
|
+
if (bulletMatch) {
|
|
169
|
+
sections.set(canonicalSectionKey(bulletMatch[1]), normalizeInline(bulletMatch[2]));
|
|
170
|
+
bulletSections += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const plainMatch = line.match(/^([^:]{2,40}):\s*(.+)$/);
|
|
175
|
+
if (plainMatch) {
|
|
176
|
+
const key = canonicalSectionKey(plainMatch[1]);
|
|
177
|
+
const value = normalizeInline(plainMatch[2]);
|
|
178
|
+
if (key === "status") status = value;
|
|
179
|
+
else sections.set(key, value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
source: "text",
|
|
185
|
+
status,
|
|
186
|
+
sections,
|
|
187
|
+
bulletSections,
|
|
188
|
+
headingSections: 0
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseStructuredRollup(text) {
|
|
193
|
+
const lines = String(text || "")
|
|
194
|
+
.split("\n")
|
|
195
|
+
.map((line) => line.trim());
|
|
196
|
+
const sections = new Map();
|
|
197
|
+
let current = null;
|
|
198
|
+
let headingSections = 0;
|
|
199
|
+
let bulletSections = 0;
|
|
200
|
+
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
if (!line) continue;
|
|
203
|
+
|
|
204
|
+
const headingMatch = line.match(/^#{1,6}\s+(.+)$/);
|
|
205
|
+
if (headingMatch) {
|
|
206
|
+
current = canonicalSectionKey(headingMatch[1]);
|
|
207
|
+
if (!sections.has(current)) sections.set(current, []);
|
|
208
|
+
headingSections += 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const bulletMatch = line.match(/^[-*]\s+(.+)$/);
|
|
213
|
+
if (bulletMatch && current) {
|
|
214
|
+
sections.get(current).push(normalizeInline(bulletMatch[1]));
|
|
215
|
+
bulletSections += 1;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (current) {
|
|
220
|
+
sections.get(current).push(normalizeInline(line));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const normalizedSections = new Map(
|
|
225
|
+
[...sections.entries()].map(([key, values]) => [key, values.filter(Boolean).join(" ") || "None"])
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
source: "text",
|
|
230
|
+
status: null,
|
|
231
|
+
sections: normalizedSections,
|
|
232
|
+
bulletSections,
|
|
233
|
+
headingSections
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function parseContractOutput(value, contract = null) {
|
|
238
|
+
if (value == null) {
|
|
239
|
+
return {
|
|
240
|
+
source: "empty",
|
|
241
|
+
status: null,
|
|
242
|
+
sections: new Map(),
|
|
243
|
+
bulletSections: 0,
|
|
244
|
+
headingSections: 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (typeof value === "string") {
|
|
249
|
+
const parsed = parseJsonCandidate(value);
|
|
250
|
+
if (parsed && typeof parsed === "object") {
|
|
251
|
+
return parseContractOutput(parsed.output !== undefined ? parsed.output : parsed, contract);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const text = stripOuterFence(value);
|
|
255
|
+
const rules = getContractRules(contract);
|
|
256
|
+
if (rules.sectionStyle === "headings") {
|
|
257
|
+
return parseStructuredRollup(text);
|
|
258
|
+
}
|
|
259
|
+
return parseBulletedBriefing(text);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
return parseContractOutput(value.join("\n"), contract);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (typeof value === "object") {
|
|
267
|
+
return parseObjectOutput(value, contract);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return parseContractOutput(String(value), contract);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function scoreFieldQuality(sectionContent) {
|
|
274
|
+
const raw = String(sectionContent || "");
|
|
275
|
+
const trimmed = raw.trim();
|
|
276
|
+
const lower = trimmed.toLowerCase();
|
|
277
|
+
const findings = [];
|
|
278
|
+
let score = 1;
|
|
279
|
+
|
|
280
|
+
if (!trimmed || lower === "none") {
|
|
281
|
+
return { score: 0, confidence: "none", findings: ["Section is empty or None"] };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (/\[(title|link|summary|sub)\]|<brief update>|<one-line status>/i.test(trimmed)) {
|
|
285
|
+
findings.push("Section contains placeholder text");
|
|
286
|
+
score -= 0.5;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const labelStripped = trimmed.replace(/^[^:]{2,40}:\s*/, "");
|
|
290
|
+
if (labelStripped.length < 10) {
|
|
291
|
+
findings.push("Section content is very short");
|
|
292
|
+
score -= 0.15;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (/\bsorry\b|\bcannot\b|\bfailed to\b|\bdoes not support\b/i.test(lower)) {
|
|
296
|
+
findings.push("Section contains apology or failure language");
|
|
297
|
+
score -= 0.3;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (/timed out|timeout/i.test(lower)) {
|
|
301
|
+
findings.push("Section references a timeout");
|
|
302
|
+
score -= 0.35;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (/^(no updates|nothing to report|n\/a|tbd|todo)$/i.test(lower) || /\bno updates\b|\bnothing to report\b/i.test(lower) && labelStripped.length < 25) {
|
|
306
|
+
findings.push("Section contains generic filler");
|
|
307
|
+
score -= 0.2;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
score = Number(Math.max(0, Math.min(1, score)).toFixed(4));
|
|
311
|
+
let confidence;
|
|
312
|
+
if (score >= 0.8) confidence = "high";
|
|
313
|
+
else if (score >= 0.5) confidence = "medium";
|
|
314
|
+
else if (score > 0) confidence = "low";
|
|
315
|
+
else confidence = "none";
|
|
316
|
+
|
|
317
|
+
return { score, confidence, findings };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function validateOutputContract(contract, value) {
|
|
321
|
+
if (!contract) return null;
|
|
322
|
+
|
|
323
|
+
const parsed = parseContractOutput(value, contract);
|
|
324
|
+
const requiredSections = contract.requiredSections || [];
|
|
325
|
+
const rules = getContractRules(contract);
|
|
326
|
+
const findings = [];
|
|
327
|
+
const sectionStates = requiredSections.map((section) => {
|
|
328
|
+
const normalizedSection = canonicalSectionKey(section);
|
|
329
|
+
const content = parsed.sections.get(normalizedSection) || null;
|
|
330
|
+
return {
|
|
331
|
+
section: normalizedSection,
|
|
332
|
+
present: parsed.sections.has(normalizedSection),
|
|
333
|
+
content,
|
|
334
|
+
nonEmpty: content != null && normalizeText(content).length > 0,
|
|
335
|
+
quality: scoreFieldQuality(content)
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const missingSections = sectionStates.filter((state) => !state.present).map((state) => state.section);
|
|
340
|
+
const emptySections = sectionStates.filter((state) => state.present && !state.nonEmpty).map((state) => state.section);
|
|
341
|
+
|
|
342
|
+
if (missingSections.length) {
|
|
343
|
+
findings.push(`Missing required sections: ${missingSections.join(", ")}.`);
|
|
344
|
+
}
|
|
345
|
+
if (emptySections.length) {
|
|
346
|
+
findings.push(`Required sections are empty: ${emptySections.join(", ")}.`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const formatSignals = {
|
|
350
|
+
source: parsed.source,
|
|
351
|
+
statusPresent: Boolean(parsed.status),
|
|
352
|
+
bulletSections: parsed.bulletSections,
|
|
353
|
+
headingSections: parsed.headingSections
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
if (rules.sectionStyle === "bullets") {
|
|
357
|
+
if (!formatSignals.statusPresent) {
|
|
358
|
+
findings.push("Bulleted briefing is missing a status line.");
|
|
359
|
+
}
|
|
360
|
+
if (rules.requireSectionItems && formatSignals.source === "text" && formatSignals.bulletSections < requiredSections.length) {
|
|
361
|
+
findings.push("Bulleted briefing does not render each required section as its own bullet.");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (rules.sectionStyle === "headings") {
|
|
366
|
+
if (formatSignals.source === "text" && formatSignals.headingSections < requiredSections.length) {
|
|
367
|
+
findings.push("Structured rollup does not render each required section as its own heading.");
|
|
368
|
+
}
|
|
369
|
+
if (rules.requireSectionItems && formatSignals.source === "text" && formatSignals.bulletSections < requiredSections.length) {
|
|
370
|
+
findings.push("Structured rollup does not provide bullet content under each required section.");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const sectionRatio =
|
|
375
|
+
requiredSections.length === 0
|
|
376
|
+
? 1
|
|
377
|
+
: Math.max(0, (requiredSections.length - missingSections.length - emptySections.length) / requiredSections.length);
|
|
378
|
+
|
|
379
|
+
let formatRatio = 1;
|
|
380
|
+
if (rules.sectionStyle === "bullets") {
|
|
381
|
+
const statusRatio = rules.requireStatus ? (formatSignals.statusPresent ? 1 : 0) : 1;
|
|
382
|
+
const bulletRatio =
|
|
383
|
+
requiredSections.length === 0 ? 1 : Math.min(1, formatSignals.bulletSections / requiredSections.length);
|
|
384
|
+
formatRatio = formatSignals.source === "text" ? (statusRatio + bulletRatio) / 2 : statusRatio;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (rules.sectionStyle === "headings") {
|
|
388
|
+
if (formatSignals.source === "text") {
|
|
389
|
+
const headingRatio =
|
|
390
|
+
requiredSections.length === 0 ? 1 : Math.min(1, formatSignals.headingSections / requiredSections.length);
|
|
391
|
+
const bulletRatio = rules.requireSectionItems
|
|
392
|
+
? requiredSections.length === 0
|
|
393
|
+
? 1
|
|
394
|
+
: Math.min(1, formatSignals.bulletSections / requiredSections.length)
|
|
395
|
+
: 1;
|
|
396
|
+
formatRatio = (headingRatio + bulletRatio) / 2;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const satisfiedRatio = Number(Math.max(0, Math.min(sectionRatio, formatRatio)).toFixed(4));
|
|
401
|
+
|
|
402
|
+
const scores = {};
|
|
403
|
+
const weakFields = [];
|
|
404
|
+
const placeholderFields = [];
|
|
405
|
+
for (const state of sectionStates) {
|
|
406
|
+
scores[state.section] = state.quality;
|
|
407
|
+
if (state.quality.score < 0.5) weakFields.push(state.section);
|
|
408
|
+
if (state.quality.findings.some((f) => /placeholder/i.test(f))) placeholderFields.push(state.section);
|
|
409
|
+
}
|
|
410
|
+
const allScores = sectionStates.map((s) => s.quality.score);
|
|
411
|
+
const averageScore = allScores.length
|
|
412
|
+
? Number((allScores.reduce((sum, v) => sum + v, 0) / allScores.length).toFixed(4))
|
|
413
|
+
: 0;
|
|
414
|
+
|
|
415
|
+
const fieldScores = {
|
|
416
|
+
scores,
|
|
417
|
+
averageScore,
|
|
418
|
+
weakFields,
|
|
419
|
+
placeholderFields
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
format: contract.format || null,
|
|
424
|
+
requiredSections,
|
|
425
|
+
styleHints: contract.styleHints || [],
|
|
426
|
+
profile: contract.profile || null,
|
|
427
|
+
parsed: {
|
|
428
|
+
status: parsed.status,
|
|
429
|
+
sections: Object.fromEntries(parsed.sections),
|
|
430
|
+
formatSignals
|
|
431
|
+
},
|
|
432
|
+
sectionStates,
|
|
433
|
+
satisfiedRatio: Math.max(0, satisfiedRatio),
|
|
434
|
+
missingSections,
|
|
435
|
+
emptySections,
|
|
436
|
+
fieldScores,
|
|
437
|
+
findings
|
|
438
|
+
};
|
|
439
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { OpenClawShadowBridge } from "../shadow/bridge.js";
|
|
2
|
+
import { PeerRegistry } from "./peer-registry.js";
|
|
3
|
+
|
|
4
|
+
function resolveNamedProfile(profiles, profileName) {
|
|
5
|
+
if (!profileName) return null;
|
|
6
|
+
return profiles[profileName] || profiles[String(profileName).replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())] || null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class PeerReadinessProbe {
|
|
10
|
+
constructor({ liveRoot } = {}) {
|
|
11
|
+
this.bridge = new OpenClawShadowBridge({ liveRoot });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async probe(runtime, peerId) {
|
|
15
|
+
const registry = new PeerRegistry(runtime.peers);
|
|
16
|
+
const peer = registry.get(peerId);
|
|
17
|
+
if (!peer) {
|
|
18
|
+
return {
|
|
19
|
+
peerId,
|
|
20
|
+
knownPeer: false,
|
|
21
|
+
routeResolvable: false,
|
|
22
|
+
targetReachable: false,
|
|
23
|
+
deliveryProfileValid: false,
|
|
24
|
+
readiness: "unknown_peer",
|
|
25
|
+
findings: ["Peer is not present in the trusted registry."]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const resolved = registry.resolveTarget({ mode: "peer_agent", peerId });
|
|
30
|
+
const deliveryProfile = resolved.deliveryProfile || runtime.delivery?.defaultPeerProfile || null;
|
|
31
|
+
const profileValid = Boolean(resolveNamedProfile(runtime.delivery?.profiles || {}, deliveryProfile));
|
|
32
|
+
const routeResolvable = Boolean(resolved.sessionKey);
|
|
33
|
+
let targetReachable;
|
|
34
|
+
const findings = [];
|
|
35
|
+
|
|
36
|
+
if (!profileValid) findings.push(`Delivery profile is missing: ${deliveryProfile || "(none)"}`);
|
|
37
|
+
if (!routeResolvable) findings.push("Peer route does not have a resolvable session key.");
|
|
38
|
+
|
|
39
|
+
if (this.bridge.available && peer.agentId) {
|
|
40
|
+
const sessionIndex = await this.bridge.loadSessionIndex(peer.agentId);
|
|
41
|
+
targetReachable = Boolean(resolved.sessionKey && sessionIndex?.[resolved.sessionKey]);
|
|
42
|
+
if (!targetReachable && routeResolvable) {
|
|
43
|
+
findings.push("Peer route is resolvable but not currently present in the live session index.");
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
targetReachable = routeResolvable && profileValid;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const readiness = !profileValid
|
|
50
|
+
? "profile_invalid"
|
|
51
|
+
: !routeResolvable
|
|
52
|
+
? "route_unresolvable"
|
|
53
|
+
: targetReachable
|
|
54
|
+
? "ready"
|
|
55
|
+
: "route_only";
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
peerId,
|
|
59
|
+
knownPeer: true,
|
|
60
|
+
routeResolvable,
|
|
61
|
+
targetReachable,
|
|
62
|
+
deliveryProfileValid: profileValid,
|
|
63
|
+
deliveryProfile,
|
|
64
|
+
sessionKey: resolved.sessionKey || null,
|
|
65
|
+
readiness,
|
|
66
|
+
findings
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export class PeerRegistry {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.peers = config.peers || {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
get(peerId) {
|
|
7
|
+
return this.peers[peerId] || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
list() {
|
|
11
|
+
return Object.entries(this.peers).map(([peerId, peer]) => ({
|
|
12
|
+
peerId,
|
|
13
|
+
...peer
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
listCards() {
|
|
18
|
+
return this.list()
|
|
19
|
+
.filter((peer) => peer.card)
|
|
20
|
+
.map((peer) => ({
|
|
21
|
+
peerId: peer.peerId,
|
|
22
|
+
agentId: peer.agentId || null,
|
|
23
|
+
label: peer.label || peer.peerId,
|
|
24
|
+
deliveryProfile: peer.deliveryProfile || null,
|
|
25
|
+
sessionKeys: peer.sessionKeys || [],
|
|
26
|
+
...peer.card
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
findByCapability(query) {
|
|
31
|
+
const terms = String(query || "")
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.split(/[^a-z0-9]+/i)
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (!terms.length) return this.listCards();
|
|
36
|
+
|
|
37
|
+
return this.listCards()
|
|
38
|
+
.map((card) => {
|
|
39
|
+
const haystack = [
|
|
40
|
+
card.label,
|
|
41
|
+
card.role,
|
|
42
|
+
card.mission,
|
|
43
|
+
...(card.capabilityTags || []),
|
|
44
|
+
...(card.preferredTaskClasses || [])
|
|
45
|
+
]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(" ")
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
const matches = terms.filter((term) => haystack.includes(term));
|
|
50
|
+
return {
|
|
51
|
+
...card,
|
|
52
|
+
matchCount: matches.length
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
.filter((card) => card.matchCount > 0)
|
|
56
|
+
.sort((a, b) => b.matchCount - a.matchCount || a.label.localeCompare(b.label));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
suggestPeers({ taskType = null, query = "", preferredDeliveryProfile = null, trustLevel = null, limit = 3 } = {}) {
|
|
60
|
+
const cards = this.listCards();
|
|
61
|
+
const queryTerms = String(query || "")
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.split(/[^a-z0-9]+/i)
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
const taskKey = String(taskType || "").toLowerCase().trim();
|
|
66
|
+
|
|
67
|
+
return cards
|
|
68
|
+
.map((card) => {
|
|
69
|
+
let score = 0;
|
|
70
|
+
const reasons = [];
|
|
71
|
+
|
|
72
|
+
if (taskKey && (card.preferredTaskClasses || []).map((item) => String(item).toLowerCase()).includes(taskKey)) {
|
|
73
|
+
score += 4;
|
|
74
|
+
reasons.push(`preferred task class:${taskKey}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tagMatches = (card.capabilityTags || []).filter((tag) => queryTerms.includes(String(tag).toLowerCase()));
|
|
78
|
+
if (tagMatches.length) {
|
|
79
|
+
score += tagMatches.length * 3;
|
|
80
|
+
reasons.push(`capability tags:${tagMatches.join(",")}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const text = [card.role, card.mission, ...(card.capabilityTags || []), ...(card.preferredTaskClasses || [])]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join(" ")
|
|
86
|
+
.toLowerCase();
|
|
87
|
+
const fuzzyMatches = queryTerms.filter((term) => text.includes(term) && !tagMatches.includes(term));
|
|
88
|
+
if (fuzzyMatches.length) {
|
|
89
|
+
score += fuzzyMatches.length;
|
|
90
|
+
reasons.push(`text match:${fuzzyMatches.join(",")}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (preferredDeliveryProfile && (card.deliveryPreferences || []).includes(preferredDeliveryProfile)) {
|
|
94
|
+
score += 1;
|
|
95
|
+
reasons.push(`delivery:${preferredDeliveryProfile}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (trustLevel && card.trustLevel === trustLevel) {
|
|
99
|
+
score += 1;
|
|
100
|
+
reasons.push(`trust:${trustLevel}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
...card,
|
|
105
|
+
score,
|
|
106
|
+
reasons
|
|
107
|
+
};
|
|
108
|
+
})
|
|
109
|
+
.filter((card) => card.score > 0)
|
|
110
|
+
.sort((a, b) => b.score - a.score || a.label.localeCompare(b.label))
|
|
111
|
+
.slice(0, limit);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
resolveTarget(target) {
|
|
115
|
+
if (!target || typeof target !== "object") return null;
|
|
116
|
+
if (target.mode !== "peer_agent") return null;
|
|
117
|
+
|
|
118
|
+
const peerId = target.peerId || target.agentId || null;
|
|
119
|
+
const peer = peerId ? this.get(peerId) : null;
|
|
120
|
+
if (!peer) {
|
|
121
|
+
throw new Error(`Unknown peer target ${peerId || "(missing peerId)"}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
peerId,
|
|
126
|
+
peer,
|
|
127
|
+
sessionKey: target.sessionKey || peer.sessionKeys?.[0] || null,
|
|
128
|
+
deliveryProfile: target.deliveryProfile || peer.deliveryProfile || null,
|
|
129
|
+
label: peer.label || peerId,
|
|
130
|
+
card: peer.card || null
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|