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,347 @@
|
|
|
1
|
+
import { parseContractOutput } from "./output-contract-validator.js";
|
|
2
|
+
|
|
3
|
+
function dedupe(items) {
|
|
4
|
+
return [...new Set((items || []).map((item) => String(item).trim()).filter(Boolean))];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeSectionKey(value) {
|
|
8
|
+
return String(value || "")
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[\s-]+/g, "_")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeText(value) {
|
|
15
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function trimValue(value, limit = 220) {
|
|
19
|
+
const text = normalizeText(value);
|
|
20
|
+
if (text.length <= limit) return text;
|
|
21
|
+
return `${text.slice(0, limit - 3)}...`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeNextActions(nextActions = []) {
|
|
25
|
+
return (nextActions || [])
|
|
26
|
+
.map((item) => {
|
|
27
|
+
if (typeof item === "string") return normalizeText(item);
|
|
28
|
+
if (item && typeof item === "object") {
|
|
29
|
+
const action = normalizeText(item.action || "");
|
|
30
|
+
const details = normalizeText(item.details || item.summary || "");
|
|
31
|
+
return [action, details].filter(Boolean).join(": ");
|
|
32
|
+
}
|
|
33
|
+
return normalizeText(item);
|
|
34
|
+
})
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeInteractionContract(contract = null) {
|
|
39
|
+
const raw = contract || {};
|
|
40
|
+
const progressAfterSeconds = raw.progressAfterSeconds ?? 90;
|
|
41
|
+
const handoff = raw.handoff || {};
|
|
42
|
+
const yieldConfig = raw.yield || {};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
ackMode: raw.ackMode || "immediate",
|
|
46
|
+
ackText: raw.ackText || null,
|
|
47
|
+
progressMode: raw.progressMode || "long_running",
|
|
48
|
+
progressAfterSeconds,
|
|
49
|
+
maxSilenceSeconds: raw.maxSilenceSeconds ?? Math.max(progressAfterSeconds * 2, 180),
|
|
50
|
+
notifyOnDone: raw.notifyOnDone ?? true,
|
|
51
|
+
notifyOnError: raw.notifyOnError ?? true,
|
|
52
|
+
requiresPingback: raw.requiresPingback ?? true,
|
|
53
|
+
pingbackTarget: raw.pingbackTarget || "same_thread",
|
|
54
|
+
completionSignal: raw.completionSignal || "done",
|
|
55
|
+
failureSignal: raw.failureSignal || "error",
|
|
56
|
+
handoffFormat: raw.handoffFormat || "structured_handoff",
|
|
57
|
+
completionSections: dedupe(raw.completionSections || ["status", "next_actions"]),
|
|
58
|
+
includeNextActions: raw.includeNextActions ?? true,
|
|
59
|
+
handoff: {
|
|
60
|
+
enabled: handoff.enabled ?? false,
|
|
61
|
+
signal: handoff.signal || raw.handoffSignal || "peer_handoff",
|
|
62
|
+
target: handoff.target || raw.handoffTarget || null,
|
|
63
|
+
deliveryProfile: handoff.deliveryProfile || raw.handoffDeliveryProfile || null,
|
|
64
|
+
capabilityQuery: handoff.capabilityQuery || raw.handoffCapabilityQuery || null,
|
|
65
|
+
preferredTaskClasses: dedupe(handoff.preferredTaskClasses || raw.handoffPreferredTaskClasses || []),
|
|
66
|
+
trustLevel: handoff.trustLevel || raw.handoffTrustLevel || null,
|
|
67
|
+
maxSuggestions: handoff.maxSuggestions ?? raw.handoffMaxSuggestions ?? 3,
|
|
68
|
+
messagePrefix: handoff.messagePrefix || raw.handoffMessagePrefix || null
|
|
69
|
+
},
|
|
70
|
+
yield: {
|
|
71
|
+
enabled: yieldConfig.enabled ?? raw.yieldEnabled ?? false,
|
|
72
|
+
signal: yieldConfig.signal || raw.yieldSignal || "follow_up",
|
|
73
|
+
targetSurface: yieldConfig.targetSurface || raw.yieldTargetSurface || "operator_review",
|
|
74
|
+
followUpObjective: yieldConfig.followUpObjective || raw.followUpObjective || null
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function resolveInteractionContract(agentContract = null, jobContract = null) {
|
|
80
|
+
return normalizeInteractionContract({
|
|
81
|
+
...(agentContract || {}),
|
|
82
|
+
...(jobContract || {})
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function formatInteractionContract(contract) {
|
|
87
|
+
if (!contract) return "";
|
|
88
|
+
|
|
89
|
+
const lines = [
|
|
90
|
+
`- ack mode: ${contract.ackMode}`,
|
|
91
|
+
`- progress mode: ${contract.progressMode}`,
|
|
92
|
+
`- progress after seconds: ${contract.progressAfterSeconds}`,
|
|
93
|
+
`- max silence seconds: ${contract.maxSilenceSeconds}`,
|
|
94
|
+
`- requires pingback: ${contract.requiresPingback}`,
|
|
95
|
+
`- pingback target: ${contract.pingbackTarget}`,
|
|
96
|
+
`- notify on done: ${contract.notifyOnDone}`,
|
|
97
|
+
`- notify on error: ${contract.notifyOnError}`,
|
|
98
|
+
`- completion signal: ${contract.completionSignal}`,
|
|
99
|
+
`- handoff format: ${contract.handoffFormat}`
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (contract.completionSections?.length) {
|
|
103
|
+
lines.push(`- completion sections: ${contract.completionSections.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
if (contract.handoff?.enabled) {
|
|
106
|
+
lines.push(`- handoff enabled: yes`);
|
|
107
|
+
if (contract.handoff.capabilityQuery) lines.push(`- handoff capability query: ${contract.handoff.capabilityQuery}`);
|
|
108
|
+
if (contract.handoff.preferredTaskClasses?.length) {
|
|
109
|
+
lines.push(`- handoff preferred tasks: ${contract.handoff.preferredTaskClasses.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
if (contract.handoff.target) {
|
|
112
|
+
lines.push(`- handoff target: ${typeof contract.handoff.target === "string" ? contract.handoff.target : JSON.stringify(contract.handoff.target)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (contract.yield?.enabled) {
|
|
116
|
+
lines.push(`- yield enabled: yes`);
|
|
117
|
+
lines.push(`- yield target surface: ${contract.yield.targetSurface}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function outputSectionMap(output, result, contract = null) {
|
|
124
|
+
const entries = [];
|
|
125
|
+
if (contract?.requiredSections?.length) {
|
|
126
|
+
const parsed = parseContractOutput(output, contract);
|
|
127
|
+
for (const section of contract.requiredSections) {
|
|
128
|
+
const key = normalizeSectionKey(section);
|
|
129
|
+
entries.push([key, trimValue(parsed.sections.get(key) || "None")]);
|
|
130
|
+
}
|
|
131
|
+
} else if (output && typeof output === "object" && !Array.isArray(output)) {
|
|
132
|
+
for (const [key, value] of Object.entries(output)) {
|
|
133
|
+
entries.push([normalizeSectionKey(key), trimValue(value)]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
entries.push(["status", trimValue(result.summary || "Completed.")]);
|
|
138
|
+
entries.push(["summary", trimValue(result.summary || "Completed.")]);
|
|
139
|
+
const nextActions = normalizeNextActions(result.nextActions || []);
|
|
140
|
+
entries.push(["next_actions", nextActions.length ? nextActions.join("; ") : "None"]);
|
|
141
|
+
entries.push(["next", nextActions.length ? nextActions.join("; ") : "None"]);
|
|
142
|
+
entries.push(["handoff", trimValue(typeof output === "string" ? output : JSON.stringify(output || result.output || ""))]);
|
|
143
|
+
entries.push(["evidence", trimValue(typeof output === "string" ? output : JSON.stringify(output || result.output || ""))]);
|
|
144
|
+
entries.push(["changes", trimValue(typeof output === "string" ? output : JSON.stringify(output || result.output || ""))]);
|
|
145
|
+
entries.push(["verification", nextActions.length ? nextActions.join("; ") : "No explicit verification captured."]);
|
|
146
|
+
entries.push(["issues", "None"]);
|
|
147
|
+
|
|
148
|
+
return new Map(entries);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildCompletionSections(contract, result) {
|
|
152
|
+
const output = result.output;
|
|
153
|
+
const map = outputSectionMap(output, result, result.outputContract || null);
|
|
154
|
+
|
|
155
|
+
return contract.completionSections.map((section) => ({
|
|
156
|
+
key: normalizeSectionKey(section),
|
|
157
|
+
label: String(section)
|
|
158
|
+
.replace(/_/g, " ")
|
|
159
|
+
.replace(/\b\w/g, (char) => char.toUpperCase()),
|
|
160
|
+
value: map.get(normalizeSectionKey(section)) || "None"
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderCompletionMessage(plan, contract, sections) {
|
|
165
|
+
if (contract.handoffFormat === "concise_status") {
|
|
166
|
+
const status = sections.find((section) => section.key === "status")?.value || plan.job.id;
|
|
167
|
+
const next = sections.find((section) => section.key === "next_actions")?.value || "None";
|
|
168
|
+
return `[${contract.completionSignal}] ${status} | next: ${next}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (contract.handoffFormat === "coding_completion") {
|
|
172
|
+
const lines = [`${contract.completionSignal.toUpperCase()}: ${plan.job.id}`];
|
|
173
|
+
for (const section of sections) {
|
|
174
|
+
lines.push(`${section.label}: ${section.value}`);
|
|
175
|
+
}
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
`${contract.completionSignal.toUpperCase()}: ${plan.job.id}`,
|
|
181
|
+
...sections.map((section) => `- ${section.label}: ${section.value}`)
|
|
182
|
+
].join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeHandoffTarget(target, deliveryProfile = null) {
|
|
186
|
+
if (!target) return null;
|
|
187
|
+
if (typeof target === "string") {
|
|
188
|
+
return {
|
|
189
|
+
mode: "peer_agent",
|
|
190
|
+
peerId: target,
|
|
191
|
+
deliveryProfile
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...target,
|
|
196
|
+
deliveryProfile: target.deliveryProfile || deliveryProfile || null
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildHandoffMessage(plan, contract, sections, suggestions = []) {
|
|
201
|
+
const status = sections.find((section) => section.key === "status")?.value || resultSummaryFallback(plan);
|
|
202
|
+
const nextActions = sections.find((section) => section.key === "next_actions")?.value || "None";
|
|
203
|
+
const lines = [];
|
|
204
|
+
|
|
205
|
+
if (contract.handoff.messagePrefix) {
|
|
206
|
+
lines.push(contract.handoff.messagePrefix);
|
|
207
|
+
}
|
|
208
|
+
lines.push(`[${contract.handoff.signal}] ${plan.job.id}`);
|
|
209
|
+
lines.push(`Status: ${status}`);
|
|
210
|
+
lines.push(`Next Actions: ${nextActions}`);
|
|
211
|
+
|
|
212
|
+
if (suggestions.length) {
|
|
213
|
+
lines.push(`Suggested Peers: ${suggestions.map((item) => item.label).join(", ")}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resultSummaryFallback(plan) {
|
|
220
|
+
return `${plan.job.id} completed.`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildHandoffPlan(plan, contract, sections, suggestions = []) {
|
|
224
|
+
if (!contract.handoff?.enabled) return null;
|
|
225
|
+
|
|
226
|
+
const target = normalizeHandoffTarget(contract.handoff.target, contract.handoff.deliveryProfile);
|
|
227
|
+
return {
|
|
228
|
+
required: true,
|
|
229
|
+
signal: contract.handoff.signal,
|
|
230
|
+
target,
|
|
231
|
+
deliveryProfile: contract.handoff.deliveryProfile || target?.deliveryProfile || null,
|
|
232
|
+
suggestions: suggestions.slice(0, contract.handoff.maxSuggestions || 3),
|
|
233
|
+
capabilityQuery: contract.handoff.capabilityQuery || null,
|
|
234
|
+
preferredTaskClasses: contract.handoff.preferredTaskClasses || [],
|
|
235
|
+
sections,
|
|
236
|
+
nextActions: contract.includeNextActions ? sections.find((section) => section.key === "next_actions")?.value?.split("; ").filter(Boolean) || [] : [],
|
|
237
|
+
message: buildHandoffMessage(plan, contract, sections, suggestions)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildYieldPlan(plan, contract, result, completion, handoff) {
|
|
242
|
+
if (!contract.yield?.enabled) return null;
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
required: true,
|
|
246
|
+
signal: contract.yield.signal,
|
|
247
|
+
targetSurface: contract.yield.targetSurface || "operator_review",
|
|
248
|
+
objective:
|
|
249
|
+
contract.yield.followUpObjective ||
|
|
250
|
+
`Continue ${plan.job.id} follow-up in a bounded next step without extending the current turn.`,
|
|
251
|
+
followUp: {
|
|
252
|
+
sourceJobId: plan.job.id,
|
|
253
|
+
sourceAgentId: plan.agent?.id || plan.packet?.agentId || null,
|
|
254
|
+
signal: contract.yield.signal,
|
|
255
|
+
objective:
|
|
256
|
+
contract.yield.followUpObjective ||
|
|
257
|
+
`Continue ${plan.job.id} follow-up in a bounded next step without extending the current turn.`,
|
|
258
|
+
targetSurface: contract.yield.targetSurface || "operator_review",
|
|
259
|
+
sections: completion?.sections || [],
|
|
260
|
+
nextActions: result.nextActions || [],
|
|
261
|
+
completion: completion?.required
|
|
262
|
+
? {
|
|
263
|
+
signal: completion.signal,
|
|
264
|
+
target: completion.target,
|
|
265
|
+
handoffFormat: completion.handoffFormat,
|
|
266
|
+
sections: completion.sections || [],
|
|
267
|
+
nextActions: completion.nextActions || [],
|
|
268
|
+
message: completion.message
|
|
269
|
+
}
|
|
270
|
+
: null,
|
|
271
|
+
handoff: handoff?.required
|
|
272
|
+
? {
|
|
273
|
+
signal: handoff.signal,
|
|
274
|
+
target: handoff.target || null,
|
|
275
|
+
deliveryProfile: handoff.deliveryProfile || null,
|
|
276
|
+
suggestions: handoff.suggestions || [],
|
|
277
|
+
capabilityQuery: handoff.capabilityQuery || null,
|
|
278
|
+
preferredTaskClasses: handoff.preferredTaskClasses || [],
|
|
279
|
+
sections: handoff.sections || [],
|
|
280
|
+
nextActions: handoff.nextActions || [],
|
|
281
|
+
message: handoff.message
|
|
282
|
+
}
|
|
283
|
+
: null
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function buildInteractionPlan(plan, result, error = null, options = {}) {
|
|
289
|
+
const contract = normalizeInteractionContract(plan.packet.layers.interactionContract);
|
|
290
|
+
result.outputContract = plan.packet.layers.outputContract || null;
|
|
291
|
+
|
|
292
|
+
const ack =
|
|
293
|
+
contract.ackMode === "silent"
|
|
294
|
+
? {
|
|
295
|
+
required: false,
|
|
296
|
+
mode: contract.ackMode,
|
|
297
|
+
target: contract.pingbackTarget,
|
|
298
|
+
message: null
|
|
299
|
+
}
|
|
300
|
+
: {
|
|
301
|
+
required: true,
|
|
302
|
+
mode: contract.ackMode,
|
|
303
|
+
target: contract.pingbackTarget,
|
|
304
|
+
message:
|
|
305
|
+
contract.ackText ||
|
|
306
|
+
`Acknowledged ${plan.job.id}. I will ping back with ${contract.completionSignal} when the run is finished.`
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const completionSections = buildCompletionSections(contract, result);
|
|
310
|
+
const handoff = buildHandoffPlan(plan, contract, completionSections, options.handoffSuggestions || []);
|
|
311
|
+
const completion = {
|
|
312
|
+
required: contract.notifyOnDone || contract.requiresPingback,
|
|
313
|
+
signal: contract.completionSignal,
|
|
314
|
+
target: contract.pingbackTarget,
|
|
315
|
+
handoffFormat: contract.handoffFormat,
|
|
316
|
+
sections: completionSections,
|
|
317
|
+
nextActions: contract.includeNextActions ? result.nextActions || [] : [],
|
|
318
|
+
message: renderCompletionMessage(plan, contract, completionSections)
|
|
319
|
+
};
|
|
320
|
+
const yieldPlan = error ? null : buildYieldPlan(plan, contract, result, completion, handoff);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
ack,
|
|
324
|
+
progress: {
|
|
325
|
+
enabled: contract.progressMode !== "none",
|
|
326
|
+
mode: contract.progressMode,
|
|
327
|
+
afterSeconds: contract.progressAfterSeconds,
|
|
328
|
+
maxSilenceSeconds: contract.maxSilenceSeconds,
|
|
329
|
+
target: contract.pingbackTarget,
|
|
330
|
+
message:
|
|
331
|
+
contract.progressMode === "none"
|
|
332
|
+
? null
|
|
333
|
+
: `Still working on ${plan.job.id}. Send a progress ping before ${contract.maxSilenceSeconds}s of silence.`
|
|
334
|
+
},
|
|
335
|
+
completion,
|
|
336
|
+
handoff,
|
|
337
|
+
yield: yieldPlan,
|
|
338
|
+
error: {
|
|
339
|
+
required: contract.notifyOnError,
|
|
340
|
+
signal: contract.failureSignal,
|
|
341
|
+
target: contract.pingbackTarget,
|
|
342
|
+
message: error
|
|
343
|
+
? `[${contract.failureSignal}] ${plan.job.id}: ${trimValue(error.message || error)}`
|
|
344
|
+
: null
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
function truthy(value) {
|
|
2
|
+
return value === true;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function hasBlockingInteractionIssues(interaction = null) {
|
|
6
|
+
if (!interaction) return false;
|
|
7
|
+
return Boolean(
|
|
8
|
+
(interaction.deliveryEvidenceRequired && !interaction.deliveryEvidenceHealthy) ||
|
|
9
|
+
(interaction.yielded && interaction.followUpQueued && !interaction.followUpConsumed) ||
|
|
10
|
+
interaction.followUpExpired ||
|
|
11
|
+
interaction.handoffExpired ||
|
|
12
|
+
interaction.handoffBlocked ||
|
|
13
|
+
(interaction.handoffRequired && interaction.handoffQueued && !interaction.handoffChosen && !interaction.handoffPendingChoice) ||
|
|
14
|
+
interaction.deliveryUncertain
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function summarizeFallback(reportFallback = null) {
|
|
19
|
+
if (!reportFallback) {
|
|
20
|
+
return {
|
|
21
|
+
configured: false,
|
|
22
|
+
ready: true,
|
|
23
|
+
blockedReasons: []
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
configured: Boolean(reportFallback.jobConfigured),
|
|
29
|
+
ready: Boolean(!reportFallback.jobConfigured || reportFallback.ready),
|
|
30
|
+
blockedReasons: reportFallback.blockedReasons || []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isStaleError(health) {
|
|
35
|
+
if (!health.lastError) return false;
|
|
36
|
+
if (!health.lastSuccessAt) return false;
|
|
37
|
+
const successMs = new Date(health.lastSuccessAt).getTime();
|
|
38
|
+
const errorMs = health.lastErrorAt ? new Date(health.lastErrorAt).getTime() : 0;
|
|
39
|
+
return successMs > errorMs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function summarizeEmbeddings(embeddingHealth = null) {
|
|
43
|
+
const health = embeddingHealth?.embeddingHealth || embeddingHealth || null;
|
|
44
|
+
if (!health) {
|
|
45
|
+
return {
|
|
46
|
+
available: false,
|
|
47
|
+
healthy: false,
|
|
48
|
+
degraded: true,
|
|
49
|
+
blockedReasons: ["embedding_health_missing"]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const blockedReasons = [];
|
|
54
|
+
if (health.disabled) blockedReasons.push("embeddings_disabled");
|
|
55
|
+
if (health.lastError && !isStaleError(health)) blockedReasons.push("embedding_error");
|
|
56
|
+
if (health.missingCount > 0 && health.freshCount === 0) blockedReasons.push("embeddings_missing");
|
|
57
|
+
if (health.failedCount > 0) blockedReasons.push("embedding_failures");
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
available: !health.disabled,
|
|
61
|
+
healthy: blockedReasons.length === 0,
|
|
62
|
+
degraded: blockedReasons.length > 0,
|
|
63
|
+
blockedReasons,
|
|
64
|
+
health
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function reconcileEmbeddingsWithRetrieval(embeddings, retrieval) {
|
|
69
|
+
if (!embeddings) return embeddings;
|
|
70
|
+
if (!retrieval) return embeddings;
|
|
71
|
+
|
|
72
|
+
const retrievalShowsHealthyEmbeddingQuery =
|
|
73
|
+
retrieval.embeddingQueryMode === "embedding_query" &&
|
|
74
|
+
Number(retrieval.semanticCount || 0) > 0 &&
|
|
75
|
+
!retrieval.embeddingError &&
|
|
76
|
+
Number(retrieval.failedEmbeddingCount || 0) === 0;
|
|
77
|
+
|
|
78
|
+
if (!retrievalShowsHealthyEmbeddingQuery) {
|
|
79
|
+
return embeddings;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const blockedReasons = (embeddings.blockedReasons || []).filter((reason) => reason !== "embedding_error");
|
|
83
|
+
return {
|
|
84
|
+
...embeddings,
|
|
85
|
+
healthy: blockedReasons.length === 0,
|
|
86
|
+
degraded: blockedReasons.length > 0,
|
|
87
|
+
blockedReasons
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildEffectiveRetrieval(retrieval = null, embeddings = null) {
|
|
92
|
+
if (!retrieval) return null;
|
|
93
|
+
|
|
94
|
+
const effective = { ...retrieval };
|
|
95
|
+
const embeddingHealth = embeddings?.health || null;
|
|
96
|
+
|
|
97
|
+
// Only adopt the embedding health's lastQueryMode when the evaluation's
|
|
98
|
+
// own retrieval doesn't already show a healthy semantic result. This
|
|
99
|
+
// prevents a stale "lexical_fallback" from overwriting a genuinely
|
|
100
|
+
// healthy "embedding_query" captured during the most recent run.
|
|
101
|
+
const evalAlreadyHealthy =
|
|
102
|
+
retrieval.embeddingQueryMode === "embedding_query" &&
|
|
103
|
+
Number(retrieval.semanticCount || 0) > 0 &&
|
|
104
|
+
!retrieval.embeddingError;
|
|
105
|
+
|
|
106
|
+
if (embeddingHealth?.lastQueryMode && !evalAlreadyHealthy) {
|
|
107
|
+
effective.embeddingQueryMode = embeddingHealth.lastQueryMode;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (embeddings?.healthy && !embeddings.degraded) {
|
|
111
|
+
effective.embeddingError = null;
|
|
112
|
+
effective.failedEmbeddingCount = 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return effective;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildLaneReadiness({
|
|
119
|
+
jobId,
|
|
120
|
+
job = null,
|
|
121
|
+
evaluation = null,
|
|
122
|
+
embeddingHealth = null,
|
|
123
|
+
reportFallback = null,
|
|
124
|
+
maintenance = null,
|
|
125
|
+
dependencyHealth = null
|
|
126
|
+
} = {}) {
|
|
127
|
+
const interaction = evaluation?.interaction || null;
|
|
128
|
+
const retrieval = evaluation?.retrieval || null;
|
|
129
|
+
const contractCheck = evaluation?.contractCheck || null;
|
|
130
|
+
const rubric = evaluation?.rubric || null;
|
|
131
|
+
const embeddingSummary = summarizeEmbeddings(embeddingHealth);
|
|
132
|
+
const effectiveRetrieval = buildEffectiveRetrieval(retrieval, embeddingSummary);
|
|
133
|
+
const embeddings = reconcileEmbeddingsWithRetrieval(embeddingSummary, effectiveRetrieval);
|
|
134
|
+
const fallback = summarizeFallback(reportFallback);
|
|
135
|
+
|
|
136
|
+
const checks = {
|
|
137
|
+
contractHealthy:
|
|
138
|
+
contractCheck == null
|
|
139
|
+
? true
|
|
140
|
+
: Number(contractCheck.satisfiedRatio ?? 0) >= 0.99 && (contractCheck.missingFromV2 || []).length === 0,
|
|
141
|
+
interactionHealthy: !hasBlockingInteractionIssues(interaction),
|
|
142
|
+
retrievalHealthy:
|
|
143
|
+
!effectiveRetrieval ||
|
|
144
|
+
(!effectiveRetrieval.embeddingError &&
|
|
145
|
+
effectiveRetrieval.embeddingQueryMode !== "lexical_fallback" &&
|
|
146
|
+
Number(effectiveRetrieval.failedEmbeddingCount || 0) === 0),
|
|
147
|
+
daemonHealthy:
|
|
148
|
+
!maintenance ||
|
|
149
|
+
((maintenance.handoffs?.expiredCount || 0) === 0 && (maintenance.followUps?.expiredCount || 0) === 0),
|
|
150
|
+
providerReachable:
|
|
151
|
+
!dependencyHealth || dependencyHealth.provider?.reachable !== false,
|
|
152
|
+
deliveryReachable:
|
|
153
|
+
!dependencyHealth || dependencyHealth.delivery?.reachable !== false,
|
|
154
|
+
fallbackReady: fallback.ready
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const blockers = [];
|
|
158
|
+
if (!checks.contractHealthy) blockers.push("output_contract_not_consistently_satisfied");
|
|
159
|
+
if (!checks.interactionHealthy) blockers.push("interaction_lifecycle_has_blocking_issues");
|
|
160
|
+
if (!checks.retrievalHealthy) blockers.push("retrieval_is_degraded_or_falling_back");
|
|
161
|
+
if (!checks.daemonHealthy) blockers.push("daemon_maintenance_detected_expired_items");
|
|
162
|
+
if (!checks.providerReachable) blockers.push("provider_unreachable");
|
|
163
|
+
if (embeddings.degraded) blockers.push(...embeddings.blockedReasons.map((item) => `embeddings:${item}`));
|
|
164
|
+
|
|
165
|
+
const warnings = [];
|
|
166
|
+
if (!checks.deliveryReachable) warnings.push("delivery_not_reachable");
|
|
167
|
+
|
|
168
|
+
const suggestions = [];
|
|
169
|
+
if (!checks.retrievalHealthy) {
|
|
170
|
+
suggestions.push("Repair embeddings or accept lexical-only mode before treating this lane as daily-driver ready.");
|
|
171
|
+
}
|
|
172
|
+
if (!checks.contractHealthy) {
|
|
173
|
+
suggestions.push("Keep contract adherence at 100% before cutover.");
|
|
174
|
+
}
|
|
175
|
+
if (!checks.interactionHealthy) {
|
|
176
|
+
suggestions.push("Resolve follow-up / handoff lifecycle issues before using this lane unattended.");
|
|
177
|
+
}
|
|
178
|
+
if (!checks.providerReachable) {
|
|
179
|
+
suggestions.push("Check provider health before treating this lane as trustworthy.");
|
|
180
|
+
}
|
|
181
|
+
if (!checks.deliveryReachable) {
|
|
182
|
+
suggestions.push("Delivery transport is not reachable. This does not block pilot readiness but should be resolved before daily-driver cutover.");
|
|
183
|
+
}
|
|
184
|
+
if (!checks.fallbackReady && fallback.configured) {
|
|
185
|
+
suggestions.push("Remote fallback is optional for this lane. Enable and validate it only if you want recovery from local failures.");
|
|
186
|
+
}
|
|
187
|
+
if (checks.contractHealthy && checks.interactionHealthy && checks.retrievalHealthy) {
|
|
188
|
+
suggestions.push("This lane is technically close to cutover; focus next on repeat-run validation and operator trust.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let tier = "not_ready";
|
|
192
|
+
if (blockers.length === 0 && Number(rubric?.overallScore || 0) >= 0.95) {
|
|
193
|
+
tier = "ready_for_pilot";
|
|
194
|
+
} else if (blockers.length <= 2 && Number(rubric?.overallScore || 0) >= 0.85) {
|
|
195
|
+
tier = "close";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
jobId,
|
|
200
|
+
taskType: job?.taskType || null,
|
|
201
|
+
primaryLane: job?.modelLane || null,
|
|
202
|
+
readinessTier: tier,
|
|
203
|
+
readyForPilot: tier === "ready_for_pilot",
|
|
204
|
+
checks,
|
|
205
|
+
blockers: [...new Set(blockers)],
|
|
206
|
+
warnings,
|
|
207
|
+
suggestions,
|
|
208
|
+
summary: {
|
|
209
|
+
overallScore: rubric?.overallScore ?? null,
|
|
210
|
+
contractSatisfiedRatio: contractCheck?.satisfiedRatio ?? null,
|
|
211
|
+
yielded: truthy(interaction?.yielded),
|
|
212
|
+
followUpConsumed: truthy(interaction?.followUpConsumed),
|
|
213
|
+
handoffDelivered: truthy(interaction?.handoffDelivered),
|
|
214
|
+
embeddingQueryMode: effectiveRetrieval?.embeddingQueryMode || null,
|
|
215
|
+
semanticCount: effectiveRetrieval?.semanticCount ?? 0,
|
|
216
|
+
fallbackConfigured: fallback.configured,
|
|
217
|
+
fallbackReady: fallback.ready
|
|
218
|
+
},
|
|
219
|
+
interaction,
|
|
220
|
+
retrieval: effectiveRetrieval,
|
|
221
|
+
embeddings,
|
|
222
|
+
fallback,
|
|
223
|
+
maintenance,
|
|
224
|
+
dependencyHealth
|
|
225
|
+
};
|
|
226
|
+
}
|