tomo-ai 0.8.3 → 0.8.5

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 (164) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +22 -6
  3. package/defaults/AGENT.md +13 -0
  4. package/defaults/CONTINUITY.md +1 -1
  5. package/defaults/skills/system/CONFIG.md +4 -3
  6. package/defaults/skills/system/SKILL.md +1 -1
  7. package/dist/agent/audience.d.ts +19 -0
  8. package/dist/agent/audience.d.ts.map +1 -0
  9. package/dist/agent/audience.js +28 -0
  10. package/dist/agent/audience.js.map +1 -0
  11. package/dist/agent/claude-login.d.ts +32 -0
  12. package/dist/agent/claude-login.d.ts.map +1 -0
  13. package/dist/agent/claude-login.js +176 -0
  14. package/dist/agent/claude-login.js.map +1 -0
  15. package/dist/agent/commands.d.ts +41 -0
  16. package/dist/agent/commands.d.ts.map +1 -0
  17. package/dist/agent/commands.js +338 -0
  18. package/dist/agent/commands.js.map +1 -0
  19. package/dist/agent/inbound-batcher.d.ts +54 -0
  20. package/dist/agent/inbound-batcher.d.ts.map +1 -0
  21. package/dist/agent/inbound-batcher.js +148 -0
  22. package/dist/agent/inbound-batcher.js.map +1 -0
  23. package/dist/agent/live-session.d.ts +8 -5
  24. package/dist/agent/live-session.d.ts.map +1 -1
  25. package/dist/agent/live-session.js +14 -21
  26. package/dist/agent/live-session.js.map +1 -1
  27. package/dist/agent/sdk-options.d.ts.map +1 -1
  28. package/dist/agent/sdk-options.js +12 -3
  29. package/dist/agent/sdk-options.js.map +1 -1
  30. package/dist/agent.d.ts +56 -28
  31. package/dist/agent.d.ts.map +1 -1
  32. package/dist/agent.js +465 -363
  33. package/dist/agent.js.map +1 -1
  34. package/dist/auth.d.ts +19 -0
  35. package/dist/auth.d.ts.map +1 -0
  36. package/dist/auth.js +56 -0
  37. package/dist/auth.js.map +1 -0
  38. package/dist/channels/imessage-dedupe.d.ts +25 -0
  39. package/dist/channels/imessage-dedupe.d.ts.map +1 -0
  40. package/dist/channels/imessage-dedupe.js +85 -0
  41. package/dist/channels/imessage-dedupe.js.map +1 -0
  42. package/dist/channels/imessage.d.ts +3 -0
  43. package/dist/channels/imessage.d.ts.map +1 -1
  44. package/dist/channels/imessage.js +41 -12
  45. package/dist/channels/imessage.js.map +1 -1
  46. package/dist/channels/telegram.d.ts.map +1 -1
  47. package/dist/channels/telegram.js +7 -2
  48. package/dist/channels/telegram.js.map +1 -1
  49. package/dist/channels/types.d.ts +6 -1
  50. package/dist/channels/types.d.ts.map +1 -1
  51. package/dist/cli/backup-workspace.d.ts +9 -0
  52. package/dist/cli/backup-workspace.d.ts.map +1 -0
  53. package/dist/cli/backup-workspace.js +76 -0
  54. package/dist/cli/backup-workspace.js.map +1 -0
  55. package/dist/cli/backup.d.ts.map +1 -1
  56. package/dist/cli/backup.js +11 -39
  57. package/dist/cli/backup.js.map +1 -1
  58. package/dist/cli/config/auth.d.ts +2 -0
  59. package/dist/cli/config/auth.d.ts.map +1 -0
  60. package/dist/cli/config/auth.js +60 -0
  61. package/dist/cli/config/auth.js.map +1 -0
  62. package/dist/cli/config/identities.js +3 -3
  63. package/dist/cli/config/identities.js.map +1 -1
  64. package/dist/cli/config/index.d.ts.map +1 -1
  65. package/dist/cli/config/index.js +4 -0
  66. package/dist/cli/config/index.js.map +1 -1
  67. package/dist/cli/config/sessions.js +2 -2
  68. package/dist/cli/config/sessions.js.map +1 -1
  69. package/dist/cli/config/shared.d.ts +1 -0
  70. package/dist/cli/config/shared.d.ts.map +1 -1
  71. package/dist/cli/config/shared.js +10 -8
  72. package/dist/cli/config/shared.js.map +1 -1
  73. package/dist/cli/daemon.js +4 -4
  74. package/dist/cli/daemon.js.map +1 -1
  75. package/dist/cli/init.d.ts.map +1 -1
  76. package/dist/cli/init.js +19 -19
  77. package/dist/cli/init.js.map +1 -1
  78. package/dist/cli/lcm.d.ts.map +1 -1
  79. package/dist/cli/lcm.js +28 -27
  80. package/dist/cli/lcm.js.map +1 -1
  81. package/dist/cli/service.d.ts.map +1 -1
  82. package/dist/cli/service.js +2 -2
  83. package/dist/cli/service.js.map +1 -1
  84. package/dist/cli/sessions.d.ts.map +1 -1
  85. package/dist/cli/sessions.js +14 -13
  86. package/dist/cli/sessions.js.map +1 -1
  87. package/dist/cli/start.js +16 -11
  88. package/dist/cli/start.js.map +1 -1
  89. package/dist/cli/uninstall.d.ts.map +1 -1
  90. package/dist/cli/uninstall.js +2 -4
  91. package/dist/cli/uninstall.js.map +1 -1
  92. package/dist/config.d.ts +19 -5
  93. package/dist/config.d.ts.map +1 -1
  94. package/dist/config.js +52 -11
  95. package/dist/config.js.map +1 -1
  96. package/dist/continuity-defaults.d.ts +4 -0
  97. package/dist/continuity-defaults.d.ts.map +1 -0
  98. package/dist/continuity-defaults.js +4 -0
  99. package/dist/continuity-defaults.js.map +1 -0
  100. package/dist/continuity.d.ts +2 -0
  101. package/dist/continuity.d.ts.map +1 -1
  102. package/dist/continuity.js +19 -3
  103. package/dist/continuity.js.map +1 -1
  104. package/dist/fs-utils.d.ts +6 -2
  105. package/dist/fs-utils.d.ts.map +1 -1
  106. package/dist/fs-utils.js +5 -3
  107. package/dist/fs-utils.js.map +1 -1
  108. package/dist/lcm/blocks.d.ts +3 -3
  109. package/dist/lcm/blocks.d.ts.map +1 -1
  110. package/dist/lcm/blocks.js +8 -8
  111. package/dist/lcm/blocks.js.map +1 -1
  112. package/dist/lcm/compact.d.ts +4 -2
  113. package/dist/lcm/compact.d.ts.map +1 -1
  114. package/dist/lcm/compact.js +7 -7
  115. package/dist/lcm/compact.js.map +1 -1
  116. package/dist/lcm/prune-tools.d.ts +2 -0
  117. package/dist/lcm/prune-tools.d.ts.map +1 -1
  118. package/dist/lcm/prune-tools.js +2 -2
  119. package/dist/lcm/prune-tools.js.map +1 -1
  120. package/dist/lcm/runner.d.ts.map +1 -1
  121. package/dist/lcm/runner.js +6 -2
  122. package/dist/lcm/runner.js.map +1 -1
  123. package/dist/lcm/stats.d.ts +2 -2
  124. package/dist/lcm/stats.d.ts.map +1 -1
  125. package/dist/lcm/stats.js +4 -4
  126. package/dist/lcm/stats.js.map +1 -1
  127. package/dist/mcp/internal-server.d.ts +5 -4
  128. package/dist/mcp/internal-server.d.ts.map +1 -1
  129. package/dist/mcp/internal-server.js +8 -5
  130. package/dist/mcp/internal-server.js.map +1 -1
  131. package/dist/mcp/pet-store.d.ts.map +1 -1
  132. package/dist/mcp/pet-store.js +5 -4
  133. package/dist/mcp/pet-store.js.map +1 -1
  134. package/dist/mcp/pet-tools.d.ts.map +1 -1
  135. package/dist/mcp/pet-tools.js +3 -1
  136. package/dist/mcp/pet-tools.js.map +1 -1
  137. package/dist/router.d.ts +28 -1
  138. package/dist/router.d.ts.map +1 -1
  139. package/dist/router.js +85 -2
  140. package/dist/router.js.map +1 -1
  141. package/dist/runtime-paths.d.ts +29 -0
  142. package/dist/runtime-paths.d.ts.map +1 -0
  143. package/dist/runtime-paths.js +33 -0
  144. package/dist/runtime-paths.js.map +1 -0
  145. package/dist/sessions/index.d.ts +1 -1
  146. package/dist/sessions/index.d.ts.map +1 -1
  147. package/dist/sessions/index.js +1 -1
  148. package/dist/sessions/index.js.map +1 -1
  149. package/dist/sessions/repair.d.ts +1 -1
  150. package/dist/sessions/repair.d.ts.map +1 -1
  151. package/dist/sessions/repair.js +2 -2
  152. package/dist/sessions/repair.js.map +1 -1
  153. package/dist/sessions/store.d.ts +27 -5
  154. package/dist/sessions/store.d.ts.map +1 -1
  155. package/dist/sessions/store.js +167 -23
  156. package/dist/sessions/store.js.map +1 -1
  157. package/dist/sessions/summon-store.d.ts +36 -0
  158. package/dist/sessions/summon-store.d.ts.map +1 -0
  159. package/dist/sessions/summon-store.js +89 -0
  160. package/dist/sessions/summon-store.js.map +1 -0
  161. package/dist/workspace/index.d.ts.map +1 -1
  162. package/dist/workspace/index.js +2 -3
  163. package/dist/workspace/index.js.map +1 -1
  164. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,10 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.5 (2026-06-26)
4
+
5
+ ### Features
6
+
7
+ - **API key authentication config** (#158). Adds explicit API-key auth configuration and prevents the Anthropic API key from leaking to custom gateways/base URLs.
8
+ - **Claude login from the owner DM** (#165). The owner can authenticate Claude directly through a DM command instead of the CLI only, with clearer messaging when login verification fails.
9
+ - **Custom workspace for SDK sessions** (#175). SDK sessions now honor a configured custom workspace path. Runtime path resolution was consolidated into a new `runtime-paths` module.
10
+
11
+ ### Bug fixes
12
+
13
+ - **iMessage inbound webhook dedupe is now persisted** (#163). Webhook dedupe state survives daemon restarts instead of living only in memory, so restarts no longer risk re-processing recently seen messages.
14
+ - **Direct-send note attribution** (#148). Summoned-group direct sends are attributed to the correct caller, attribution is caller-aware, and pending notes are capped.
15
+ - **Suppress group LCM output** (#164). Lifecycle-management chatter no longer leaks into group chats.
16
+
17
+ ### Other
18
+
19
+ - Test against Node 26 in CI and align Node typings (#171).
20
+ - Extract `backup-workspace` out of the backup command.
21
+ - Bump `@anthropic-ai/claude-agent-sdk` `0.3.181` → `0.3.195` (#173, #169, #176).
22
+ - Bump `grammy` `1.44.0` (#168), `@clack/prompts` `1.6.0` (#170), `eslint` `10.6.0`, `actions/checkout` v7 (#166), and the dev-dependencies group (#174).
23
+
24
+ ## 0.8.4 (2026-06-19)
25
+
26
+ ### Features
27
+
28
+ - **`/summon` — pull your main session into a group chat.** A configured identity's owner can `/summon` in an allowlisted group so the group's messages run on their unified `dm:` session (full personal context) until `/dismiss` or after `summonExpiryMinutes` of group inactivity (default 60; `0` disables, `TOMO_SUMMON_EXPIRY_MINUTES` to override). Turn output stays in the owner's private DM — group-facing replies require an explicit `send_message` direct-mode call — so private context never auto-posts to the group. Summon state is persisted across daemon restarts, lapses lazily, and an audience-switch note is injected when a `dm:` session's inbound audience flips between private DM and a summoned group.
29
+ - **Configurable continuity heartbeat.** The continuity heartbeat interval is no longer hardcoded and can be tuned via config.
30
+ - **`pet_status` chat command** (#157). Surface the virtual pet's current state directly from chat.
31
+ - **Message steering on by default** (#150). `steering` now defaults to on (`TOMO_STEERING=false` to opt out), and direct sends were cleaned up alongside it.
32
+
33
+ ### Bug fixes
34
+
35
+ - **`/summon` no longer emits a spurious "expired — handed back" notice on re-summon** (#160). The "already summoned?" guard read (also used by `/status` and `/dismiss`) lazily expired a lapsed summon and fired the group-facing handback notice, so re-summoning a group whose previous summon had gone stale posted a contradictory pair of messages. The handback notice now fires only on the real group-message routing path; guard reads still clear the `dm:` session's stale "summoned" context, but silently.
36
+ - **Session timeout recovery and LCM typing fixes** (#155).
37
+ - **iMessage typing-indicator lifecycle fix** (#154). The typing indicator is started and cleared correctly across the message lifecycle.
38
+ - **`send_message` MEDIA/STICKER tag parsing in direct mode** (#147). Attachment tags are now parsed in direct sends, with regression coverage.
39
+ - **Virtual pet hunger pacing tuned** (#149).
40
+
41
+ ### Other
42
+
43
+ - Internal refactor: extracted inbound batching and chat-command handling out of the `Agent` class (#145).
44
+ - Bump `@anthropic-ai/claude-agent-sdk` `0.3.168` → `0.3.177` → `0.3.181` (#159, #153).
45
+ - Bump dev-dependencies group (#152).
46
+
3
47
  ## 0.8.3 (2026-06-11)
4
48
 
5
49
  ### Features
6
50
 
7
- - **Experimental message steering** (#142). New `steering` config (`TOMO_STEERING=true`, default off) lets user messages that arrive during an in-flight tool-using turn bypass the per-session queue and inject at the next tool-call boundary. If the current turn has no boundary left, the message runs as the next follow-up turn. Cron, continuity, and other system-originated turns continue to queue normally.
51
+ - **Message steering** (#142). New optional `steering` config (`TOMO_STEERING=false` to opt out, default on) lets user messages that arrive during an in-flight tool-using turn bypass the per-session queue and inject at the next tool-call boundary. If the current turn has no boundary left, the message runs as the next follow-up turn. Cron, continuity, and other system-originated turns continue to queue normally.
8
52
 
9
53
  ### Bug fixes
10
54
 
package/README.md CHANGED
@@ -32,7 +32,7 @@ That's it. Open Telegram and message your bot.
32
32
  ## Requirements
33
33
 
34
34
  - Node.js 22.12+
35
- - [Claude Code](https://claude.com/claude-code) installed. Authenticate Claude Code for direct Claude models, or configure a LiteLLM gateway for non-Claude backends.
35
+ - [Claude Code](https://claude.com/claude-code) installed. Direct Claude models can use a Claude subscription or an Anthropic API key; LiteLLM gateways support other backends.
36
36
  - At least one channel:
37
37
  - **Telegram** — bot token from [@BotFather](https://t.me/BotFather)
38
38
  - **iMessage** — [BlueBubbles](https://bluebubbles.app) server running on a Mac with iMessage signed in
@@ -41,7 +41,7 @@ That's it. Open Telegram and message your bot.
41
41
 
42
42
  ```bash
43
43
  tomo init # First-time setup
44
- tomo config # Interactive settings (model, LiteLLM, channels, identities, groups)
44
+ tomo config # Interactive settings (authentication, model, LiteLLM, channels, identities, groups)
45
45
  tomo start # Start in background (daemon)
46
46
  tomo start -f # Start in foreground (for dev)
47
47
  tomo stop # Stop the daemon
@@ -60,7 +60,9 @@ tomo sessions clear # Reset all sessions
60
60
  | `/new` | Start a new conversation (resets session) |
61
61
  | `/model` | Switch model (Claude aliases or LiteLLM `provider/model` names) |
62
62
  | `/restore` | Restore `config.json` from `config.json.bak` and restart |
63
+ | `/login` | Refresh Claude login from a configured owner's private DM |
63
64
  | `/status` | Show session info (model, channel, message count) |
65
+ | `/pet` | Check Tomo's pet's mood, growth stage, and stats |
64
66
 
65
67
  ## Features
66
68
 
@@ -202,6 +204,10 @@ Run `tomo config` for interactive setup, or edit `~/.tomo/config.json` directly:
202
204
 
203
205
  ```json
204
206
  {
207
+ "auth": {
208
+ "method": "api-key",
209
+ "apiKey": "sk-ant-..."
210
+ },
205
211
  "channels": {
206
212
  "telegram": {
207
213
  "token": "your-bot-token",
@@ -225,8 +231,8 @@ Run `tomo config` for interactive setup, or edit `~/.tomo/config.json` directly:
225
231
  },
226
232
  "maxTurns": 50,
227
233
  "saveInboundImages": true,
228
- "steering": false,
229
234
  "continuity": true,
235
+ "continuityIntervalMinutes": 55,
230
236
  "continuityScript": {
231
237
  "path": "~/bin/tomo-continuity.sh",
232
238
  "timeoutMs": 30000,
@@ -245,25 +251,35 @@ Environment variables override config file values:
245
251
 
246
252
  | Variable | Description |
247
253
  |----------|-------------|
254
+ | `ANTHROPIC_API_KEY` | Use Anthropic API-key authentication; overrides the authentication stored in `config.json` |
248
255
  | `TELEGRAM_BOT_TOKEN` | Override Telegram token |
249
256
  | `IMESSAGE_URL` | Override BlueBubbles URL |
257
+ | `IMESSAGE_TYPING_START_DELAY_MS` | Delay before showing iMessage typing for ordinary turns (default: `1200`) |
258
+ | `IMESSAGE_PASSIVE_TYPING_START_DELAY_MS` | Delay before showing iMessage typing for passive iMessage group turns (default: `4000`) |
250
259
  | `CLAUDE_MODEL` | Override model |
251
260
  | `TOMO_LITELLM_BASE_URL` | Route Claude Agent SDK model calls through a LiteLLM proxy |
252
261
  | `TOMO_LITELLM_API_KEY` | API key sent to the LiteLLM proxy as `ANTHROPIC_API_KEY` |
253
262
  | `TOMO_LITELLM_MODE` | Optional LiteLLM mode: `anthropic-compatible` or `chatgpt-subscription` |
254
263
  | `TOMO_WORKSPACE` | Override workspace directory |
255
264
  | `TOMO_MAX_TURNS` | Override per-turn tool-use ceiling (default: `50`) |
256
- | `TOMO_STEERING` | Set `true` to steer mid-turn messages into the in-flight turn (experimental, default: `false`) |
265
+ | `TOMO_STEERING` | Override message steering. Defaults to `true`; set `false` to keep mid-turn messages queued. |
266
+ | `TOMO_CONTINUITY_INTERVAL_MINUTES` | Override scheduled continuity heartbeat interval (default: `55`, minimum: `1`) |
257
267
  | `TOMO_CONTINUITY_SCRIPT` | Override the optional continuity script path |
258
268
  | `TOMO_CONTINUITY_SCRIPT_TIMEOUT_MS` | Override continuity script timeout (default: `30000`) |
259
269
  | `TOMO_CONTINUITY_SCRIPT_MAX_OUTPUT_CHARS` | Override continuity script stdout/stderr cap (default: `8000`) |
260
270
  | `LOG_LEVEL` | Log level (default: `debug`) |
261
271
 
272
+ ### Anthropic Authentication
273
+
274
+ Direct Claude models use your existing Claude Code subscription login by default. To use Anthropic API billing instead, run `tomo config`, choose **Anthropic authentication**, and enter an API key. Tomo stores the selected method under `auth` in `~/.tomo/config.json` and passes the key only to direct Claude Agent SDK child processes. `ANTHROPIC_API_KEY` remains supported and takes precedence over the saved setting.
275
+
276
+ Because the config contains channel credentials and may now contain an Anthropic API key, Tomo writes both `config.json` and `config.json.bak` with owner-only (`0600`) permissions.
277
+
262
278
  `continuityScript` can also be a simple path string, e.g. `"continuityScript": "~/bin/tomo-continuity.sh"`. Relative paths resolve under `~/.tomo`; the script runs once per scheduled heartbeat and manual `tomo continuity` trigger, and its stdout/stderr or failure status is appended to the normal continuity prompt.
263
279
 
264
- ### Steering (experimental)
280
+ ### Steering
265
281
 
266
- By default, a message you send while Tomo is mid-task waits in a queue until the current turn finishes. With `"steering": true`, messages sent during a long tool-using turn are injected into it at the next tool-call boundary — so "stop", "wait", or extra context reaches the model immediately instead of after the task completes. If the turn has no tool calls left, the message runs as its own follow-up turn right after. iMessage fragment settling still applies before injection; system-originated turns (cron, continuity) keep their normal queued behavior. This relies on the Claude Agent SDK's mid-turn message queue and is off by default.
282
+ By default, messages you send while Tomo is mid-task are steered into the in-flight turn at the next tool-call boundary — so "stop", "wait", or extra context reaches the model immediately instead of waiting for the current turn to finish. If the turn has no tool calls left, the message runs as its own follow-up turn right after. iMessage fragment settling still applies before injection; system-originated turns (cron, continuity) keep their normal queued behavior. Set `"steering": false` or `TOMO_STEERING=false` to keep mid-turn messages queued behind the active turn.
267
283
 
268
284
  ### LiteLLM / ChatGPT Subscription Models
269
285
 
package/defaults/AGENT.md CHANGED
@@ -24,3 +24,16 @@ Call `list_sessions` first if you're unsure which group to address. For normal i
24
24
  You also have `rename_group_chat` for changing the real title of a Telegram or iMessage group. Only use it when the user explicitly asks to rename a group, and pass a group session key from `list_sessions`.
25
25
 
26
26
  You also have `react_to_latest_message` for reacting/tapbacking to the latest inbound message Tomo has seen in a session. Latest-message state is in-memory since startup; if the tool says none is known, wait for a new inbound message. Usually pass the current Session key.
27
+
28
+ ## Summoned Groups
29
+
30
+ The user can run `/summon` in a group chat to temporarily route that group's messages into this session (until `/dismiss`, or automatically after a period of group inactivity). Summoned messages arrive tagged like `[group "Title"] Sender: ...`.
31
+
32
+ How to reply to a summoned group message:
33
+
34
+ - **To the group**: call `send_message` with the group's session key and mode `direct`. Compose the message yourself — you are the session with the context. Never use `delegate` for a summoned group; that wakes the group's own session, which is exactly what summoning bypasses.
35
+ - **To the user privately**: plain text replies in a summoned turn go to the user's private DM, not the group. Use that only for side-notes worth telling them privately; otherwise end the turn with `NO_REPLY`.
36
+ - Match the group's tone and reply like a participant — short, no headers, address people by name when natural. Not every message needs a group reply; stay silent (`NO_REPLY`, no tool call) for chatter that isn't for you.
37
+ - The harness flags audience changes (`[System: audience switched ...]`) whenever consecutive messages hop between the private DM and a group, or between groups. Treat that as a hard reset of tone and privacy — trust the tags over conversational momentum.
38
+
39
+ Everyone in the group can read what you send it. Keep private memories and DM context out of group-facing messages — being summoned shares your judgment and knowledge, not the user's private life.
@@ -4,7 +4,7 @@ Read this file when a continuity heartbeat fires. This is your guide for free ti
4
4
 
5
5
  ## What is this?
6
6
 
7
- The system sends you a heartbeat every ~55 minutes. This is not a user message — it's the harness giving you unstructured time. Use it however you want.
7
+ By default, the system sends you a heartbeat every ~55 minutes. This is not a user message — it's the harness giving you unstructured time. Use it however you want.
8
8
 
9
9
  ## Ideas for free time
10
10
 
@@ -11,6 +11,7 @@ Before direct edits, copy `~/.tomo/config.json` to `~/.tomo/config.json.bak`. Ch
11
11
  "model": "claude-sonnet-4-6[1m]",
12
12
  "city": "Seattle",
13
13
  "continuity": false,
14
+ "continuityIntervalMinutes": 55,
14
15
  "continuityScript": {
15
16
  "path": "~/bin/tomo-continuity.sh",
16
17
  "timeoutMs": 30000,
@@ -49,7 +50,6 @@ Before direct edits, copy `~/.tomo/config.json` to `~/.tomo/config.json.bak`. Ch
49
50
  "apiKey": "sk-tomo-local"
50
51
  },
51
52
  "maxTurns": 50,
52
- "steering": false,
53
53
  "mcpServers": {
54
54
  "github-copilot": {
55
55
  "type": "http",
@@ -80,6 +80,7 @@ Before direct edits, copy `~/.tomo/config.json` to `~/.tomo/config.json.bak`. Ch
80
80
  | `model` | string | Claude model IDs/aliases, or a LiteLLM `provider/model` name such as `chatgpt/gpt-5.5`. Default model for every session. |
81
81
  | `city` | string \| null | Any city name (e.g. `"Seattle"`). Used for weather in continuity pings. `null` or missing = no weather. |
82
82
  | `continuity` | boolean | `true` / `false`. Enables periodic proactive heartbeats. Off by default. |
83
+ | `continuityIntervalMinutes` | number | Optional. Minutes between scheduled continuity heartbeats. Default `55`; minimum `1`. Ignored for manual `tomo continuity` triggers. |
83
84
  | `continuityScript` | string \| object \| null | Optional script run once per scheduled continuity heartbeat and manual `tomo continuity` trigger before the prompt is sent to Tomo. Use a string path (`"~/bin/tomo-continuity.sh"`) or `{ "path": "...", "timeoutMs": 30000, "maxOutputChars": 8000 }`. Relative paths resolve under `~/.tomo`; `~`, `$VAR`, and `${VAR}` expand. The script runs as the Tomo OS user with `TOMO_CONTINUITY=true`; stdout/stderr are appended to the heartbeat prompt and capped separately by `maxOutputChars`. Non-zero exits, missing files, and timeouts are passed to Tomo as script status instead of aborting the heartbeat. |
84
85
  | `groupSecret` | string \| null | Passphrase users send in a group chat to activate Tomo there. `null` disables group chats entirely. |
85
86
  | `channels.telegram.token` | string | BotFather token (`123456:...`). Required to enable the Telegram channel. |
@@ -97,7 +98,7 @@ Before direct edits, copy `~/.tomo/config.json` to `~/.tomo/config.json.bak`. Ch
97
98
  | `litellm.baseUrl` | string | Optional LiteLLM proxy base URL, e.g. `http://localhost:4000`. When set, Tomo still uses Claude Agent SDK but sends SDK model calls to the proxy via `ANTHROPIC_BASE_URL`. |
98
99
  | `litellm.apiKey` | string | LiteLLM proxy key sent as `ANTHROPIC_API_KEY`. This is the proxy key, not an Anthropic key. For ChatGPT subscription models, LiteLLM owns the OAuth device flow and token storage. |
99
100
  | `maxTurns` | number | Max agent turns per single user message (one turn ≈ one tool-use round). Default `50`. Raise if you see "max turns exceeded" on long tool chains. |
100
- | `steering` | boolean | Experimental. Default `false`. When `true`, user messages that arrive while a turn is in flight bypass the per-session queue and are injected at the next tool-call boundary. If the current turn has no boundary left, the message runs as a follow-up turn. Cron, continuity, and other system-originated turns still queue normally. Also settable with `TOMO_STEERING=true`. |
101
+ | `steering` | boolean | Optional. Default `true`. User messages that arrive while a turn is in flight bypass the per-session queue and are injected at the next tool-call boundary. If the current turn has no boundary left, the message runs as a follow-up turn. Cron, continuity, and other system-originated turns still queue normally. Set `false` or `TOMO_STEERING=false` to keep mid-turn messages queued behind the active turn. |
101
102
  | `mcpServers` | object | External MCP servers keyed by server name. Supports stdio (`command`, `args`, `env`), HTTP (`type: "http"`, `url`, `headers`), and SSE (`type: "sse"`, `url`, `headers`). Environment variables like `${HOME}` expand in `url`, `headers`, `env`, and `args`. |
102
103
  | `mcpServers.<name>.oauth` | object | Optional harness-managed OAuth for HTTP/SSE MCP servers. Supports `authorizationServer` (optional; omitted = discover from the MCP server), `clientId` (optional if dynamic registration is available), `scopes`, `tokenStoreKey`, `redirectUri`, and `clientName`. Tokens are stored outside agent context in `workspace/secrets/mcp-oauth.json` (`0600`). |
103
104
  | `mcpAllowedTools` | string[] | Auto-allowed external MCP tools. If omitted, Tomo defaults to `mcp__<server>__*` for every configured external server. Set this to narrow tool access, e.g. `["mcp__github__list_issues"]`. |
@@ -134,5 +135,5 @@ Start it with `litellm --config ~/litellm-chatgpt.yaml`, then set Tomo's model t
134
135
  ## Requirements and overrides
135
136
 
136
137
  - **At least one channel must be configured** — either `channels.telegram.token` or `channels.imessage.url`. Startup fails otherwise.
137
- - **Env vars override file values** where they exist: `TELEGRAM_BOT_TOKEN`, `IMESSAGE_URL`, `IMESSAGE_PASSWORD`, `IMESSAGE_WEBHOOK_PORT`, `CLAUDE_MODEL`, `TOMO_LITELLM_BASE_URL`, `TOMO_LITELLM_API_KEY`, `TOMO_LITELLM_MODE`, `TOMO_CITY`, `TOMO_CONTINUITY`, `TOMO_WORKSPACE`, `SESSIONS_DIR`, `HISTORY_LIMIT`, `TOMO_MAX_TURNS`, `TOMO_STEERING`.
138
+ - **Env vars override file values** where they exist: `TELEGRAM_BOT_TOKEN`, `IMESSAGE_URL`, `IMESSAGE_PASSWORD`, `IMESSAGE_WEBHOOK_PORT`, `CLAUDE_MODEL`, `TOMO_LITELLM_BASE_URL`, `TOMO_LITELLM_API_KEY`, `TOMO_LITELLM_MODE`, `TOMO_CITY`, `TOMO_CONTINUITY`, `TOMO_CONTINUITY_INTERVAL_MINUTES`, `TOMO_WORKSPACE`, `SESSIONS_DIR`, `HISTORY_LIMIT`, `TOMO_MAX_TURNS`, `TOMO_STEERING`.
138
139
  - `workspaceDir`, `sessionsDir`, `historyLimit` are env-only — they're not read from the JSON file.
@@ -73,7 +73,7 @@ Before changing `~/.tomo/config.json` directly, copy the current file to `~/.tom
73
73
  Responses stream to Telegram in real-time — messages update every 1.5s as tokens arrive.
74
74
 
75
75
  ### Message steering
76
- If `steering: true` is set in `~/.tomo/config.json`, user messages that arrive during a long tool-using turn are injected into the in-flight turn at the next tool-call boundary instead of waiting behind it. Use this to treat mid-task corrections like "stop", "wait", or added context as immediately relevant. If there is no remaining tool boundary, the message runs as the next follow-up turn. Cron, continuity, and other system-originated turns still queue normally.
76
+ By default, user messages that arrive during a long tool-using turn are injected into the in-flight turn at the next tool-call boundary instead of waiting behind it. Use this to treat mid-task corrections like "stop", "wait", or added context as immediately relevant. If there is no remaining tool boundary, the message runs as the next follow-up turn. Cron, continuity, and other system-originated turns still queue normally. Set `steering: false` in `~/.tomo/config.json` to keep mid-turn user messages queued.
77
77
 
78
78
  ### MEDIA: tag
79
79
  To send an image/file to the user, include `MEDIA:/path/to/file.png` in your response. The harness strips it from text and sends the file. Text before/after becomes the caption.
@@ -0,0 +1,19 @@
1
+ /** Audience identifier for an inbound message on a unified dm: session —
2
+ * "dm" for private messages, the raw "<channel>:<chatId>" key for (summoned)
3
+ * group messages. */
4
+ export declare function audienceOf(channelName: string, message: {
5
+ isGroup?: boolean;
6
+ chatId: string;
7
+ }): string;
8
+ /**
9
+ * Note injected when a dm session's inbound audience changes (private DM ↔
10
+ * summoned group, or between two summoned groups). The per-message
11
+ * [group ...] tags say where each message came FROM; this says the audience
12
+ * CHANGED — the moment when tone or private context is most likely to be
13
+ * carried across by mistake. Returns "" when nothing changed.
14
+ *
15
+ * `audiences` lists this turn's inbound audiences in order (one entry for a
16
+ * single message; several for a coalesced batch, which can mix audiences).
17
+ */
18
+ export declare function audienceSwitchNote(prev: string | undefined, audiences: string[], label: (audience: string) => string): string;
19
+ //# sourceMappingURL=audience.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience.d.ts","sourceRoot":"","sources":["../../src/agent/audience.ts"],"names":[],"mappings":"AAAA;;sBAEsB;AACtB,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEtG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,EAAE,EACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAClC,MAAM,CAQR"}
@@ -0,0 +1,28 @@
1
+ /** Audience identifier for an inbound message on a unified dm: session —
2
+ * "dm" for private messages, the raw "<channel>:<chatId>" key for (summoned)
3
+ * group messages. */
4
+ export function audienceOf(channelName, message) {
5
+ return message.isGroup ? `${channelName}:${message.chatId}` : "dm";
6
+ }
7
+ /**
8
+ * Note injected when a dm session's inbound audience changes (private DM ↔
9
+ * summoned group, or between two summoned groups). The per-message
10
+ * [group ...] tags say where each message came FROM; this says the audience
11
+ * CHANGED — the moment when tone or private context is most likely to be
12
+ * carried across by mistake. Returns "" when nothing changed.
13
+ *
14
+ * `audiences` lists this turn's inbound audiences in order (one entry for a
15
+ * single message; several for a coalesced batch, which can mix audiences).
16
+ */
17
+ export function audienceSwitchNote(prev, audiences, label) {
18
+ if (audiences.length === 0)
19
+ return "";
20
+ const distinct = [...new Set(audiences)];
21
+ if (distinct.length > 1) {
22
+ return `[System: audience check — the messages below span ${distinct.map(label).join(" and ")}. Mind each message's tag when replying; group replies only via send_message.]`;
23
+ }
24
+ if (prev === undefined || distinct[0] === prev)
25
+ return "";
26
+ return `[System: audience switched — the previous message in this session was from ${label(prev)}; this one is from ${label(distinct[0])}. Re-anchor tone and privacy to the new audience.]`;
27
+ }
28
+ //# sourceMappingURL=audience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience.js","sourceRoot":"","sources":["../../src/agent/audience.ts"],"names":[],"mappings":"AAAA;;sBAEsB;AACtB,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,OAA8C;IAC5F,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACrE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAwB,EACxB,SAAmB,EACnB,KAAmC;IAEnC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IACzC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,qDAAqD,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,gFAAgF,CAAC;IAChL,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC1D,OAAO,8EAA8E,KAAK,CAAC,IAAI,CAAC,sBAAsB,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,oDAAoD,CAAC;AAC/L,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ export interface ClaudeLoginManagerOptions {
3
+ spawnLogin?: () => ChildProcessWithoutNullStreams;
4
+ verifyLogin?: () => Promise<void>;
5
+ timeoutMs?: number;
6
+ }
7
+ export interface ClaudeLoginStart {
8
+ url: string;
9
+ reused: boolean;
10
+ }
11
+ export interface ClaudeLoginCompletion {
12
+ verified: boolean;
13
+ verificationError?: string;
14
+ }
15
+ /**
16
+ * Owns the single machine-global Claude OAuth flow. The PKCE verifier lives in
17
+ * the waiting `claude auth login` child, so the process must stay alive between
18
+ * sending the URL and receiving the one-time code from the owner.
19
+ */
20
+ export declare class ClaudeLoginManager {
21
+ private pending;
22
+ private readonly spawnLogin;
23
+ private readonly verifyLogin;
24
+ private readonly timeoutMs;
25
+ constructor(options?: ClaudeLoginManagerOptions);
26
+ start(owner: string): Promise<ClaudeLoginStart>;
27
+ complete(owner: string, code: string): Promise<ClaudeLoginCompletion>;
28
+ cancel(owner: string): boolean;
29
+ stop(): void;
30
+ private clearPending;
31
+ }
32
+ //# sourceMappingURL=claude-login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-login.d.ts","sourceRoot":"","sources":["../../src/agent/claude-login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAoB1F,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,MAAM,8BAA8B,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,yBAA8B;IAQ7C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAuF/C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAiC3E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAW9B,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,YAAY;CAIrB"}
@@ -0,0 +1,176 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
3
+ const LOGIN_URL_RE = /https:\/\/claude\.com\/cai\/oauth\/authorize\?[^\s]+/;
4
+ /**
5
+ * Owns the single machine-global Claude OAuth flow. The PKCE verifier lives in
6
+ * the waiting `claude auth login` child, so the process must stay alive between
7
+ * sending the URL and receiving the one-time code from the owner.
8
+ */
9
+ export class ClaudeLoginManager {
10
+ pending = null;
11
+ spawnLogin;
12
+ verifyLogin;
13
+ timeoutMs;
14
+ constructor(options = {}) {
15
+ this.spawnLogin = options.spawnLogin ?? (() => spawn("claude", ["auth", "login"], {
16
+ stdio: ["pipe", "pipe", "pipe"],
17
+ }));
18
+ this.verifyLogin = options.verifyLogin ?? verifyClaudeLogin;
19
+ this.timeoutMs = options.timeoutMs ?? LOGIN_TIMEOUT_MS;
20
+ }
21
+ async start(owner) {
22
+ const normalizedOwner = owner.toLowerCase();
23
+ if (this.pending) {
24
+ if (this.pending.owner !== normalizedOwner) {
25
+ throw new Error("Another owner already has a Claude login in progress");
26
+ }
27
+ return {
28
+ url: this.pending.url ?? await this.pending.urlPromise,
29
+ reused: true,
30
+ };
31
+ }
32
+ const child = this.spawnLogin();
33
+ let resolveUrl;
34
+ let rejectUrl;
35
+ const urlPromise = new Promise((resolve, reject) => {
36
+ resolveUrl = resolve;
37
+ rejectUrl = reject;
38
+ });
39
+ let resolveCompletion;
40
+ let rejectCompletion;
41
+ const completionPromise = new Promise((resolve, reject) => {
42
+ resolveCompletion = resolve;
43
+ rejectCompletion = reject;
44
+ });
45
+ // A child can fail before `/login <code>` attaches its await. Keep that
46
+ // rejection observed while preserving it for the eventual caller.
47
+ void completionPromise.catch(() => { });
48
+ const pending = {
49
+ owner: normalizedOwner,
50
+ child,
51
+ output: "",
52
+ codeSubmitted: false,
53
+ timeout: setTimeout(() => {
54
+ const err = new Error("Claude login expired after 10 minutes; run /login again");
55
+ rejectUrl(err);
56
+ rejectCompletion(err);
57
+ child.kill();
58
+ this.clearPending(pending);
59
+ }, this.timeoutMs),
60
+ urlPromise,
61
+ resolveUrl,
62
+ rejectUrl,
63
+ completionPromise,
64
+ resolveCompletion,
65
+ rejectCompletion,
66
+ };
67
+ this.pending = pending;
68
+ const capture = (chunk) => {
69
+ pending.output = (pending.output + chunk.toString()).slice(-16_000);
70
+ if (pending.url)
71
+ return;
72
+ const match = LOGIN_URL_RE.exec(pending.output);
73
+ if (match) {
74
+ pending.url = match[0];
75
+ pending.resolveUrl(match[0]);
76
+ }
77
+ };
78
+ child.stdout.on("data", capture);
79
+ child.stderr.on("data", capture);
80
+ child.stdin.on("error", (err) => {
81
+ pending.rejectCompletion(new Error(`Could not submit Claude login code: ${err.message}`));
82
+ });
83
+ child.once("error", (err) => {
84
+ pending.rejectUrl(new Error(`Could not start Claude login: ${err.message}`));
85
+ pending.rejectCompletion(err);
86
+ this.clearPending(pending);
87
+ });
88
+ child.once("exit", (code, signal) => {
89
+ // Exit 0 after a submitted code means the CLI accepted the exchange. The
90
+ // separate authenticated probe below is the authoritative verification.
91
+ if (code === 0 && pending.codeSubmitted) {
92
+ pending.resolveCompletion();
93
+ }
94
+ else {
95
+ const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
96
+ const err = new Error(`Claude login did not complete (${reason})`);
97
+ pending.rejectUrl(err);
98
+ pending.rejectCompletion(err);
99
+ }
100
+ this.clearPending(pending);
101
+ });
102
+ return { url: await urlPromise, reused: false };
103
+ }
104
+ async complete(owner, code) {
105
+ const pending = this.pending;
106
+ if (!pending)
107
+ throw new Error("No Claude login is waiting; send /login first");
108
+ if (pending.owner !== owner.toLowerCase()) {
109
+ throw new Error("This Claude login belongs to another owner");
110
+ }
111
+ if (pending.codeSubmitted)
112
+ throw new Error("That Claude login code is already being verified");
113
+ const trimmed = code.trim();
114
+ const hash = trimmed.lastIndexOf("#");
115
+ if (hash <= 0 || hash === trimmed.length - 1 || /\s/.test(trimmed)) {
116
+ throw new Error("Invalid authorization code; paste the complete code returned by Claude");
117
+ }
118
+ const expectedState = pending.url ? new URL(pending.url).searchParams.get("state") : null;
119
+ const receivedState = trimmed.slice(hash + 1);
120
+ if (!expectedState || receivedState !== expectedState) {
121
+ throw new Error("Authorization code does not match the active /login request");
122
+ }
123
+ pending.codeSubmitted = true;
124
+ pending.child.stdin.end(`${trimmed}\n`);
125
+ await pending.completionPromise;
126
+ try {
127
+ await this.verifyLogin();
128
+ return { verified: true };
129
+ }
130
+ catch (err) {
131
+ return {
132
+ verified: false,
133
+ verificationError: err instanceof Error ? err.message : String(err),
134
+ };
135
+ }
136
+ }
137
+ cancel(owner) {
138
+ const pending = this.pending;
139
+ if (!pending || pending.owner !== owner.toLowerCase())
140
+ return false;
141
+ const err = new Error("Claude login cancelled");
142
+ pending.rejectUrl(err);
143
+ pending.rejectCompletion(err);
144
+ pending.child.kill();
145
+ this.clearPending(pending);
146
+ return true;
147
+ }
148
+ stop() {
149
+ const pending = this.pending;
150
+ if (!pending)
151
+ return;
152
+ const err = new Error("Tomo stopped during Claude login");
153
+ pending.rejectUrl(err);
154
+ pending.rejectCompletion(err);
155
+ pending.child.kill();
156
+ this.clearPending(pending);
157
+ }
158
+ clearPending(pending) {
159
+ clearTimeout(pending.timeout);
160
+ if (this.pending === pending)
161
+ this.pending = null;
162
+ }
163
+ }
164
+ function verifyClaudeLogin() {
165
+ return new Promise((resolve, reject) => {
166
+ execFile("claude", ["-p", "Reply with exactly OK.", "--safe-mode", "--no-session-persistence", "--tools", ""], { timeout: 60_000 }, (err, stdout, stderr) => {
167
+ if (!err) {
168
+ resolve();
169
+ return;
170
+ }
171
+ const detail = `${stderr || stdout}`.trim().split("\n")[0]?.slice(0, 300);
172
+ reject(new Error(`Claude login verification failed${detail ? `: ${detail}` : ""}`));
173
+ });
174
+ });
175
+ }
176
+ //# sourceMappingURL=claude-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-login.js","sourceRoot":"","sources":["../../src/agent/claude-login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Channel } from "../channels/types.js";
2
+ import type { IdentityRouter } from "../router.js";
3
+ import type { SessionStore } from "../sessions/index.js";
4
+ /** Back up ~/.tomo/config.json before a programmatic rewrite. */
5
+ export declare function backupConfigFile(): void;
6
+ export interface ChatCommandDeps {
7
+ router: IdentityRouter;
8
+ sessions: SessionStore;
9
+ /** Shared with the Agent — per-session model overrides read at session create time. */
10
+ modelOverrides: Map<string, string>;
11
+ closeLiveSession(key: string): void;
12
+ isSessionLive(key: string): boolean;
13
+ queuePendingNote(sessionKey: string, note: string): void;
14
+ }
15
+ /**
16
+ * Handles slash commands typed in chat (/new, /model, /status, /pet,
17
+ * /restore, /login).
18
+ * Wired to Channel.onCommand by the Agent.
19
+ */
20
+ export declare class ChatCommandHandler {
21
+ private readonly deps;
22
+ private restoringConfig;
23
+ private readonly claudeLogin;
24
+ constructor(deps: ChatCommandDeps);
25
+ /** True once /restore has begun — the Agent drops inbound messages until restart. */
26
+ get isRestoring(): boolean;
27
+ stop(): void;
28
+ handle(channel: Channel, command: string, chatId: string, senderName: string, args?: string, senderId?: string): Promise<void>;
29
+ /**
30
+ * /summon pulls the owner's main dm: session into a group: subsequent group
31
+ * messages run on the dm session (full personal context) while turn output
32
+ * still goes to the owner's private DM. Group-facing replies require an
33
+ * explicit send_message direct tool call to the raw group session key.
34
+ */
35
+ private handleSummonCommand;
36
+ private persistModelOverride;
37
+ private handleClaudeLogin;
38
+ private scheduleRestart;
39
+ private restoreConfigAndRestart;
40
+ }
41
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/agent/commands.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAazD,iEAAiE;AACjE,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,uFAAuF;IACvF,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1D;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;gBAE3B,IAAI,EAAE,eAAe;IAElD,qFAAqF;IACrF,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,IAAI,IAAI;IAIN,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+JpI;;;;;OAKG;YACW,mBAAmB;IA8DjC,OAAO,CAAC,oBAAoB;YAed,iBAAiB;IAuE/B,OAAO,CAAC,eAAe;YAgBT,uBAAuB;CAwBtC"}