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,330 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ConfigLoader } from "../config/loader.js";
|
|
3
|
+
import { MemoryStore } from "../memory/memory-store.js";
|
|
4
|
+
import { NotificationStore } from "./notification-store.js";
|
|
5
|
+
import { DeliveryStore } from "./delivery-store.js";
|
|
6
|
+
import { PeerRegistry } from "./peer-registry.js";
|
|
7
|
+
import { SchedulerStateStore } from "./scheduler-state.js";
|
|
8
|
+
import { computeNextRun } from "./schedule.js";
|
|
9
|
+
import { ProviderRegistry } from "../providers/registry.js";
|
|
10
|
+
|
|
11
|
+
function _safeCount(arr) {
|
|
12
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function _safeSlice(arr, n) {
|
|
16
|
+
return Array.isArray(arr) ? arr.slice(0, n) : [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveEmbeddingsStatus(enabled) {
|
|
20
|
+
if (enabled === false) return "disabled";
|
|
21
|
+
if (enabled === true) return "enabled";
|
|
22
|
+
return "unknown";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function shouldHideJobFromUserStatus(job) {
|
|
26
|
+
return job?.id === "memory-rollup" && job?.lastStatus === "error";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function collectAgents(runtime, memoryStore) {
|
|
30
|
+
const agents = [];
|
|
31
|
+
for (const [agentId, agent] of Object.entries(runtime.agents || {})) {
|
|
32
|
+
let memoryHealth;
|
|
33
|
+
try {
|
|
34
|
+
const paths = await memoryStore.initAgent(agentId);
|
|
35
|
+
const sqlite = await memoryStore.ensureSqliteStore(paths);
|
|
36
|
+
memoryHealth = {
|
|
37
|
+
totalEntries: sqlite.count(),
|
|
38
|
+
embeddingHealth: sqlite.getEmbeddingHealth()
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
memoryHealth = { totalEntries: 0, embeddingHealth: null, error: "unable_to_read" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
agents.push({
|
|
45
|
+
id: agentId,
|
|
46
|
+
primaryLane: agent.primaryLane || null,
|
|
47
|
+
fallbackLane: agent.fallbackLane || null,
|
|
48
|
+
memoryPolicy: agent.memoryPolicy || null,
|
|
49
|
+
toolPolicy: agent.toolPolicy || null,
|
|
50
|
+
deliveryProfile: agent.delivery?.profile || null,
|
|
51
|
+
memoryHealth
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return agents;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function collectJobs(runtime, schedulerState) {
|
|
58
|
+
const jobs = [];
|
|
59
|
+
const jobStates = schedulerState?.jobs || {};
|
|
60
|
+
for (const [jobId, jobConfig] of Object.entries(runtime.jobs || {})) {
|
|
61
|
+
const state = jobStates[jobId] || {};
|
|
62
|
+
let nextDue = null;
|
|
63
|
+
try {
|
|
64
|
+
if (jobConfig.trigger) {
|
|
65
|
+
nextDue = computeNextRun(jobConfig.trigger, new Date()).toISOString();
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// trigger may not be parseable without context
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
jobs.push({
|
|
72
|
+
id: jobId,
|
|
73
|
+
trigger: jobConfig.trigger || null,
|
|
74
|
+
taskType: jobConfig.taskType || null,
|
|
75
|
+
agentId: jobConfig.agentId || null,
|
|
76
|
+
lastRunAt: state.lastRunAt || null,
|
|
77
|
+
lastStatus: state.lastStatus || null,
|
|
78
|
+
nextDue
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return jobs;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function collectProviders(runtime, providerRegistry) {
|
|
85
|
+
const providers = [];
|
|
86
|
+
for (const [providerId, providerConfig] of Object.entries(runtime.providers || {})) {
|
|
87
|
+
let capabilities;
|
|
88
|
+
try {
|
|
89
|
+
const desc = providerRegistry.describe(providerConfig);
|
|
90
|
+
capabilities = desc.capabilities || null;
|
|
91
|
+
} catch {
|
|
92
|
+
capabilities = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
providers.push({
|
|
96
|
+
id: providerId,
|
|
97
|
+
adapter: providerConfig.adapter || providerId,
|
|
98
|
+
baseUrl: providerConfig.baseUrl || null,
|
|
99
|
+
capabilities
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return providers;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function collectDelivery(deliveryStore, runtime) {
|
|
106
|
+
const deliveryConfig = runtime.delivery || {};
|
|
107
|
+
const profiles = Object.keys(deliveryConfig.profiles || {});
|
|
108
|
+
let recentFailureCount = 0;
|
|
109
|
+
let totalRecords = 0;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const index = await deliveryStore.loadIndex();
|
|
113
|
+
const records = Object.values(index.delivered || {});
|
|
114
|
+
totalRecords = records.length;
|
|
115
|
+
recentFailureCount = records.filter(
|
|
116
|
+
(record) => record.status === "failed" || record.status === "uncertain"
|
|
117
|
+
).length;
|
|
118
|
+
} catch {
|
|
119
|
+
// delivery store may not exist yet
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
profiles,
|
|
124
|
+
defaultInteractiveProfile: deliveryConfig.defaultInteractiveProfile || null,
|
|
125
|
+
defaultSchedulerProfile: deliveryConfig.defaultSchedulerProfile || null,
|
|
126
|
+
standaloneMode: Boolean(
|
|
127
|
+
process.env.NEMORIS_STANDALONE === "1" ||
|
|
128
|
+
process.env.NEMORIS_STANDALONE === "true" ||
|
|
129
|
+
deliveryConfig.standaloneMode
|
|
130
|
+
),
|
|
131
|
+
totalRecords,
|
|
132
|
+
recentFailureCount
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function collectPeers(runtime) {
|
|
137
|
+
const peerRegistry = new PeerRegistry(runtime.peers || {});
|
|
138
|
+
const peers = peerRegistry.list();
|
|
139
|
+
return peers.map((peer) => ({
|
|
140
|
+
peerId: peer.peerId,
|
|
141
|
+
label: peer.label || peer.peerId,
|
|
142
|
+
agentId: peer.agentId || null,
|
|
143
|
+
deliveryProfile: peer.deliveryProfile || null,
|
|
144
|
+
sessionKeys: peer.sessionKeys || [],
|
|
145
|
+
hasCard: Boolean(peer.card)
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function collectRuntime(runtime, notifications) {
|
|
150
|
+
const retention = runtime.runtime?.retention || {};
|
|
151
|
+
const concurrency = runtime.runtime?.concurrency || {};
|
|
152
|
+
const retrieval = runtime.runtime?.retrieval || {};
|
|
153
|
+
|
|
154
|
+
const followUps = (notifications || []).filter((n) => n.stage === "follow_up");
|
|
155
|
+
const pendingFollowUps = followUps.filter((n) => n.status === "pending");
|
|
156
|
+
const handoffs = (notifications || []).filter((n) => n.stage === "handoff");
|
|
157
|
+
const pendingHandoffs = handoffs.filter(
|
|
158
|
+
(n) => n.status === "awaiting_choice" || n.handoffState === "pending"
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
concurrency: {
|
|
163
|
+
maxJobsPerTick: concurrency.maxJobsPerTick ?? concurrency.maxConcurrentJobs ?? 2
|
|
164
|
+
},
|
|
165
|
+
embeddings: {
|
|
166
|
+
enabled: runtime.embeddings?.enabled ?? null
|
|
167
|
+
},
|
|
168
|
+
retention: {
|
|
169
|
+
runs: retention.runs || {},
|
|
170
|
+
notifications: retention.notifications || {},
|
|
171
|
+
deliveries: retention.deliveries || {}
|
|
172
|
+
},
|
|
173
|
+
retrieval: {
|
|
174
|
+
lexicalWeight: retrieval.lexicalWeight ?? null,
|
|
175
|
+
embeddingWeight: retrieval.embeddingWeight ?? null,
|
|
176
|
+
recencyWeight: retrieval.recencyWeight ?? null
|
|
177
|
+
},
|
|
178
|
+
followUpStats: {
|
|
179
|
+
total: followUps.length,
|
|
180
|
+
pending: pendingFollowUps.length
|
|
181
|
+
},
|
|
182
|
+
handoffStats: {
|
|
183
|
+
total: handoffs.length,
|
|
184
|
+
pending: pendingHandoffs.length
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function buildRuntimeStatus(options = {}) {
|
|
190
|
+
const {
|
|
191
|
+
projectRoot,
|
|
192
|
+
stateRoot,
|
|
193
|
+
liveRoot: _liveRoot,
|
|
194
|
+
// Allow injecting dependencies for testing
|
|
195
|
+
configLoader,
|
|
196
|
+
memoryStore,
|
|
197
|
+
notificationStore,
|
|
198
|
+
deliveryStore,
|
|
199
|
+
schedulerStateStore,
|
|
200
|
+
providerRegistry
|
|
201
|
+
} = options;
|
|
202
|
+
|
|
203
|
+
const loader = configLoader || new ConfigLoader({ rootDir: path.join(projectRoot, "config") });
|
|
204
|
+
const memory = memoryStore || new MemoryStore({ rootDir: path.join(stateRoot, "memory") });
|
|
205
|
+
const notifications = notificationStore || new NotificationStore({ rootDir: path.join(stateRoot, "notifications") });
|
|
206
|
+
const deliveries = deliveryStore || new DeliveryStore({ rootDir: path.join(stateRoot, "deliveries") });
|
|
207
|
+
const stateStore = schedulerStateStore || new SchedulerStateStore({ rootDir: path.join(stateRoot, "scheduler") });
|
|
208
|
+
const registry = providerRegistry || new ProviderRegistry();
|
|
209
|
+
|
|
210
|
+
let runtime;
|
|
211
|
+
let configErrors = [];
|
|
212
|
+
try {
|
|
213
|
+
runtime = await loader.loadAll();
|
|
214
|
+
} catch (err) {
|
|
215
|
+
// Hard validation error or config parse failure — return degraded status
|
|
216
|
+
configErrors = (err.validationErrors?.map((e) => e.details || e.code) || [err.message || "Unknown config error"]).filter(Boolean);
|
|
217
|
+
try {
|
|
218
|
+
runtime = await loader.loadAll({ skipValidation: true });
|
|
219
|
+
} catch {
|
|
220
|
+
return {
|
|
221
|
+
status: "degraded",
|
|
222
|
+
configErrors,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const [schedulerState, allNotifications, agents] = await Promise.all([
|
|
228
|
+
stateStore.load().catch(() => ({ jobs: {} })),
|
|
229
|
+
notifications.listAll().catch(() => []),
|
|
230
|
+
collectAgents(runtime, memory)
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
const [deliveryStatus, jobs, providers, peers, runtimeStats] = await Promise.all([
|
|
234
|
+
collectDelivery(deliveries, runtime),
|
|
235
|
+
Promise.resolve(collectJobs(runtime, schedulerState)),
|
|
236
|
+
Promise.resolve(collectProviders(runtime, registry)),
|
|
237
|
+
Promise.resolve(collectPeers(runtime)),
|
|
238
|
+
Promise.resolve(collectRuntime(runtime, allNotifications))
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
timestamp: new Date().toISOString(),
|
|
243
|
+
agents,
|
|
244
|
+
jobs,
|
|
245
|
+
providers,
|
|
246
|
+
delivery: deliveryStatus,
|
|
247
|
+
peers,
|
|
248
|
+
runtime: runtimeStats
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function formatRuntimeStatus(status) {
|
|
253
|
+
// Early exit for degraded status — avoid shape mismatches with partial config
|
|
254
|
+
if (status.status === "degraded") {
|
|
255
|
+
const lines = [
|
|
256
|
+
"=== Nemoris V2 Runtime Status ===",
|
|
257
|
+
`Timestamp: ${status.timestamp || new Date().toISOString()}`,
|
|
258
|
+
"",
|
|
259
|
+
"⚠ Status: degraded — run `nemoris doctor` for details",
|
|
260
|
+
];
|
|
261
|
+
for (const err of (status.configErrors || [])) {
|
|
262
|
+
lines.push(` • ${err}`);
|
|
263
|
+
}
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const lines = [];
|
|
268
|
+
const embeddingsStatus = resolveEmbeddingsStatus(status.runtime?.embeddings?.enabled);
|
|
269
|
+
const visibleJobs = (status.jobs || []).filter((job) => !shouldHideJobFromUserStatus(job));
|
|
270
|
+
|
|
271
|
+
lines.push("=== Nemoris V2 Runtime Status ===");
|
|
272
|
+
lines.push(`Timestamp: ${status.timestamp}`);
|
|
273
|
+
if (status.configErrors?.length) {
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push(`⚠ Config validation: ${status.configErrors.length} issue(s)`);
|
|
276
|
+
for (const err of status.configErrors) {
|
|
277
|
+
lines.push(` • ${err}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
lines.push("");
|
|
281
|
+
|
|
282
|
+
// Agents
|
|
283
|
+
lines.push(`--- Agents (${status.agents.length}) ---`);
|
|
284
|
+
for (const agent of status.agents) {
|
|
285
|
+
const memEntries = agent.memoryHealth?.totalEntries ?? "?";
|
|
286
|
+
lines.push(` ${agent.id}`);
|
|
287
|
+
lines.push(` lane: ${agent.primaryLane || "(none)"} memory entries: ${memEntries} embeddings: ${embeddingsStatus}`);
|
|
288
|
+
}
|
|
289
|
+
lines.push("");
|
|
290
|
+
|
|
291
|
+
// Jobs
|
|
292
|
+
lines.push(`--- Jobs (${visibleJobs.length}) ---`);
|
|
293
|
+
for (const job of visibleJobs) {
|
|
294
|
+
const lastRun = job.lastRunAt ? `last: ${job.lastRunAt}` : "last: never";
|
|
295
|
+
const lastStatus = job.lastStatus ? `status: ${job.lastStatus}` : "status: (none)";
|
|
296
|
+
const next = job.nextDue ? `next: ${job.nextDue}` : "next: (unknown)";
|
|
297
|
+
lines.push(` ${job.id} [${job.trigger || "?"}]`);
|
|
298
|
+
lines.push(` ${lastRun} ${lastStatus} ${next}`);
|
|
299
|
+
}
|
|
300
|
+
lines.push("");
|
|
301
|
+
|
|
302
|
+
// Providers
|
|
303
|
+
lines.push(`--- Providers (${status.providers.length}) ---`);
|
|
304
|
+
for (const provider of status.providers) {
|
|
305
|
+
lines.push(` ${provider.id} (${provider.adapter}) ${provider.baseUrl || "(no url)"}`);
|
|
306
|
+
}
|
|
307
|
+
lines.push("");
|
|
308
|
+
|
|
309
|
+
// Delivery
|
|
310
|
+
lines.push("--- Delivery ---");
|
|
311
|
+
lines.push(` profiles: ${status.delivery.profiles.join(", ") || "(none)"}`);
|
|
312
|
+
lines.push(` standalone: ${status.delivery.standaloneMode}`);
|
|
313
|
+
lines.push(` records: ${status.delivery.totalRecords} failures: ${status.delivery.recentFailureCount}`);
|
|
314
|
+
lines.push("");
|
|
315
|
+
|
|
316
|
+
// Peers
|
|
317
|
+
lines.push(`--- Peers (${status.peers.length}) ---`);
|
|
318
|
+
for (const peer of status.peers) {
|
|
319
|
+
lines.push(` ${peer.peerId} label: ${peer.label} card: ${peer.hasCard ? "yes" : "no"}`);
|
|
320
|
+
}
|
|
321
|
+
lines.push("");
|
|
322
|
+
|
|
323
|
+
// Runtime
|
|
324
|
+
lines.push("--- Runtime ---");
|
|
325
|
+
lines.push(` max jobs/tick: ${status.runtime.concurrency.maxJobsPerTick}`);
|
|
326
|
+
lines.push(` follow-ups: ${status.runtime.followUpStats.total} total, ${status.runtime.followUpStats.pending} pending`);
|
|
327
|
+
lines.push(` handoffs: ${status.runtime.handoffStats.total} total, ${status.runtime.handoffStats.pending} pending`);
|
|
328
|
+
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ulid } from "./ulid.js";
|
|
2
|
+
|
|
3
|
+
const VALID_TRANSITIONS = {
|
|
4
|
+
created: ["accepted", "rejected"],
|
|
5
|
+
accepted: ["running", "rejected"],
|
|
6
|
+
running: ["completed", "failed", "cancelled", "timed_out"],
|
|
7
|
+
timed_out: ["escalated"],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class TaskContract {
|
|
11
|
+
constructor(db, envelopeStore) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.envelopes = envelopeStore;
|
|
14
|
+
this._ensureSchema();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_ensureSchema() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
20
|
+
task_id TEXT PRIMARY KEY,
|
|
21
|
+
envelope_id TEXT NOT NULL,
|
|
22
|
+
owner_agent TEXT NOT NULL,
|
|
23
|
+
assigned_agent TEXT NOT NULL,
|
|
24
|
+
status TEXT NOT NULL DEFAULT 'created',
|
|
25
|
+
objective TEXT NOT NULL,
|
|
26
|
+
deadline_at TEXT NOT NULL,
|
|
27
|
+
result_slot TEXT,
|
|
28
|
+
error_slot TEXT,
|
|
29
|
+
escalation_chain TEXT NOT NULL,
|
|
30
|
+
escalation_index INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
created_at TEXT NOT NULL,
|
|
32
|
+
updated_at TEXT NOT NULL,
|
|
33
|
+
FOREIGN KEY (envelope_id) REFERENCES envelopes(id)
|
|
34
|
+
);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_agent);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_deadline ON tasks(deadline_at);
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
createTask({ ownerAgent, assignedAgent, objective, deadlineMinutes, escalationChain = ["assigned", "owner", "operator"] }) {
|
|
42
|
+
const taskId = ulid();
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const deadlineAt = new Date(now.getTime() + deadlineMinutes * 60 * 1000).toISOString();
|
|
45
|
+
const envelope = this.envelopes.create({
|
|
46
|
+
sourceAgent: ownerAgent,
|
|
47
|
+
criticality: "result",
|
|
48
|
+
payloadType: "task",
|
|
49
|
+
payload: { objective, assignedAgent, deadlineMinutes },
|
|
50
|
+
});
|
|
51
|
+
this.db.prepare(`
|
|
52
|
+
INSERT INTO tasks (task_id, envelope_id, owner_agent, assigned_agent, status, objective, deadline_at, escalation_chain, escalation_index, created_at, updated_at)
|
|
53
|
+
VALUES (?, ?, ?, ?, 'created', ?, ?, ?, 0, ?, ?)
|
|
54
|
+
`).run(taskId, envelope.id, ownerAgent, assignedAgent, objective.slice(0, 500), deadlineAt, JSON.stringify(escalationChain), now.toISOString(), now.toISOString());
|
|
55
|
+
return this.get(taskId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get(taskId) {
|
|
59
|
+
return this.db.prepare("SELECT * FROM tasks WHERE task_id = ?").get(taskId) || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
transition(taskId, newStatus, callerAgent, { result, error } = {}) {
|
|
63
|
+
const task = this.get(taskId);
|
|
64
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
65
|
+
const allowed = VALID_TRANSITIONS[task.status];
|
|
66
|
+
if (!allowed || !allowed.includes(newStatus)) {
|
|
67
|
+
throw new Error(`Invalid transition: ${task.status} → ${newStatus}`);
|
|
68
|
+
}
|
|
69
|
+
// Permission checks
|
|
70
|
+
if (newStatus === "cancelled" && callerAgent !== task.owner_agent) {
|
|
71
|
+
throw new Error("Only owner can cancel a task");
|
|
72
|
+
}
|
|
73
|
+
const assignedOnly = ["accepted", "running", "completed", "failed", "rejected"];
|
|
74
|
+
if (assignedOnly.includes(newStatus) && callerAgent !== task.assigned_agent) {
|
|
75
|
+
throw new Error(`Only assigned agent can transition to ${newStatus}`);
|
|
76
|
+
}
|
|
77
|
+
const now = new Date().toISOString();
|
|
78
|
+
const updates = { status: newStatus, updated_at: now };
|
|
79
|
+
if (result !== undefined) {
|
|
80
|
+
updates.result_slot = typeof result === "string" ? result : JSON.stringify(result);
|
|
81
|
+
if (updates.result_slot.length > 4000) {
|
|
82
|
+
updates.result_slot = JSON.stringify({
|
|
83
|
+
ledger_ref: `overflow-${taskId}`,
|
|
84
|
+
summary: updates.result_slot.slice(0, 200),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (error !== undefined) {
|
|
89
|
+
updates.error_slot = typeof error === "string" ? error : JSON.stringify(error);
|
|
90
|
+
}
|
|
91
|
+
const setClauses = Object.keys(updates).map((k) => `${k} = ?`).join(", ");
|
|
92
|
+
const values = [...Object.values(updates), taskId];
|
|
93
|
+
this.db.prepare(`UPDATE tasks SET ${setClauses} WHERE task_id = ?`).run(...values);
|
|
94
|
+
return this.get(taskId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
listByStatus(status) {
|
|
98
|
+
return this.db.prepare("SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC").all(status);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
listTimedOut() {
|
|
102
|
+
const now = new Date().toISOString();
|
|
103
|
+
return this.db.prepare("SELECT * FROM tasks WHERE status = 'running' AND deadline_at < ?").all(now);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sweepTimedOutTasks() {
|
|
107
|
+
const now = new Date().toISOString();
|
|
108
|
+
const overdue = this.db.prepare("SELECT * FROM tasks WHERE status = 'running' AND deadline_at < ?").all(now);
|
|
109
|
+
for (const task of overdue) {
|
|
110
|
+
this.db.prepare("UPDATE tasks SET status = 'timed_out', updated_at = ? WHERE task_id = ?").run(now, task.task_id);
|
|
111
|
+
}
|
|
112
|
+
return overdue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
advanceEscalation(taskId) {
|
|
116
|
+
const task = this.get(taskId);
|
|
117
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
118
|
+
if (!["timed_out", "escalated"].includes(task.status)) {
|
|
119
|
+
throw new Error(`Cannot escalate task in status: ${task.status}`);
|
|
120
|
+
}
|
|
121
|
+
const chain = JSON.parse(task.escalation_chain);
|
|
122
|
+
const newIndex = task.escalation_index + 1;
|
|
123
|
+
this.db.prepare("UPDATE tasks SET escalation_index = ?, updated_at = ? WHERE task_id = ?")
|
|
124
|
+
.run(newIndex, new Date().toISOString(), taskId);
|
|
125
|
+
if (newIndex >= chain.length) {
|
|
126
|
+
this.db.prepare("UPDATE tasks SET status = 'escalated', updated_at = ? WHERE task_id = ?")
|
|
127
|
+
.run(new Date().toISOString(), taskId);
|
|
128
|
+
}
|
|
129
|
+
return { escalationIndex: newIndex, target: chain[newIndex] || null, exhausted: newIndex >= chain.length };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getInbox() {
|
|
133
|
+
return this.db.prepare(`
|
|
134
|
+
SELECT * FROM tasks
|
|
135
|
+
WHERE status IN ('timed_out', 'escalated')
|
|
136
|
+
AND escalation_index >= json_array_length(escalation_chain)
|
|
137
|
+
ORDER BY created_at DESC
|
|
138
|
+
`).all();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { activeRecall } from "../memory/active-recall.js";
|
|
2
|
+
|
|
3
|
+
function compactCheckpoint(checkpoint) {
|
|
4
|
+
if (!checkpoint) return null;
|
|
5
|
+
return {
|
|
6
|
+
objective: checkpoint.objective,
|
|
7
|
+
status: checkpoint.status,
|
|
8
|
+
nextActions: checkpoint.nextActions || [],
|
|
9
|
+
blockers: checkpoint.blockers || []
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function buildTaskPacket({
|
|
14
|
+
agentId,
|
|
15
|
+
objective,
|
|
16
|
+
memoryStore,
|
|
17
|
+
backendManager = null,
|
|
18
|
+
workspaceRoot = null,
|
|
19
|
+
backendOrder = null,
|
|
20
|
+
qmdSupplementLimit = 2,
|
|
21
|
+
retrievalQueries = null,
|
|
22
|
+
identity = null,
|
|
23
|
+
interactionContract = null,
|
|
24
|
+
outputContract = null,
|
|
25
|
+
reportGuidance = null,
|
|
26
|
+
allowedTools = [],
|
|
27
|
+
artifactRefs = [],
|
|
28
|
+
budget = {},
|
|
29
|
+
checkpointId = "latest",
|
|
30
|
+
memoryLimit = 8,
|
|
31
|
+
retrievalBlend = null,
|
|
32
|
+
conversationContext = undefined
|
|
33
|
+
}) {
|
|
34
|
+
const checkpoint = compactCheckpoint(await memoryStore.loadCheckpoint(agentId, checkpointId));
|
|
35
|
+
let memory;
|
|
36
|
+
const useActiveRecall = memoryStore?.queryRetrievalCandidates;
|
|
37
|
+
if (useActiveRecall) {
|
|
38
|
+
const tokenBudget = budget.maxTokens ? Math.min(2000, Math.floor(budget.maxTokens * 0.08)) : 2000;
|
|
39
|
+
const recallResult = await activeRecall({
|
|
40
|
+
inboundMessage: objective,
|
|
41
|
+
memoryStore,
|
|
42
|
+
tokenBudget,
|
|
43
|
+
topicRelevanceFn: null,
|
|
44
|
+
});
|
|
45
|
+
const items = [
|
|
46
|
+
...recallResult.facts.map((c) => ({ ...c, tier: "fact" })),
|
|
47
|
+
...recallResult.asides.map((c) => ({ ...c, tier: "aside" })),
|
|
48
|
+
];
|
|
49
|
+
memory = { items, backends: null, retrieval: null };
|
|
50
|
+
} else {
|
|
51
|
+
memory = backendManager
|
|
52
|
+
? await backendManager.queryCombined(agentId, objective, {
|
|
53
|
+
workspaceRoot,
|
|
54
|
+
backendOrder,
|
|
55
|
+
qmdSupplementLimit,
|
|
56
|
+
fileQuery: retrievalQueries?.fileQuery || objective,
|
|
57
|
+
qmdQuery: retrievalQueries?.qmdQuery || objective,
|
|
58
|
+
limit: memoryLimit,
|
|
59
|
+
retrievalBlend
|
|
60
|
+
})
|
|
61
|
+
: await memoryStore.query(agentId, objective, { limit: memoryLimit, retrievalBlend });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
agentId,
|
|
66
|
+
objective,
|
|
67
|
+
conversationContext: conversationContext || null,
|
|
68
|
+
budget: {
|
|
69
|
+
maxTokens: budget.maxTokens ?? 8000,
|
|
70
|
+
maxRuntimeSeconds: budget.maxRuntimeSeconds ?? 60
|
|
71
|
+
},
|
|
72
|
+
layers: {
|
|
73
|
+
systemRules: [
|
|
74
|
+
"Transcript is not the system of record.",
|
|
75
|
+
"Use checkpoint and memory before requesting more history.",
|
|
76
|
+
"Do not write durable memory without policy approval."
|
|
77
|
+
],
|
|
78
|
+
identity,
|
|
79
|
+
interactionContract,
|
|
80
|
+
outputContract,
|
|
81
|
+
reportGuidance,
|
|
82
|
+
checkpoint,
|
|
83
|
+
retrieval: memory.backends || null,
|
|
84
|
+
retrievalMeta: memory.retrieval || null,
|
|
85
|
+
retrievalQueries,
|
|
86
|
+
memory: memory.items.map((item) => ({
|
|
87
|
+
entryId: item.entryId || null,
|
|
88
|
+
type: item.type || "memory",
|
|
89
|
+
category: item.category || "general",
|
|
90
|
+
title: item.title || "",
|
|
91
|
+
content: item.content || "",
|
|
92
|
+
summary: item.summary || "",
|
|
93
|
+
reason: item.reason || "",
|
|
94
|
+
score: Number((item.score ?? 0).toFixed(4)),
|
|
95
|
+
tier: item.tier || null,
|
|
96
|
+
sourceBackend: item.sourceBackend || "file",
|
|
97
|
+
candidateSource: item.candidateSource || "indexed",
|
|
98
|
+
lexicalScore: Number((item.lexicalScore ?? 0).toFixed(4)),
|
|
99
|
+
embeddingSimilarity: Number((item.embeddingSimilarity ?? 0).toFixed(4)),
|
|
100
|
+
embeddingFreshness: item.embeddingFreshness || "missing",
|
|
101
|
+
retrievalSources: item.retrievalSources || ["lexical"]
|
|
102
|
+
})),
|
|
103
|
+
tools: allowedTools,
|
|
104
|
+
artifacts: artifactRefs
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|