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.
Files changed (223) hide show
  1. package/.env.example +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +209 -0
  4. package/SECURITY.md +119 -0
  5. package/bin/nemoris +46 -0
  6. package/config/agents/agent.toml.example +28 -0
  7. package/config/agents/default.toml +22 -0
  8. package/config/agents/orchestrator.toml +18 -0
  9. package/config/delivery.toml +73 -0
  10. package/config/embeddings.toml +5 -0
  11. package/config/identity/default-purpose.md +1 -0
  12. package/config/identity/default-soul.md +3 -0
  13. package/config/identity/orchestrator-purpose.md +1 -0
  14. package/config/identity/orchestrator-soul.md +1 -0
  15. package/config/improvement-targets.toml +15 -0
  16. package/config/jobs/heartbeat-check.toml +30 -0
  17. package/config/jobs/memory-rollup.toml +46 -0
  18. package/config/jobs/workspace-health.toml +63 -0
  19. package/config/mcp.toml +16 -0
  20. package/config/output-contracts.toml +17 -0
  21. package/config/peers.toml +32 -0
  22. package/config/peers.toml.example +32 -0
  23. package/config/policies/memory-default.toml +10 -0
  24. package/config/policies/memory-heartbeat.toml +5 -0
  25. package/config/policies/memory-ops.toml +10 -0
  26. package/config/policies/tools-heartbeat-minimal.toml +8 -0
  27. package/config/policies/tools-interactive-safe.toml +8 -0
  28. package/config/policies/tools-ops-bounded.toml +8 -0
  29. package/config/policies/tools-orchestrator.toml +7 -0
  30. package/config/providers/anthropic.toml +15 -0
  31. package/config/providers/ollama.toml +5 -0
  32. package/config/providers/openai-codex.toml +9 -0
  33. package/config/providers/openrouter.toml +5 -0
  34. package/config/router.toml +22 -0
  35. package/config/runtime.toml +114 -0
  36. package/config/skills/self-improvement.toml +15 -0
  37. package/config/skills/telegram-onboarding-spec.md +240 -0
  38. package/config/skills/workspace-monitor.toml +15 -0
  39. package/config/task-router.toml +42 -0
  40. package/install.sh +50 -0
  41. package/package.json +90 -0
  42. package/src/auth/auth-profiles.js +169 -0
  43. package/src/auth/openai-codex-oauth.js +285 -0
  44. package/src/battle.js +449 -0
  45. package/src/cli/help.js +265 -0
  46. package/src/cli/output-filter.js +49 -0
  47. package/src/cli/runtime-control.js +704 -0
  48. package/src/cli-main.js +2763 -0
  49. package/src/cli.js +78 -0
  50. package/src/config/loader.js +332 -0
  51. package/src/config/schema-validator.js +214 -0
  52. package/src/config/toml-lite.js +8 -0
  53. package/src/daemon/action-handlers.js +71 -0
  54. package/src/daemon/healing-tick.js +87 -0
  55. package/src/daemon/health-probes.js +90 -0
  56. package/src/daemon/notifier.js +57 -0
  57. package/src/daemon/nurse.js +218 -0
  58. package/src/daemon/repair-log.js +106 -0
  59. package/src/daemon/rule-staging.js +90 -0
  60. package/src/daemon/rules.js +29 -0
  61. package/src/daemon/telegram-commands.js +54 -0
  62. package/src/daemon/updater.js +85 -0
  63. package/src/jobs/job-runner.js +78 -0
  64. package/src/mcp/consumer.js +129 -0
  65. package/src/memory/active-recall.js +171 -0
  66. package/src/memory/backend-manager.js +97 -0
  67. package/src/memory/backends/file-backend.js +38 -0
  68. package/src/memory/backends/qmd-backend.js +219 -0
  69. package/src/memory/embedding-guards.js +24 -0
  70. package/src/memory/embedding-index.js +118 -0
  71. package/src/memory/embedding-service.js +179 -0
  72. package/src/memory/file-index.js +177 -0
  73. package/src/memory/memory-signature.js +5 -0
  74. package/src/memory/memory-store.js +648 -0
  75. package/src/memory/retrieval-planner.js +66 -0
  76. package/src/memory/scoring.js +145 -0
  77. package/src/memory/simhash.js +78 -0
  78. package/src/memory/sqlite-active-store.js +824 -0
  79. package/src/memory/write-policy.js +36 -0
  80. package/src/onboarding/aliases.js +33 -0
  81. package/src/onboarding/auth/api-key.js +224 -0
  82. package/src/onboarding/auth/ollama-detect.js +42 -0
  83. package/src/onboarding/clack-prompter.js +77 -0
  84. package/src/onboarding/doctor.js +530 -0
  85. package/src/onboarding/lock.js +42 -0
  86. package/src/onboarding/model-catalog.js +344 -0
  87. package/src/onboarding/phases/auth.js +589 -0
  88. package/src/onboarding/phases/build.js +130 -0
  89. package/src/onboarding/phases/choose.js +82 -0
  90. package/src/onboarding/phases/detect.js +98 -0
  91. package/src/onboarding/phases/hatch.js +216 -0
  92. package/src/onboarding/phases/identity.js +79 -0
  93. package/src/onboarding/phases/ollama.js +345 -0
  94. package/src/onboarding/phases/scaffold.js +99 -0
  95. package/src/onboarding/phases/telegram.js +377 -0
  96. package/src/onboarding/phases/validate.js +204 -0
  97. package/src/onboarding/phases/verify.js +206 -0
  98. package/src/onboarding/platform.js +482 -0
  99. package/src/onboarding/status-bar.js +95 -0
  100. package/src/onboarding/templates.js +794 -0
  101. package/src/onboarding/toml-writer.js +38 -0
  102. package/src/onboarding/tui.js +250 -0
  103. package/src/onboarding/uninstall.js +153 -0
  104. package/src/onboarding/wizard.js +499 -0
  105. package/src/providers/anthropic.js +168 -0
  106. package/src/providers/base.js +247 -0
  107. package/src/providers/circuit-breaker.js +136 -0
  108. package/src/providers/ollama.js +163 -0
  109. package/src/providers/openai-codex.js +149 -0
  110. package/src/providers/openrouter.js +136 -0
  111. package/src/providers/registry.js +36 -0
  112. package/src/providers/router.js +16 -0
  113. package/src/runtime/bootstrap-cache.js +47 -0
  114. package/src/runtime/capabilities-prompt.js +25 -0
  115. package/src/runtime/completion-ping.js +99 -0
  116. package/src/runtime/config-validator.js +121 -0
  117. package/src/runtime/context-ledger.js +360 -0
  118. package/src/runtime/cutover-readiness.js +42 -0
  119. package/src/runtime/daemon.js +729 -0
  120. package/src/runtime/delivery-ack.js +195 -0
  121. package/src/runtime/delivery-adapters/local-file.js +41 -0
  122. package/src/runtime/delivery-adapters/openclaw-cli.js +94 -0
  123. package/src/runtime/delivery-adapters/openclaw-peer.js +98 -0
  124. package/src/runtime/delivery-adapters/shadow.js +13 -0
  125. package/src/runtime/delivery-adapters/standalone-http.js +98 -0
  126. package/src/runtime/delivery-adapters/telegram.js +104 -0
  127. package/src/runtime/delivery-adapters/tui.js +128 -0
  128. package/src/runtime/delivery-manager.js +807 -0
  129. package/src/runtime/delivery-store.js +168 -0
  130. package/src/runtime/dependency-health.js +118 -0
  131. package/src/runtime/envelope.js +114 -0
  132. package/src/runtime/evaluation.js +1089 -0
  133. package/src/runtime/exec-approvals.js +216 -0
  134. package/src/runtime/executor.js +500 -0
  135. package/src/runtime/failure-ping.js +67 -0
  136. package/src/runtime/flows.js +83 -0
  137. package/src/runtime/guards.js +45 -0
  138. package/src/runtime/handoff.js +51 -0
  139. package/src/runtime/identity-cache.js +28 -0
  140. package/src/runtime/improvement-engine.js +109 -0
  141. package/src/runtime/improvement-harness.js +581 -0
  142. package/src/runtime/input-sanitiser.js +72 -0
  143. package/src/runtime/interaction-contract.js +347 -0
  144. package/src/runtime/lane-readiness.js +226 -0
  145. package/src/runtime/migration.js +323 -0
  146. package/src/runtime/model-resolution.js +78 -0
  147. package/src/runtime/network.js +64 -0
  148. package/src/runtime/notification-store.js +97 -0
  149. package/src/runtime/notifier.js +256 -0
  150. package/src/runtime/orchestrator.js +53 -0
  151. package/src/runtime/orphan-reaper.js +41 -0
  152. package/src/runtime/output-contract-schema.js +139 -0
  153. package/src/runtime/output-contract-validator.js +439 -0
  154. package/src/runtime/peer-readiness.js +69 -0
  155. package/src/runtime/peer-registry.js +133 -0
  156. package/src/runtime/pilot-status.js +108 -0
  157. package/src/runtime/prompt-builder.js +261 -0
  158. package/src/runtime/provider-attempt.js +582 -0
  159. package/src/runtime/report-fallback.js +71 -0
  160. package/src/runtime/result-normalizer.js +183 -0
  161. package/src/runtime/retention.js +74 -0
  162. package/src/runtime/review.js +244 -0
  163. package/src/runtime/route-job.js +15 -0
  164. package/src/runtime/run-store.js +38 -0
  165. package/src/runtime/schedule.js +88 -0
  166. package/src/runtime/scheduler-state.js +434 -0
  167. package/src/runtime/scheduler.js +656 -0
  168. package/src/runtime/session-compactor.js +182 -0
  169. package/src/runtime/session-search.js +155 -0
  170. package/src/runtime/slack-inbound.js +249 -0
  171. package/src/runtime/ssrf.js +102 -0
  172. package/src/runtime/status-aggregator.js +330 -0
  173. package/src/runtime/task-contract.js +140 -0
  174. package/src/runtime/task-packet.js +107 -0
  175. package/src/runtime/task-router.js +140 -0
  176. package/src/runtime/telegram-inbound.js +1565 -0
  177. package/src/runtime/token-counter.js +134 -0
  178. package/src/runtime/token-estimator.js +59 -0
  179. package/src/runtime/tool-loop.js +200 -0
  180. package/src/runtime/transport-server.js +311 -0
  181. package/src/runtime/tui-server.js +411 -0
  182. package/src/runtime/ulid.js +44 -0
  183. package/src/security/ssrf-check.js +197 -0
  184. package/src/setup.js +369 -0
  185. package/src/shadow/bridge.js +303 -0
  186. package/src/skills/loader.js +84 -0
  187. package/src/tools/catalog.json +49 -0
  188. package/src/tools/cli-delegate.js +44 -0
  189. package/src/tools/mcp-client.js +106 -0
  190. package/src/tools/micro/cancel-task.js +6 -0
  191. package/src/tools/micro/complete-task.js +6 -0
  192. package/src/tools/micro/fail-task.js +6 -0
  193. package/src/tools/micro/http-fetch.js +74 -0
  194. package/src/tools/micro/index.js +36 -0
  195. package/src/tools/micro/lcm-recall.js +60 -0
  196. package/src/tools/micro/list-dir.js +17 -0
  197. package/src/tools/micro/list-skills.js +46 -0
  198. package/src/tools/micro/load-skill.js +38 -0
  199. package/src/tools/micro/memory-search.js +45 -0
  200. package/src/tools/micro/read-file.js +11 -0
  201. package/src/tools/micro/session-search.js +54 -0
  202. package/src/tools/micro/shell-exec.js +43 -0
  203. package/src/tools/micro/trigger-job.js +79 -0
  204. package/src/tools/micro/web-search.js +58 -0
  205. package/src/tools/micro/workspace-paths.js +39 -0
  206. package/src/tools/micro/write-file.js +14 -0
  207. package/src/tools/micro/write-memory.js +41 -0
  208. package/src/tools/registry.js +348 -0
  209. package/src/tools/tool-result-contract.js +36 -0
  210. package/src/tui/chat.js +835 -0
  211. package/src/tui/renderer.js +175 -0
  212. package/src/tui/socket-client.js +217 -0
  213. package/src/utils/canonical-json.js +29 -0
  214. package/src/utils/compaction.js +30 -0
  215. package/src/utils/env-loader.js +5 -0
  216. package/src/utils/errors.js +80 -0
  217. package/src/utils/fs.js +101 -0
  218. package/src/utils/ids.js +5 -0
  219. package/src/utils/model-context-limits.js +30 -0
  220. package/src/utils/token-budget.js +74 -0
  221. package/src/utils/usage-cost.js +25 -0
  222. package/src/utils/usage-metrics.js +14 -0
  223. package/vendor/smol-toml-1.5.2.tgz +0 -0
@@ -0,0 +1,285 @@
1
+ import { spawn } from "node:child_process";
2
+ import { getAuthProfile, resolveAuthProfilesPath, updateAuthProfile, upsertAuthProfile } from "./auth-profiles.js";
3
+ import { loginOpenAICodex } from "@mariozechner/pi-ai/oauth";
4
+
5
+ const REFRESH_BUFFER_MS = 5 * 60 * 1000;
6
+ const OPENAI_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
7
+ export const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai-codex:default";
8
+
9
+ function spawnDetached(command, args) {
10
+ try {
11
+ const child = spawn(command, args, {
12
+ detached: true,
13
+ stdio: "ignore",
14
+ });
15
+ child.unref();
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ async function openBrowser(url) {
23
+ if (!url) return false;
24
+ if (process.platform === "darwin") {
25
+ return spawnDetached("open", [url]);
26
+ }
27
+ if (process.platform === "win32") {
28
+ return spawnDetached("cmd", ["/c", "start", "", url]);
29
+ }
30
+ return spawnDetached("xdg-open", [url]);
31
+ }
32
+
33
+ function decodeJwtPayload(token) {
34
+ try {
35
+ const parts = String(token || "").split(".");
36
+ if (parts.length < 2) return null;
37
+ const payload = parts[1]
38
+ .replace(/-/g, "+")
39
+ .replace(/_/g, "/")
40
+ .padEnd(Math.ceil(parts[1].length / 4) * 4, "=");
41
+ return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function resolveExpires(profile) {
48
+ if (profile?.expires === undefined || profile?.expires === null || profile?.expires === "") {
49
+ return null;
50
+ }
51
+ if (typeof profile.expires === "number" && Number.isFinite(profile.expires)) {
52
+ return profile.expires;
53
+ }
54
+ const parsed = Date.parse(profile.expires);
55
+ return Number.isNaN(parsed) ? null : parsed;
56
+ }
57
+
58
+ function deriveClientId(profile) {
59
+ if (profile?.clientId) return String(profile.clientId);
60
+ const payload = decodeJwtPayload(profile?.access);
61
+ return payload?.client_id || null;
62
+ }
63
+
64
+ function normalizeOauthCredentials(credentials) {
65
+ return {
66
+ type: "oauth",
67
+ provider: "openai-codex",
68
+ access: credentials.access,
69
+ refresh: credentials.refresh,
70
+ expires: credentials.expires,
71
+ accountId: credentials.accountId || null,
72
+ clientId: deriveClientId({ access: credentials.access }) || null,
73
+ };
74
+ }
75
+
76
+ export async function initiateOpenAICodexOAuthFlow({
77
+ profileId = OPENAI_CODEX_DEFAULT_PROFILE_ID,
78
+ filePath = resolveAuthProfilesPath(),
79
+ loginImpl = loginOpenAICodex,
80
+ openUrlImpl = openBrowser,
81
+ promptForPasteImpl = async () => "",
82
+ onNote = null,
83
+ onProgress = null,
84
+ } = {}) {
85
+ if (typeof loginImpl !== "function") {
86
+ throw new Error("No login implementation available for OpenAI Codex OAuth.");
87
+ }
88
+
89
+ if (typeof onNote === "function") {
90
+ await onNote([
91
+ "Browser will open for OpenAI authentication.",
92
+ "If it does not open automatically, paste the URL below into your browser.",
93
+ "OAuth callback: localhost:1455",
94
+ ].join("\n"), "OpenAI OAuth");
95
+ }
96
+
97
+ const credentials = await loginImpl({
98
+ originator: "nemoris",
99
+ onAuth: async ({ url }) => {
100
+ if (typeof onProgress === "function") {
101
+ await onProgress(`Complete sign-in in browser...\n ${url}`);
102
+ }
103
+ await openUrlImpl(url);
104
+ },
105
+ onPrompt: async ({ message }) => promptForPasteImpl(message || "Paste the authorization code or redirect URL"),
106
+ onProgress: async (message) => {
107
+ if (typeof onProgress === "function") {
108
+ await onProgress(message);
109
+ }
110
+ },
111
+ });
112
+
113
+ const profile = normalizeOauthCredentials(credentials);
114
+ upsertAuthProfile(profileId, profile, filePath);
115
+ return profile;
116
+ }
117
+
118
+ export function inspectOpenAICodexProfile(profile, { now = Date.now() } = {}) {
119
+ if (!profile || typeof profile !== "object") {
120
+ return { state: "profile_incomplete", reason: "missing_profile" };
121
+ }
122
+ if (profile.provider !== "openai-codex") {
123
+ return { state: "profile_incomplete", reason: "wrong_provider" };
124
+ }
125
+ if (profile.type !== "oauth" && profile.type !== "api_key") {
126
+ return { state: "profile_incomplete", reason: "unsupported_type" };
127
+ }
128
+ if (profile.type === "api_key") {
129
+ return profile.key
130
+ ? { state: "usable_token", reason: "api_key", token: profile.key }
131
+ : { state: "profile_incomplete", reason: "missing_api_key" };
132
+ }
133
+
134
+ const access = profile.access || null;
135
+ const refresh = profile.refresh || null;
136
+ const expiresAt = resolveExpires(profile);
137
+
138
+ if (!access && refresh) {
139
+ return { state: "refresh_needed", reason: "missing_access_token", expiresAt };
140
+ }
141
+ if (!access && !refresh) {
142
+ return { state: "reauth_required", reason: "missing_access_token" };
143
+ }
144
+ if (expiresAt === null) {
145
+ if (refresh) {
146
+ return { state: "refresh_needed", reason: "missing_expires", expiresAt: null };
147
+ }
148
+ return { state: "profile_incomplete", reason: "missing_expires" };
149
+ }
150
+ if (expiresAt <= now) {
151
+ if (refresh) {
152
+ return { state: "refresh_needed", reason: "expired", expiresAt };
153
+ }
154
+ return { state: "reauth_required", reason: "expired_no_refresh", expiresAt };
155
+ }
156
+ if (expiresAt - now <= REFRESH_BUFFER_MS) {
157
+ if (refresh) {
158
+ return { state: "refresh_needed", reason: "near_expiry", expiresAt };
159
+ }
160
+ return { state: "usable_token", reason: "near_expiry_without_refresh", token: access, expiresAt };
161
+ }
162
+ return { state: "usable_token", reason: "ok", token: access, expiresAt };
163
+ }
164
+
165
+ function buildRefreshBody(profile) {
166
+ const refresh = profile?.refresh || null;
167
+ if (!refresh) {
168
+ throw new Error("OpenAI Codex OAuth profile has no refresh token.");
169
+ }
170
+ const params = new URLSearchParams({
171
+ grant_type: "refresh_token",
172
+ refresh_token: refresh,
173
+ });
174
+ const clientId = deriveClientId(profile);
175
+ if (clientId) {
176
+ params.set("client_id", clientId);
177
+ }
178
+ return params;
179
+ }
180
+
181
+ export async function refreshOpenAICodexProfile(profile, {
182
+ fetchImpl = globalThis.fetch,
183
+ tokenUrl = process.env.NEMORIS_OPENAI_OAUTH_TOKEN_URL || OPENAI_OAUTH_TOKEN_URL,
184
+ now = Date.now()
185
+ } = {}) {
186
+ if (profile?.type === "api_key") {
187
+ return profile;
188
+ }
189
+ if (!fetchImpl) {
190
+ throw new Error("No fetch implementation available for OpenAI Codex OAuth refresh.");
191
+ }
192
+
193
+ const response = await fetchImpl(tokenUrl, {
194
+ method: "POST",
195
+ headers: {
196
+ "content-type": "application/x-www-form-urlencoded",
197
+ "accept": "application/json",
198
+ },
199
+ body: buildRefreshBody(profile),
200
+ });
201
+
202
+ const raw = await response.text();
203
+ let data;
204
+ try {
205
+ data = raw ? JSON.parse(raw) : {};
206
+ } catch {
207
+ data = { error_description: raw };
208
+ }
209
+
210
+ if (!response.ok) {
211
+ const detail = data?.error_description || data?.error || raw || `HTTP ${response.status}`;
212
+ throw new Error(`OpenAI Codex OAuth refresh failed: ${detail}`);
213
+ }
214
+
215
+ const access = data?.access_token || null;
216
+ if (!access) {
217
+ throw new Error("OpenAI Codex OAuth refresh returned no access_token.");
218
+ }
219
+ const refresh = data?.refresh_token || profile.refresh || null;
220
+ const expiresInMs = Number(data?.expires_in || 0) > 0 ? Number(data.expires_in) * 1000 : null;
221
+ const nextExpires = expiresInMs ? now + expiresInMs : resolveExpires(profile);
222
+ if (!nextExpires) {
223
+ throw new Error("OpenAI Codex OAuth refresh returned no usable expiry.");
224
+ }
225
+
226
+ return {
227
+ ...profile,
228
+ type: "oauth",
229
+ provider: "openai-codex",
230
+ access,
231
+ refresh,
232
+ expires: nextExpires,
233
+ clientId: deriveClientId({ ...profile, access }) || profile.clientId || null
234
+ };
235
+ }
236
+
237
+ export async function resolveOpenAICodexAccess(profileId, {
238
+ fetchImpl = globalThis.fetch,
239
+ filePath = resolveAuthProfilesPath(),
240
+ now = Date.now()
241
+ } = {}) {
242
+ const profile = getAuthProfile(profileId, filePath);
243
+ const inspection = inspectOpenAICodexProfile(profile, { now });
244
+
245
+ if (inspection.state === "usable_token") {
246
+ return {
247
+ status: inspection.state,
248
+ reason: inspection.reason,
249
+ token: inspection.token,
250
+ profile
251
+ };
252
+ }
253
+
254
+ if (inspection.state === "refresh_needed") {
255
+ try {
256
+ const refreshed = await refreshOpenAICodexProfile(profile, { fetchImpl, now });
257
+ updateAuthProfile(profileId, () => refreshed, filePath);
258
+ return {
259
+ status: "usable_token",
260
+ reason: `refreshed:${inspection.reason}`,
261
+ token: refreshed.access,
262
+ profile: refreshed
263
+ };
264
+ } catch (error) {
265
+ const reason = inspection.reason === "missing_expires"
266
+ ? "profile_incomplete"
267
+ : profile?.refresh
268
+ ? "refresh_failed"
269
+ : "reauth_required";
270
+ const wrapped = new Error(error.message);
271
+ wrapped.authState = reason;
272
+ wrapped.profileReason = inspection.reason;
273
+ throw wrapped;
274
+ }
275
+ }
276
+
277
+ const error = new Error(
278
+ inspection.reason === "missing_expires"
279
+ ? "OpenAI Codex OAuth profile is missing expires; re-authenticate or provide a refreshable profile."
280
+ : `OpenAI Codex OAuth profile is not usable (${inspection.reason}).`
281
+ );
282
+ error.authState = inspection.state;
283
+ error.profileReason = inspection.reason;
284
+ throw error;
285
+ }