nemoris 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/.env.example +49 -49
  2. package/LICENSE +21 -21
  3. package/README.md +209 -209
  4. package/SECURITY.md +59 -119
  5. package/bin/nemoris +46 -46
  6. package/config/agents/agent.toml.example +28 -28
  7. package/config/agents/content.toml +23 -0
  8. package/config/agents/default.toml +22 -22
  9. package/config/agents/heartbeat.toml +35 -0
  10. package/config/agents/iris.toml +23 -0
  11. package/config/agents/lab.toml +23 -0
  12. package/config/agents/main.toml +45 -0
  13. package/config/agents/nemo.toml +21 -0
  14. package/config/agents/ops.toml +38 -0
  15. package/config/agents/orchestrator.toml +18 -18
  16. package/config/agents/revenue.toml +23 -0
  17. package/config/agents/testyboo.toml +19 -0
  18. package/config/delivery.toml +73 -73
  19. package/config/embeddings.toml +5 -5
  20. package/config/identity/content-purpose.md +11 -0
  21. package/config/identity/content-soul.md +45 -0
  22. package/config/identity/default-purpose.md +1 -1
  23. package/config/identity/default-soul.md +3 -3
  24. package/config/identity/heartbeat-purpose.md +9 -0
  25. package/config/identity/heartbeat-soul.md +16 -0
  26. package/config/identity/iris-purpose.md +17 -0
  27. package/config/identity/iris-soul.md +68 -0
  28. package/config/identity/lab-purpose.md +10 -0
  29. package/config/identity/lab-soul.md +38 -0
  30. package/config/identity/main-purpose.md +17 -0
  31. package/config/identity/main-soul.md +66 -0
  32. package/config/identity/main-user.md +22 -0
  33. package/config/identity/ops-purpose.md +9 -0
  34. package/config/identity/ops-soul.md +16 -0
  35. package/config/identity/orchestrator-purpose.md +1 -1
  36. package/config/identity/orchestrator-soul.md +1 -1
  37. package/config/identity/revenue-purpose.md +9 -0
  38. package/config/identity/revenue-soul.md +41 -0
  39. package/config/identity/testyboo-purpose.md +13 -0
  40. package/config/identity/testyboo-soul.md +20 -0
  41. package/config/improvement-targets.toml +15 -15
  42. package/config/jobs/heartbeat-check.toml +30 -30
  43. package/config/jobs/memory-rollup.toml +46 -46
  44. package/config/jobs/workspace-health.toml +63 -63
  45. package/config/mcp.toml +16 -16
  46. package/config/output-contracts.toml +17 -17
  47. package/config/peers.toml +32 -32
  48. package/config/peers.toml.example +32 -32
  49. package/config/policies/memory-default.toml +10 -10
  50. package/config/policies/memory-heartbeat.toml +5 -5
  51. package/config/policies/memory-ops.toml +10 -10
  52. package/config/policies/tools-heartbeat-minimal.toml +8 -8
  53. package/config/policies/tools-interactive-safe.toml +8 -8
  54. package/config/policies/tools-ops-bounded.toml +8 -8
  55. package/config/policies/tools-orchestrator.toml +7 -7
  56. package/config/providers/anthropic.toml +15 -15
  57. package/config/providers/ollama.toml +5 -5
  58. package/config/providers/openai-codex.toml +9 -9
  59. package/config/providers/openrouter.toml +5 -5
  60. package/config/router.toml +22 -22
  61. package/config/runtime.toml +114 -114
  62. package/config/skills/self-improvement.toml +15 -15
  63. package/config/skills/telegram-onboarding-spec.md +240 -240
  64. package/config/skills/workspace-monitor.toml +15 -15
  65. package/config/task-router.toml +42 -42
  66. package/install.sh +50 -50
  67. package/package.json +91 -90
  68. package/src/auth/auth-profiles.js +169 -169
  69. package/src/auth/openai-codex-oauth.js +285 -285
  70. package/src/battle.js +449 -449
  71. package/src/cli/help.js +265 -265
  72. package/src/cli/output-filter.js +49 -49
  73. package/src/cli/runtime-control.js +704 -704
  74. package/src/cli-main.js +2763 -2763
  75. package/src/cli.js +78 -78
  76. package/src/config/loader.js +332 -332
  77. package/src/config/schema-validator.js +214 -214
  78. package/src/config/toml-lite.js +8 -8
  79. package/src/daemon/action-handlers.js +71 -71
  80. package/src/daemon/healing-tick.js +87 -87
  81. package/src/daemon/health-probes.js +90 -90
  82. package/src/daemon/notifier.js +57 -57
  83. package/src/daemon/nurse.js +218 -218
  84. package/src/daemon/repair-log.js +106 -106
  85. package/src/daemon/rule-staging.js +90 -90
  86. package/src/daemon/rules.js +29 -29
  87. package/src/daemon/telegram-commands.js +54 -54
  88. package/src/daemon/updater.js +85 -85
  89. package/src/jobs/job-runner.js +78 -78
  90. package/src/mcp/consumer.js +129 -129
  91. package/src/memory/active-recall.js +171 -171
  92. package/src/memory/backend-manager.js +97 -97
  93. package/src/memory/backends/file-backend.js +38 -38
  94. package/src/memory/backends/qmd-backend.js +219 -219
  95. package/src/memory/embedding-guards.js +24 -24
  96. package/src/memory/embedding-index.js +118 -118
  97. package/src/memory/embedding-service.js +179 -179
  98. package/src/memory/file-index.js +177 -177
  99. package/src/memory/memory-signature.js +5 -5
  100. package/src/memory/memory-store.js +648 -648
  101. package/src/memory/retrieval-planner.js +66 -66
  102. package/src/memory/scoring.js +145 -145
  103. package/src/memory/simhash.js +78 -78
  104. package/src/memory/sqlite-active-store.js +824 -824
  105. package/src/memory/write-policy.js +36 -36
  106. package/src/onboarding/aliases.js +33 -33
  107. package/src/onboarding/auth/api-key.js +224 -224
  108. package/src/onboarding/auth/ollama-detect.js +42 -42
  109. package/src/onboarding/clack-prompter.js +77 -77
  110. package/src/onboarding/doctor.js +530 -530
  111. package/src/onboarding/lock.js +42 -42
  112. package/src/onboarding/model-catalog.js +344 -344
  113. package/src/onboarding/phases/auth.js +576 -589
  114. package/src/onboarding/phases/build.js +130 -130
  115. package/src/onboarding/phases/choose.js +82 -82
  116. package/src/onboarding/phases/detect.js +98 -98
  117. package/src/onboarding/phases/hatch.js +216 -216
  118. package/src/onboarding/phases/identity.js +79 -79
  119. package/src/onboarding/phases/ollama.js +345 -345
  120. package/src/onboarding/phases/scaffold.js +99 -99
  121. package/src/onboarding/phases/telegram.js +377 -377
  122. package/src/onboarding/phases/validate.js +204 -204
  123. package/src/onboarding/phases/verify.js +206 -206
  124. package/src/onboarding/platform.js +482 -482
  125. package/src/onboarding/status-bar.js +95 -95
  126. package/src/onboarding/templates.js +794 -794
  127. package/src/onboarding/toml-writer.js +38 -38
  128. package/src/onboarding/tui.js +250 -250
  129. package/src/onboarding/uninstall.js +153 -153
  130. package/src/onboarding/wizard.js +516 -499
  131. package/src/providers/anthropic.js +168 -168
  132. package/src/providers/base.js +247 -247
  133. package/src/providers/circuit-breaker.js +136 -136
  134. package/src/providers/ollama.js +163 -163
  135. package/src/providers/openai-codex.js +149 -149
  136. package/src/providers/openrouter.js +136 -136
  137. package/src/providers/registry.js +36 -36
  138. package/src/providers/router.js +16 -16
  139. package/src/runtime/bootstrap-cache.js +47 -47
  140. package/src/runtime/capabilities-prompt.js +25 -25
  141. package/src/runtime/completion-ping.js +99 -99
  142. package/src/runtime/config-validator.js +121 -121
  143. package/src/runtime/context-ledger.js +360 -360
  144. package/src/runtime/cutover-readiness.js +42 -42
  145. package/src/runtime/daemon.js +729 -729
  146. package/src/runtime/delivery-ack.js +195 -195
  147. package/src/runtime/delivery-adapters/local-file.js +41 -41
  148. package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
  149. package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
  150. package/src/runtime/delivery-adapters/shadow.js +13 -13
  151. package/src/runtime/delivery-adapters/standalone-http.js +98 -98
  152. package/src/runtime/delivery-adapters/telegram.js +104 -104
  153. package/src/runtime/delivery-adapters/tui.js +128 -128
  154. package/src/runtime/delivery-manager.js +807 -807
  155. package/src/runtime/delivery-store.js +168 -168
  156. package/src/runtime/dependency-health.js +118 -118
  157. package/src/runtime/envelope.js +114 -114
  158. package/src/runtime/evaluation.js +1089 -1089
  159. package/src/runtime/exec-approvals.js +216 -216
  160. package/src/runtime/executor.js +500 -500
  161. package/src/runtime/failure-ping.js +67 -67
  162. package/src/runtime/flows.js +83 -83
  163. package/src/runtime/guards.js +45 -45
  164. package/src/runtime/handoff.js +51 -51
  165. package/src/runtime/identity-cache.js +28 -28
  166. package/src/runtime/improvement-engine.js +109 -109
  167. package/src/runtime/improvement-harness.js +581 -581
  168. package/src/runtime/input-sanitiser.js +72 -72
  169. package/src/runtime/interaction-contract.js +347 -347
  170. package/src/runtime/lane-readiness.js +226 -226
  171. package/src/runtime/migration.js +323 -323
  172. package/src/runtime/model-resolution.js +78 -78
  173. package/src/runtime/network.js +64 -64
  174. package/src/runtime/notification-store.js +97 -97
  175. package/src/runtime/notifier.js +256 -256
  176. package/src/runtime/orchestrator.js +53 -53
  177. package/src/runtime/orphan-reaper.js +41 -41
  178. package/src/runtime/output-contract-schema.js +139 -139
  179. package/src/runtime/output-contract-validator.js +439 -439
  180. package/src/runtime/peer-readiness.js +69 -69
  181. package/src/runtime/peer-registry.js +133 -133
  182. package/src/runtime/pilot-status.js +108 -108
  183. package/src/runtime/prompt-builder.js +261 -261
  184. package/src/runtime/provider-attempt.js +582 -582
  185. package/src/runtime/report-fallback.js +71 -71
  186. package/src/runtime/result-normalizer.js +183 -183
  187. package/src/runtime/retention.js +74 -74
  188. package/src/runtime/review.js +244 -244
  189. package/src/runtime/route-job.js +15 -15
  190. package/src/runtime/run-store.js +38 -38
  191. package/src/runtime/schedule.js +88 -88
  192. package/src/runtime/scheduler-state.js +434 -434
  193. package/src/runtime/scheduler.js +656 -656
  194. package/src/runtime/session-compactor.js +182 -182
  195. package/src/runtime/session-search.js +155 -155
  196. package/src/runtime/slack-inbound.js +249 -249
  197. package/src/runtime/ssrf.js +102 -102
  198. package/src/runtime/status-aggregator.js +330 -330
  199. package/src/runtime/task-contract.js +140 -140
  200. package/src/runtime/task-packet.js +107 -107
  201. package/src/runtime/task-router.js +140 -140
  202. package/src/runtime/telegram-inbound.js +1565 -1565
  203. package/src/runtime/token-counter.js +134 -134
  204. package/src/runtime/token-estimator.js +59 -59
  205. package/src/runtime/tool-loop.js +200 -200
  206. package/src/runtime/transport-server.js +311 -311
  207. package/src/runtime/tui-server.js +411 -411
  208. package/src/runtime/ulid.js +44 -44
  209. package/src/security/ssrf-check.js +197 -197
  210. package/src/setup.js +369 -369
  211. package/src/shadow/bridge.js +303 -303
  212. package/src/skills/loader.js +84 -84
  213. package/src/tools/catalog.json +49 -49
  214. package/src/tools/cli-delegate.js +44 -44
  215. package/src/tools/mcp-client.js +106 -106
  216. package/src/tools/micro/cancel-task.js +6 -6
  217. package/src/tools/micro/complete-task.js +6 -6
  218. package/src/tools/micro/fail-task.js +6 -6
  219. package/src/tools/micro/http-fetch.js +74 -74
  220. package/src/tools/micro/index.js +36 -36
  221. package/src/tools/micro/lcm-recall.js +60 -60
  222. package/src/tools/micro/list-dir.js +17 -17
  223. package/src/tools/micro/list-skills.js +46 -46
  224. package/src/tools/micro/load-skill.js +38 -38
  225. package/src/tools/micro/memory-search.js +45 -45
  226. package/src/tools/micro/read-file.js +11 -11
  227. package/src/tools/micro/session-search.js +54 -54
  228. package/src/tools/micro/shell-exec.js +43 -43
  229. package/src/tools/micro/trigger-job.js +79 -79
  230. package/src/tools/micro/web-search.js +58 -58
  231. package/src/tools/micro/workspace-paths.js +39 -39
  232. package/src/tools/micro/write-file.js +14 -14
  233. package/src/tools/micro/write-memory.js +41 -41
  234. package/src/tools/registry.js +348 -348
  235. package/src/tools/tool-result-contract.js +36 -36
  236. package/src/tui/chat.js +835 -835
  237. package/src/tui/renderer.js +175 -175
  238. package/src/tui/socket-client.js +217 -217
  239. package/src/utils/canonical-json.js +29 -29
  240. package/src/utils/compaction.js +30 -30
  241. package/src/utils/env-loader.js +5 -5
  242. package/src/utils/errors.js +80 -80
  243. package/src/utils/fs.js +101 -101
  244. package/src/utils/ids.js +5 -5
  245. package/src/utils/model-context-limits.js +30 -30
  246. package/src/utils/token-budget.js +74 -74
  247. package/src/utils/usage-cost.js +25 -25
  248. package/src/utils/usage-metrics.js +14 -14
@@ -1,240 +1,240 @@
1
- # Spec: Telegram Onboarding TUI Phase
2
-
3
- ## Overview
4
-
5
- Add Telegram setup as an optional sub-phase inside the Build phase (Phase 3) of the onboarding wizard. Users can connect Telegram during `nemoris init` or defer to `nemoris setup telegram` later.
6
-
7
- ## Placement
8
-
9
- Inside `buildFresh()` and `buildShadow()` in `src/onboarding/phases/build.js`, after auth completes:
10
-
11
- ```
12
- scaffold → identity → auth → telegram (optional) → done
13
- ```
14
-
15
- Not a new top-level phase. Lock file carries `telegramConfigured: boolean` and `telegramVerified: boolean` in wizard state — no phase index change.
16
-
17
- ---
18
-
19
- ## Interactive Flow
20
-
21
- ### Gate
22
-
23
- ```
24
- Connect Telegram? (Y/n)
25
- ```
26
-
27
- - `n` → skip, `state.telegramConfigured = false`
28
- - `Y` → enter sub-flow
29
-
30
- ### Step 1: Bot Token
31
-
32
- ```
33
- Telegram Bot Token (from @BotFather): ●●●●●●
34
- ✓ Token validated @kodi_nemoris_bot
35
- ```
36
-
37
- - Input via `promptSecret()`
38
- - Validate by calling Telegram `getMe` API — new function in `telegram-inbound.js`:
39
- ```js
40
- export async function getMe(botToken, { fetchImpl = globalThis.fetch } = {}) {
41
- const res = await fetchImpl(`https://api.telegram.org/bot${botToken}/getMe`, { method: "GET" });
42
- const body = await res.json();
43
- if (!body.ok) return { ok: false, error: body.description || "getMe failed" };
44
- return { ok: true, username: body.result.username, firstName: body.result.first_name };
45
- }
46
- ```
47
- - On failure: show error, offer re-entry or skip
48
- - On success: write token to `.env` as `NEMORIS_TELEGRAM_BOT_TOKEN=<value>`
49
- - macOS only: call `launchctl setenv NEMORIS_TELEGRAM_BOT_TOKEN <value>` so the running daemon picks it up without restart. Guard with `process.platform === "darwin"`.
50
-
51
- ### Step 2: Chat ID Discovery (daemon-aware)
52
-
53
- Two paths based on whether the daemon is currently running.
54
-
55
- #### Detection
56
-
57
- Check if daemon is running:
58
- 1. macOS: `launchctl list ai.nanoclaw.daemon` — exit code 0 means loaded
59
- 2. Fallback: check for `state/daemon.pid` or similar runtime indicator
60
-
61
- #### Path A: Daemon is running
62
-
63
- ```
64
- Daemon is running. Send any message to @kodi_nemoris_bot on Telegram...
65
- Waiting for your message...
66
- ✓ Found you chat_id: 7781763328, @leeUsername
67
- ```
68
-
69
- - Poll the SQLite state store (`state/active.db`) for the first interactive job row where `source = 'telegram'`
70
- - Query: `SELECT chat_id FROM interactive_jobs WHERE source = 'telegram' ORDER BY created_at DESC LIMIT 1`
71
- - Poll interval: 2s, timeout: 120s
72
- - On timeout: offer to enter chat_id manually or retry
73
-
74
- This avoids any conflict with the daemon's getUpdates polling connection. No `deleteWebhook`, no `getUpdates` — the daemon handles message ingestion, the wizard just reads the result.
75
-
76
- #### Path B: Daemon is not running
77
-
78
- ```
79
- Send any message to @kodi_nemoris_bot on Telegram, then press Enter...
80
- ✓ Found you chat_id: 7781763328, @leeUsername
81
- ```
82
-
83
- - Reuses existing `whoami(botToken)` function (deleteWebhook → getUpdates → extract chat)
84
- - No conflict because no daemon is competing for getUpdates
85
- - On failure (no messages found): prompt user to send a message first and press Enter to retry
86
-
87
- #### Both paths
88
-
89
- - Auto-fill `operator_chat_id` and `authorized_chat_ids[0]` with discovered chat_id
90
- - If chat_id was already in `config/runtime.toml`, show it and ask to confirm or replace
91
-
92
- ### Step 3: Delivery Mode
93
-
94
- ```
95
- Delivery mode:
96
- [1] Long polling (recommended — no tunnel needed)
97
- [2] Webhook (requires public URL)
98
- ```
99
-
100
- - Uses `select()` from tui.js
101
- - **Polling** (default): `polling_mode = true`, `webhook_url = ""`
102
- - **Webhook**: prompts for public URL, validates format, calls `registerWebhook(token, url)`. On failure: show error, offer retry or fall back to polling.
103
-
104
- ### Step 4: Write Config
105
-
106
- Writes the `[telegram]` section to `config/runtime.toml`:
107
-
108
- ```toml
109
- [telegram]
110
- bot_token_env = "NEMORIS_TELEGRAM_BOT_TOKEN"
111
- polling_mode = true
112
- webhook_url = ""
113
- operator_chat_id = "7781763328"
114
- authorized_chat_ids = ["7781763328"]
115
- default_agent = "kodi"
116
- ```
117
-
118
- - `default_agent` is set to `state.agentId` from the Build phase
119
- - Preserves all other sections in runtime.toml (read → patch → write)
120
- - Uses TOML serialization consistent with existing config writer patterns
121
-
122
- ### Step 5: Smoke Test (with failure handling)
123
-
124
- ```
125
- Sending test message...
126
- ✓ Telegram connected "Hello from Nemoris"
127
- ```
128
-
129
- Sends a test message via `sendMessage` to the discovered `chat_id` using the validated token.
130
-
131
- #### Failure path
132
-
133
- ```
134
- Sending test message...
135
- ✗ Delivery failed: 403 Forbidden — bot was blocked by the user
136
-
137
- [1] Retry
138
- [2] Skip — finish setup without verification
139
- [3] Re-enter bot token
140
-
141
- Choice:
142
- ```
143
-
144
- - **Retry**: loops back to sendMessage
145
- - **Skip**: sets `state.telegramConfigured = true`, `state.telegramVerified = false`. Verify phase will show a warning: `"⚠ Telegram configured but not verified — run nemoris setup telegram to test"`
146
- - **Re-enter token**: loops back to Step 1
147
-
148
- The wizard does NOT exit cleanly on a failed smoke test. It always gives the user a choice.
149
-
150
- ---
151
-
152
- ## Non-Interactive Mode
153
-
154
- Env-var driven, zero prompts. Suitable for Docker/CI.
155
-
156
- | Env Var | Required | Default | Purpose |
157
- |---------|----------|---------|---------|
158
- | `NEMORIS_TELEGRAM_BOT_TOKEN` | Yes (to enable) | — | If set, Telegram phase runs |
159
- | `NEMORIS_TELEGRAM_CHAT_ID` | No | — | Skips whoami/state-store probe |
160
- | `NEMORIS_TELEGRAM_MODE` | No | `polling` | `polling` or `webhook` |
161
- | `NEMORIS_TELEGRAM_WEBHOOK_URL` | If mode=webhook | — | Public URL for webhook registration |
162
- | `NEMORIS_SKIP_TELEGRAM` | No | — | Set to `true` to skip entirely |
163
-
164
- If `NEMORIS_TELEGRAM_BOT_TOKEN` is not set, Telegram phase is silently skipped. No error.
165
-
166
- If `NEMORIS_TELEGRAM_CHAT_ID` is not set and daemon is not running, the whoami probe runs automatically (no stdin needed). If it returns null, Telegram is configured without a chat_id — the user must set it manually later.
167
-
168
- ---
169
-
170
- ## Verify Phase Changes
171
-
172
- ### Telegram configured + verified
173
-
174
- ```
175
- What's next:
176
-
177
- nemoris start start the daemon
178
- nemoris status see your agent's state
179
- Message @kodi_nemoris_bot talk to your agent via Telegram
180
- ```
181
-
182
- ### Telegram configured + NOT verified
183
-
184
- ```
185
- ⚠ Telegram configured but not verified
186
-
187
- What's next:
188
-
189
- nemoris setup telegram verify Telegram connection
190
- nemoris start start the daemon
191
- ```
192
-
193
- ### Telegram not configured
194
-
195
- ```
196
- What's next:
197
-
198
- nemoris start start the daemon
199
- nemoris status see your agent's state
200
- nemoris setup telegram connect Telegram later
201
- ```
202
-
203
- ---
204
-
205
- ## CLI Refactor
206
-
207
- Extract shared logic from `setupTelegram()` and `telegramWhoami()` in `cli.js` so both the wizard phase and the standalone commands use the same code paths:
208
-
209
- - `validateBotToken(token)` → calls `getMe`, returns `{ ok, username, error }`
210
- - `discoverChatId(token, { daemonRunning, stateStore, webhookUrl })` → daemon-aware chat_id discovery
211
- - `writeTelegramConfig(installDir, config)` → patches `[telegram]` section in runtime.toml
212
- - `sendTestMessage(token, chatId)` → smoke test delivery
213
-
214
- These live in `src/onboarding/phases/telegram.js` and are imported by `cli.js`.
215
-
216
- The standalone `nemoris setup telegram` command becomes a thin wrapper that calls the same phase functions with an interactive readline session, outside the wizard context.
217
-
218
- ---
219
-
220
- ## Files
221
-
222
- | File | Change |
223
- |------|--------|
224
- | `src/onboarding/phases/telegram.js` | **New** — Telegram sub-phase: token validation, daemon-aware chat_id discovery, mode selection, config writing, smoke test with failure handling |
225
- | `src/onboarding/phases/build.js` | Import and call `runTelegramPhase()` after auth in both `buildFresh()` and `buildShadow()` |
226
- | `src/onboarding/phases/verify.js` | Conditional "What's Next" based on `telegramConfigured` / `telegramVerified` |
227
- | `src/onboarding/tui.js` | Add `waitForEnter(message)` helper |
228
- | `src/runtime/telegram-inbound.js` | Add `getMe(botToken)` function |
229
- | `src/cli.js` | Refactor `setupTelegram()` / `telegramWhoami()` to use shared functions from `telegram.js` |
230
-
231
- ## Scope
232
-
233
- - `telegram.js`: ~150 lines
234
- - `build.js` changes: ~15 lines
235
- - `verify.js` changes: ~15 lines
236
- - `telegram-inbound.js` addition: ~10 lines
237
- - `tui.js` addition: ~10 lines
238
- - `cli.js` refactor: ~30 lines changed
239
-
240
- Total: ~200 lines new, ~60 lines modified. One new file.
1
+ # Spec: Telegram Onboarding TUI Phase
2
+
3
+ ## Overview
4
+
5
+ Add Telegram setup as an optional sub-phase inside the Build phase (Phase 3) of the onboarding wizard. Users can connect Telegram during `nemoris init` or defer to `nemoris setup telegram` later.
6
+
7
+ ## Placement
8
+
9
+ Inside `buildFresh()` and `buildShadow()` in `src/onboarding/phases/build.js`, after auth completes:
10
+
11
+ ```
12
+ scaffold → identity → auth → telegram (optional) → done
13
+ ```
14
+
15
+ Not a new top-level phase. Lock file carries `telegramConfigured: boolean` and `telegramVerified: boolean` in wizard state — no phase index change.
16
+
17
+ ---
18
+
19
+ ## Interactive Flow
20
+
21
+ ### Gate
22
+
23
+ ```
24
+ Connect Telegram? (Y/n)
25
+ ```
26
+
27
+ - `n` → skip, `state.telegramConfigured = false`
28
+ - `Y` → enter sub-flow
29
+
30
+ ### Step 1: Bot Token
31
+
32
+ ```
33
+ Telegram Bot Token (from @BotFather): ●●●●●●
34
+ ✓ Token validated @kodi_nemoris_bot
35
+ ```
36
+
37
+ - Input via `promptSecret()`
38
+ - Validate by calling Telegram `getMe` API — new function in `telegram-inbound.js`:
39
+ ```js
40
+ export async function getMe(botToken, { fetchImpl = globalThis.fetch } = {}) {
41
+ const res = await fetchImpl(`https://api.telegram.org/bot${botToken}/getMe`, { method: "GET" });
42
+ const body = await res.json();
43
+ if (!body.ok) return { ok: false, error: body.description || "getMe failed" };
44
+ return { ok: true, username: body.result.username, firstName: body.result.first_name };
45
+ }
46
+ ```
47
+ - On failure: show error, offer re-entry or skip
48
+ - On success: write token to `.env` as `NEMORIS_TELEGRAM_BOT_TOKEN=<value>`
49
+ - macOS only: call `launchctl setenv NEMORIS_TELEGRAM_BOT_TOKEN <value>` so the running daemon picks it up without restart. Guard with `process.platform === "darwin"`.
50
+
51
+ ### Step 2: Chat ID Discovery (daemon-aware)
52
+
53
+ Two paths based on whether the daemon is currently running.
54
+
55
+ #### Detection
56
+
57
+ Check if daemon is running:
58
+ 1. macOS: `launchctl list ai.nanoclaw.daemon` — exit code 0 means loaded
59
+ 2. Fallback: check for `state/daemon.pid` or similar runtime indicator
60
+
61
+ #### Path A: Daemon is running
62
+
63
+ ```
64
+ Daemon is running. Send any message to @kodi_nemoris_bot on Telegram...
65
+ Waiting for your message...
66
+ ✓ Found you chat_id: 7781763328, @leeUsername
67
+ ```
68
+
69
+ - Poll the SQLite state store (`state/active.db`) for the first interactive job row where `source = 'telegram'`
70
+ - Query: `SELECT chat_id FROM interactive_jobs WHERE source = 'telegram' ORDER BY created_at DESC LIMIT 1`
71
+ - Poll interval: 2s, timeout: 120s
72
+ - On timeout: offer to enter chat_id manually or retry
73
+
74
+ This avoids any conflict with the daemon's getUpdates polling connection. No `deleteWebhook`, no `getUpdates` — the daemon handles message ingestion, the wizard just reads the result.
75
+
76
+ #### Path B: Daemon is not running
77
+
78
+ ```
79
+ Send any message to @kodi_nemoris_bot on Telegram, then press Enter...
80
+ ✓ Found you chat_id: 7781763328, @leeUsername
81
+ ```
82
+
83
+ - Reuses existing `whoami(botToken)` function (deleteWebhook → getUpdates → extract chat)
84
+ - No conflict because no daemon is competing for getUpdates
85
+ - On failure (no messages found): prompt user to send a message first and press Enter to retry
86
+
87
+ #### Both paths
88
+
89
+ - Auto-fill `operator_chat_id` and `authorized_chat_ids[0]` with discovered chat_id
90
+ - If chat_id was already in `config/runtime.toml`, show it and ask to confirm or replace
91
+
92
+ ### Step 3: Delivery Mode
93
+
94
+ ```
95
+ Delivery mode:
96
+ [1] Long polling (recommended — no tunnel needed)
97
+ [2] Webhook (requires public URL)
98
+ ```
99
+
100
+ - Uses `select()` from tui.js
101
+ - **Polling** (default): `polling_mode = true`, `webhook_url = ""`
102
+ - **Webhook**: prompts for public URL, validates format, calls `registerWebhook(token, url)`. On failure: show error, offer retry or fall back to polling.
103
+
104
+ ### Step 4: Write Config
105
+
106
+ Writes the `[telegram]` section to `config/runtime.toml`:
107
+
108
+ ```toml
109
+ [telegram]
110
+ bot_token_env = "NEMORIS_TELEGRAM_BOT_TOKEN"
111
+ polling_mode = true
112
+ webhook_url = ""
113
+ operator_chat_id = "7781763328"
114
+ authorized_chat_ids = ["7781763328"]
115
+ default_agent = "kodi"
116
+ ```
117
+
118
+ - `default_agent` is set to `state.agentId` from the Build phase
119
+ - Preserves all other sections in runtime.toml (read → patch → write)
120
+ - Uses TOML serialization consistent with existing config writer patterns
121
+
122
+ ### Step 5: Smoke Test (with failure handling)
123
+
124
+ ```
125
+ Sending test message...
126
+ ✓ Telegram connected "Hello from Nemoris"
127
+ ```
128
+
129
+ Sends a test message via `sendMessage` to the discovered `chat_id` using the validated token.
130
+
131
+ #### Failure path
132
+
133
+ ```
134
+ Sending test message...
135
+ ✗ Delivery failed: 403 Forbidden — bot was blocked by the user
136
+
137
+ [1] Retry
138
+ [2] Skip — finish setup without verification
139
+ [3] Re-enter bot token
140
+
141
+ Choice:
142
+ ```
143
+
144
+ - **Retry**: loops back to sendMessage
145
+ - **Skip**: sets `state.telegramConfigured = true`, `state.telegramVerified = false`. Verify phase will show a warning: `"⚠ Telegram configured but not verified — run nemoris setup telegram to test"`
146
+ - **Re-enter token**: loops back to Step 1
147
+
148
+ The wizard does NOT exit cleanly on a failed smoke test. It always gives the user a choice.
149
+
150
+ ---
151
+
152
+ ## Non-Interactive Mode
153
+
154
+ Env-var driven, zero prompts. Suitable for Docker/CI.
155
+
156
+ | Env Var | Required | Default | Purpose |
157
+ |---------|----------|---------|---------|
158
+ | `NEMORIS_TELEGRAM_BOT_TOKEN` | Yes (to enable) | — | If set, Telegram phase runs |
159
+ | `NEMORIS_TELEGRAM_CHAT_ID` | No | — | Skips whoami/state-store probe |
160
+ | `NEMORIS_TELEGRAM_MODE` | No | `polling` | `polling` or `webhook` |
161
+ | `NEMORIS_TELEGRAM_WEBHOOK_URL` | If mode=webhook | — | Public URL for webhook registration |
162
+ | `NEMORIS_SKIP_TELEGRAM` | No | — | Set to `true` to skip entirely |
163
+
164
+ If `NEMORIS_TELEGRAM_BOT_TOKEN` is not set, Telegram phase is silently skipped. No error.
165
+
166
+ If `NEMORIS_TELEGRAM_CHAT_ID` is not set and daemon is not running, the whoami probe runs automatically (no stdin needed). If it returns null, Telegram is configured without a chat_id — the user must set it manually later.
167
+
168
+ ---
169
+
170
+ ## Verify Phase Changes
171
+
172
+ ### Telegram configured + verified
173
+
174
+ ```
175
+ What's next:
176
+
177
+ nemoris start start the daemon
178
+ nemoris status see your agent's state
179
+ Message @kodi_nemoris_bot talk to your agent via Telegram
180
+ ```
181
+
182
+ ### Telegram configured + NOT verified
183
+
184
+ ```
185
+ ⚠ Telegram configured but not verified
186
+
187
+ What's next:
188
+
189
+ nemoris setup telegram verify Telegram connection
190
+ nemoris start start the daemon
191
+ ```
192
+
193
+ ### Telegram not configured
194
+
195
+ ```
196
+ What's next:
197
+
198
+ nemoris start start the daemon
199
+ nemoris status see your agent's state
200
+ nemoris setup telegram connect Telegram later
201
+ ```
202
+
203
+ ---
204
+
205
+ ## CLI Refactor
206
+
207
+ Extract shared logic from `setupTelegram()` and `telegramWhoami()` in `cli.js` so both the wizard phase and the standalone commands use the same code paths:
208
+
209
+ - `validateBotToken(token)` → calls `getMe`, returns `{ ok, username, error }`
210
+ - `discoverChatId(token, { daemonRunning, stateStore, webhookUrl })` → daemon-aware chat_id discovery
211
+ - `writeTelegramConfig(installDir, config)` → patches `[telegram]` section in runtime.toml
212
+ - `sendTestMessage(token, chatId)` → smoke test delivery
213
+
214
+ These live in `src/onboarding/phases/telegram.js` and are imported by `cli.js`.
215
+
216
+ The standalone `nemoris setup telegram` command becomes a thin wrapper that calls the same phase functions with an interactive readline session, outside the wizard context.
217
+
218
+ ---
219
+
220
+ ## Files
221
+
222
+ | File | Change |
223
+ |------|--------|
224
+ | `src/onboarding/phases/telegram.js` | **New** — Telegram sub-phase: token validation, daemon-aware chat_id discovery, mode selection, config writing, smoke test with failure handling |
225
+ | `src/onboarding/phases/build.js` | Import and call `runTelegramPhase()` after auth in both `buildFresh()` and `buildShadow()` |
226
+ | `src/onboarding/phases/verify.js` | Conditional "What's Next" based on `telegramConfigured` / `telegramVerified` |
227
+ | `src/onboarding/tui.js` | Add `waitForEnter(message)` helper |
228
+ | `src/runtime/telegram-inbound.js` | Add `getMe(botToken)` function |
229
+ | `src/cli.js` | Refactor `setupTelegram()` / `telegramWhoami()` to use shared functions from `telegram.js` |
230
+
231
+ ## Scope
232
+
233
+ - `telegram.js`: ~150 lines
234
+ - `build.js` changes: ~15 lines
235
+ - `verify.js` changes: ~15 lines
236
+ - `telegram-inbound.js` addition: ~10 lines
237
+ - `tui.js` addition: ~10 lines
238
+ - `cli.js` refactor: ~30 lines changed
239
+
240
+ Total: ~200 lines new, ~60 lines modified. One new file.
@@ -1,15 +1,15 @@
1
- [skill]
2
- id = "workspace_monitor"
3
- description = "Monitor workspace directory for changes and summarise"
4
- agent_scope = ["ops", "main"]
5
-
6
- [skill.context]
7
- prompt = "You are monitoring a workspace directory for changes. Compare current state against last known checkpoint. Report: new files, modified files, deleted files, key content changes. Keep summary under 200 words."
8
-
9
- [skill.tools]
10
- required = ["read_file", "list_dir"]
11
- optional = ["shell_exec"]
12
-
13
- [skill.budget]
14
- max_tokens = 4096
15
- max_tool_calls = 10
1
+ [skill]
2
+ id = "workspace_monitor"
3
+ description = "Monitor workspace directory for changes and summarise"
4
+ agent_scope = ["ops", "main"]
5
+
6
+ [skill.context]
7
+ prompt = "You are monitoring a workspace directory for changes. Compare current state against last known checkpoint. Report: new files, modified files, deleted files, key content changes. Keep summary under 200 words."
8
+
9
+ [skill.tools]
10
+ required = ["read_file", "list_dir"]
11
+ optional = ["shell_exec"]
12
+
13
+ [skill.budget]
14
+ max_tokens = 4096
15
+ max_tool_calls = 10
@@ -1,42 +1,42 @@
1
- [defaults]
2
- enabled = true
3
- default_route_mode = "primary"
4
-
5
- [rules.local_reporting]
6
- description = "Keep bounded reporting jobs on the stronger local reporting lane."
7
- target_lane = "local_report"
8
- match_task_types = ["workspace_health", "memory_rollup"]
9
- priority = 90
10
-
11
- [rules.coding_work]
12
- description = "Escalate code-edit and repo-fix work to a stronger coding lane before model resolution."
13
- target_lane = "job_heavy"
14
- route_mode = "primary"
15
- match_keywords = ["code", "coding", "repo", "repository", "bug", "fix", "patch", "refactor", "test", "file fix"]
16
- match_task_types = ["code_fix", "repo_maintenance", "code_review", "test_repair"]
17
- require_tools = ["apply_patch"]
18
- priority = 100
19
-
20
- [rules.memory_heavy_reporting]
21
- description = "Use the report lane for memory-heavy summaries and handoff work."
22
- target_lane = "local_report"
23
- route_mode = "primary"
24
- match_keywords = ["summary", "rollup", "handoff", "memory", "backlog", "projects"]
25
- match_task_types = ["memory_rollup", "handoff_summary", "project_rollup"]
26
- priority = 80
27
-
28
- [rules.user_visible_handoff]
29
- description = "Promote cheap local maintenance work to the stronger report lane when it owes the user a visible handoff or pingback."
30
- target_lane = "local_report"
31
- route_mode = "primary"
32
- match_task_types = ["heartbeat", "heartbeat_check", "light_maintenance", "classification", "triage"]
33
- require_pingback = true
34
- priority = 70
35
-
36
- [rules.cheap_maintenance]
37
- description = "Keep low-risk heartbeat and maintenance loops on the cheap local lane."
38
- target_lane = "local_cheap"
39
- route_mode = "primary"
40
- match_keywords = ["heartbeat", "status", "maintenance", "triage", "check"]
41
- match_task_types = ["heartbeat", "heartbeat_check", "light_maintenance", "classification", "triage"]
42
- priority = 40
1
+ [defaults]
2
+ enabled = true
3
+ default_route_mode = "primary"
4
+
5
+ [rules.local_reporting]
6
+ description = "Keep bounded reporting jobs on the stronger local reporting lane."
7
+ target_lane = "local_report"
8
+ match_task_types = ["workspace_health", "memory_rollup"]
9
+ priority = 90
10
+
11
+ [rules.coding_work]
12
+ description = "Escalate code-edit and repo-fix work to a stronger coding lane before model resolution."
13
+ target_lane = "job_heavy"
14
+ route_mode = "primary"
15
+ match_keywords = ["code", "coding", "repo", "repository", "bug", "fix", "patch", "refactor", "test", "file fix"]
16
+ match_task_types = ["code_fix", "repo_maintenance", "code_review", "test_repair"]
17
+ require_tools = ["apply_patch"]
18
+ priority = 100
19
+
20
+ [rules.memory_heavy_reporting]
21
+ description = "Use the report lane for memory-heavy summaries and handoff work."
22
+ target_lane = "local_report"
23
+ route_mode = "primary"
24
+ match_keywords = ["summary", "rollup", "handoff", "memory", "backlog", "projects"]
25
+ match_task_types = ["memory_rollup", "handoff_summary", "project_rollup"]
26
+ priority = 80
27
+
28
+ [rules.user_visible_handoff]
29
+ description = "Promote cheap local maintenance work to the stronger report lane when it owes the user a visible handoff or pingback."
30
+ target_lane = "local_report"
31
+ route_mode = "primary"
32
+ match_task_types = ["heartbeat", "heartbeat_check", "light_maintenance", "classification", "triage"]
33
+ require_pingback = true
34
+ priority = 70
35
+
36
+ [rules.cheap_maintenance]
37
+ description = "Keep low-risk heartbeat and maintenance loops on the cheap local lane."
38
+ target_lane = "local_cheap"
39
+ route_mode = "primary"
40
+ match_keywords = ["heartbeat", "status", "maintenance", "triage", "check"]
41
+ match_task_types = ["heartbeat", "heartbeat_check", "light_maintenance", "classification", "triage"]
42
+ priority = 40