tomo-ai 0.5.2 → 0.5.4

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 (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/defaults/AGENT.md +4 -0
  3. package/defaults/skills/system/SKILL.md +6 -1
  4. package/dist/agent/live-session.d.ts +57 -0
  5. package/dist/agent/live-session.d.ts.map +1 -0
  6. package/dist/agent/live-session.js +256 -0
  7. package/dist/agent/live-session.js.map +1 -0
  8. package/dist/agent/sdk-options.d.ts +51 -0
  9. package/dist/agent/sdk-options.d.ts.map +1 -0
  10. package/dist/agent/sdk-options.js +107 -0
  11. package/dist/agent/sdk-options.js.map +1 -0
  12. package/dist/agent/text-utils.d.ts +14 -0
  13. package/dist/agent/text-utils.d.ts.map +1 -0
  14. package/dist/agent/text-utils.js +28 -0
  15. package/dist/agent/text-utils.js.map +1 -0
  16. package/dist/agent.d.ts +45 -1
  17. package/dist/agent.d.ts.map +1 -1
  18. package/dist/agent.js +185 -449
  19. package/dist/agent.js.map +1 -1
  20. package/dist/channels/imessage.d.ts +3 -1
  21. package/dist/channels/imessage.d.ts.map +1 -1
  22. package/dist/channels/imessage.js +40 -4
  23. package/dist/channels/imessage.js.map +1 -1
  24. package/dist/channels/telegram.d.ts +4 -1
  25. package/dist/channels/telegram.d.ts.map +1 -1
  26. package/dist/channels/telegram.js +98 -7
  27. package/dist/channels/telegram.js.map +1 -1
  28. package/dist/channels/types.d.ts +15 -0
  29. package/dist/channels/types.d.ts.map +1 -1
  30. package/dist/cli/config/autostart.d.ts +2 -0
  31. package/dist/cli/config/autostart.d.ts.map +1 -0
  32. package/dist/cli/config/autostart.js +33 -0
  33. package/dist/cli/config/autostart.js.map +1 -0
  34. package/dist/cli/config/channels.d.ts +2 -0
  35. package/dist/cli/config/channels.d.ts.map +1 -0
  36. package/dist/cli/config/channels.js +156 -0
  37. package/dist/cli/config/channels.js.map +1 -0
  38. package/dist/cli/config/costs.d.ts +2 -0
  39. package/dist/cli/config/costs.d.ts.map +1 -0
  40. package/dist/cli/config/costs.js +141 -0
  41. package/dist/cli/config/costs.js.map +1 -0
  42. package/dist/cli/config/groups.d.ts +2 -0
  43. package/dist/cli/config/groups.d.ts.map +1 -0
  44. package/dist/cli/config/groups.js +57 -0
  45. package/dist/cli/config/groups.js.map +1 -0
  46. package/dist/cli/config/identities.d.ts +2 -0
  47. package/dist/cli/config/identities.d.ts.map +1 -0
  48. package/dist/cli/config/identities.js +313 -0
  49. package/dist/cli/config/identities.js.map +1 -0
  50. package/dist/cli/{config.d.ts → config/index.d.ts} +1 -1
  51. package/dist/cli/config/index.d.ts.map +1 -0
  52. package/dist/cli/config/index.js +64 -0
  53. package/dist/cli/config/index.js.map +1 -0
  54. package/dist/cli/config/model.d.ts +2 -0
  55. package/dist/cli/config/model.d.ts.map +1 -0
  56. package/dist/cli/config/model.js +21 -0
  57. package/dist/cli/config/model.js.map +1 -0
  58. package/dist/cli/config/sessions.d.ts +2 -0
  59. package/dist/cli/config/sessions.d.ts.map +1 -0
  60. package/dist/cli/config/sessions.js +83 -0
  61. package/dist/cli/config/sessions.js.map +1 -0
  62. package/dist/cli/config/shared.d.ts +9 -0
  63. package/dist/cli/config/shared.d.ts.map +1 -0
  64. package/dist/cli/config/shared.js +38 -0
  65. package/dist/cli/config/shared.js.map +1 -0
  66. package/dist/cli/cron.js +10 -4
  67. package/dist/cli/cron.js.map +1 -1
  68. package/dist/cli.js +1 -1
  69. package/dist/cli.js.map +1 -1
  70. package/dist/mcp/cron-tools.d.ts +26 -0
  71. package/dist/mcp/cron-tools.d.ts.map +1 -0
  72. package/dist/mcp/cron-tools.js +123 -0
  73. package/dist/mcp/cron-tools.js.map +1 -0
  74. package/dist/mcp/internal-server.d.ts.map +1 -1
  75. package/dist/mcp/internal-server.js +53 -0
  76. package/dist/mcp/internal-server.js.map +1 -1
  77. package/dist/workspace/index.d.ts.map +1 -1
  78. package/dist/workspace/index.js +8 -0
  79. package/dist/workspace/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/dist/cli/config.d.ts.map +0 -1
  82. package/dist/cli/config.js +0 -884
  83. package/dist/cli/config.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.4 (2026-04-29)
4
+
5
+ ### Features
6
+
7
+ - **Group chat rename MCP tool**: `tomo-internal` now exposes `rename_group_chat(target, title)` for explicit user-requested group title changes. Telegram uses Bot API `setChatTitle`; iMessage uses BlueBubbles Private API `PUT /chat/:guid` with `displayName`. Successful renames update Tomo's persisted `chatTitle` immediately so `list_sessions` reflects the new name.
8
+ - **Latest-message reaction MCP tool**: `react_to_latest_message(target, reaction, remove?)` reacts/tapbacks to the latest inbound message Tomo has seen in a session since startup. Telegram uses `setMessageReaction`; iMessage uses BlueBubbles Private API `POST /message/react`. Supported cross-channel reactions are `love`, `like`, `dislike`, `laugh`, `emphasize`, and `question`.
9
+ - **Telegram sticker sending**: responses can now include `STICKER:<telegram_file_id>` to send a Telegram sticker via `sendSticker`, mirroring the existing `MEDIA:` attachment tag flow.
10
+ - **Telegram sticker ingress**: inbound Telegram stickers now arrive as text metadata including `file_id`, optional emoji/set/type flags, and a ready-to-use `STICKER:<file_id>` resend hint.
11
+
12
+ ## 0.5.3 (2026-04-28)
13
+
14
+ ### Features
15
+
16
+ - **Cron exposed as MCP tools** (#72): `schedule_create`, `schedule_list`, `schedule_remove`. Three deferred tools on the existing `tomo-internal` server, alongside `send_message` / `list_sessions`. Selected over keeping cron Bash-only because (1) ~99% of cron operations originate from the agent, not from a human terminal, and (2) the structured-args path eliminates the class of CLI-flag-default bugs (cf. the `--once` fix in this same release). With tool search default-on in the Agent SDK, the schemas only land in context when the agent searches for scheduling capability — no per-turn cost. Names use `schedule_*` rather than `cron_*` to avoid namespace collision with Anthropic's built-in `CronCreate / CronList / CronDelete` deferred tools surfacing in the same search. Handlers instantiate a fresh `CronStore` per call so they pick up writes from the CLI, the scheduler daemon, or external edits — the on-disk JSON is the single source of truth. The `tomo cron …` CLI is preserved as a parallel surface for human audit/debug.
17
+
18
+ ### Bug fixes
19
+
20
+ - **`tomo cron add` honours the `at`-default for `deleteAfterRun`** (#71). The `--once` Commander option carried a `false` default, so `opts.once ?? (schedule.kind === "at")` always resolved to `false` and one-time `at` schedules were created with `deleteAfterRun=false`. The store layer's default already does the right thing for one-time schedules; the fix is to drop the CLI default so `opts.once` stays `undefined` when the flag isn't passed and the store's fallback can fire. Observed live: a one-time visa-interview reminder created via `--schedule "at 2026-04-27T09:00"` fired correctly but lingered as a `disabled` zombie in `cron list` instead of cleaning itself up.
21
+ - **`schedule_create` invalid-schedule path no longer escapes the handler** (#72). `parseScheduleString` accepts any unrecognized string as `kind: "cron"` (catch-all), and croner throws inside `computeNextRun` on a malformed expression. The original try/catch wrapped only the parse step, so a string like `"this is not a schedule"` passed through to `store.add()` and the croner throw escaped uncaught. The try now covers parse + add together.
22
+
23
+ ### UX
24
+
25
+ - **`tomo cron list` and `cron add` surface lifecycle** (#71). Each row now reads `(<enabled|disabled>, <once|recurring>)` and the `add` confirmation prints a `Type:` line. The previous output gave no in-CLI signal that a cron expression like `0 19 1 5 *` was about to silently re-fire every May 1 — operators had to grep `~/.tomo/data/cron/jobs.json` for `deleteAfterRun` to audit one-shot intent vs. behaviour. The `tomo-cron` skill doc gains a "one-shot trap with cron expressions" section recommending ISO-date and `in Xd` schedules over date-pinned cron expressions for single-fire reminders.
26
+
3
27
  ## 0.5.2 (2026-04-27)
4
28
 
5
29
  ### Bug fixes
package/defaults/AGENT.md CHANGED
@@ -20,3 +20,7 @@ You have `list_sessions` and `send_message` tools for proactively posting to ano
20
20
  - **`direct`**: send verbatim text. Use for factual broadcasts ("meeting moved to 3pm"), pasted content, or self-targeted mid-loop progress updates.
21
21
 
22
22
  Call `list_sessions` first if you're unsure which group to address. For normal in-conversation responses, just reply with text — don't reach for these tools.
23
+
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
+
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.
@@ -67,6 +67,9 @@ Responses stream to Telegram in real-time — messages update every 1.5s as toke
67
67
  ### MEDIA: tag
68
68
  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.
69
69
 
70
+ ### STICKER: tag
71
+ To send a Telegram sticker by file_id, include `STICKER:<telegram_file_id>` in your response. The harness strips it from text and sends the sticker on Telegram; other channels ignore sticker sends. Only use file_ids you have seen or been given.
72
+
70
73
  ### NO_REPLY
71
74
  Reply with exactly `NO_REPLY` to suppress delivery to the channel. Use for background tasks that found nothing to report.
72
75
 
@@ -85,12 +88,14 @@ Two listen modes per group:
85
88
  The active mode plus group title and known participants are part of your per-session system prompt under `## Group Chat Context` whenever you're in a group session — survives compaction. Each incoming group message is prefixed with the sender name (`Alice: ...`) so you can attribute it.
86
89
 
87
90
  ### Proactive messaging (MCP tools)
88
- Two in-process MCP tools let you message outside the current conversation:
91
+ In-process MCP tools let you message outside the current conversation:
89
92
 
90
93
  - `mcp__tomo-internal__list_sessions` — discover valid targets. Returns `{identities: [{name}], groups: [{key, title?, participants?}]}`. Group titles and participants populate as messages arrive in the group; an entry without them just hasn't seen activity since the schema landed.
91
94
  - `mcp__tomo-internal__send_message(target, message, mode?)` — send to a target. Two modes:
92
95
  - `delegate` (default): describe the *intent* (e.g. "follow up with Alice about her recent trip"). The recipient session's Claude composes the actual message in its own voice/context. Best for social or contextual relays. Fire-and-forget.
93
96
  - `direct`: send the verbatim text. Recipient is not triggered into a Claude turn. Best for factual broadcasts ("meeting moved to 3pm"), pasted content, or self-targeted mid-loop progress updates.
97
+ - `mcp__tomo-internal__rename_group_chat(target, title)` — rename the real Telegram or iMessage group title. Use only when the user explicitly asks for a rename; `target` must be a group session key from `list_sessions`.
98
+ - `mcp__tomo-internal__react_to_latest_message(target, reaction, remove?)` — react/tapback to the latest inbound message Tomo has seen in that session since startup. This latest-message state is in-memory; after a restart, wait for a new inbound message before using it. Usually pass the current Session key. Reactions: `love`, `like`, `dislike`, `laugh`, `emphasize`, `question`.
94
99
 
95
100
  Pass identity name (`"alice"`) or session key (`"telegram:-1001234567"`) as `target`. Call `list_sessions` first if unsure. Tool result lines (with `is_error` flag) appear in `tomo logs` immediately after the corresponding tool call.
96
101
 
@@ -0,0 +1,57 @@
1
+ import { type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { sdkOptions } from "./sdk-options.js";
3
+ export interface MessageRequest {
4
+ message: SDKUserMessage;
5
+ /** Called for each text-delta as it streams (cumulative running text). */
6
+ onText?: (text: string) => void;
7
+ /**
8
+ * Called once per text block when an `assistant` event arrives, after the
9
+ * block's deltas have all been streamed. Receives the block's full text.
10
+ * Channels can use this to seal the in-flight streamed message and start
11
+ * fresh on the next block, so multi-block turns ship as multiple messages
12
+ * instead of a single edit-in-place that drops earlier blocks.
13
+ */
14
+ onBlockComplete?: (text: string) => void | Promise<void>;
15
+ resolve: (response: string) => void;
16
+ reject: (err: Error) => void;
17
+ }
18
+ export interface QueryResult {
19
+ costUsd: number;
20
+ inputTokens: number;
21
+ outputTokens: number;
22
+ cacheReadTokens: number;
23
+ cacheCreationTokens: number;
24
+ contextUsed: number;
25
+ contextMax: number;
26
+ contextBreakdown?: {
27
+ name: string;
28
+ tokens: number;
29
+ }[];
30
+ }
31
+ export declare class LiveSession {
32
+ private q;
33
+ private pendingMessage;
34
+ private currentRequest;
35
+ private parts;
36
+ private streamingText;
37
+ private sessionId;
38
+ private alive;
39
+ lastResult: QueryResult | null;
40
+ private prevTotalCost;
41
+ private eventLoopDone;
42
+ private sessionKey;
43
+ private pendingToolNames;
44
+ constructor(options: ReturnType<typeof sdkOptions>, sessionKey?: string);
45
+ private messageGenerator;
46
+ private consumeEvents;
47
+ private handleEvent;
48
+ private logContextUsage;
49
+ send(text: string, onText?: (text: string) => void, images?: Array<{
50
+ data: string;
51
+ mediaType: string;
52
+ }>, onBlockComplete?: (text: string) => void | Promise<void>): Promise<string>;
53
+ getSessionId(): string | null;
54
+ isAlive(): boolean;
55
+ close(): void;
56
+ }
57
+ //# sourceMappingURL=live-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-session.d.ts","sourceRoot":"","sources":["../../src/agent/live-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,cAAc,EAAmB,MAAM,gCAAgC,CAAC;AAEzG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD;AAuCD,qBAAa,WAAW;IACtB,OAAO,CAAC,CAAC,CAAQ;IACjB,OAAO,CAAC,cAAc,CAAgD;IACtE,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,KAAK,CAAQ;IACrB,UAAU,EAAE,WAAW,GAAG,IAAI,CAAQ;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAqB;IAGvC,OAAO,CAAC,gBAAgB,CAA6B;gBAEzC,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM;YAMxD,gBAAgB;YAUjB,aAAa;YAeb,WAAW;YA2GX,eAAe;IA6CvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,EACnD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvD,OAAO,CAAC,MAAM,CAAC;IA6ClB,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,OAAO,IAAI,OAAO;IAIlB,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,256 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ import { log } from "../logger.js";
3
+ function summarizeToolResult(content) {
4
+ // Tool results arrive as either a string or an array of content blocks
5
+ // ({type:"text",text:"..."} | {type:"image",...} | etc.). We flatten to a
6
+ // short readable string for log lines — no need to be exhaustive.
7
+ if (content == null)
8
+ return "(empty)";
9
+ if (typeof content === "string")
10
+ return content.slice(0, 500);
11
+ if (Array.isArray(content)) {
12
+ const parts = [];
13
+ for (const b of content) {
14
+ if (b && typeof b === "object") {
15
+ if ("text" in b && typeof b.text === "string") {
16
+ parts.push(b.text);
17
+ }
18
+ else if ("type" in b) {
19
+ parts.push(`<${b.type}>`);
20
+ }
21
+ }
22
+ }
23
+ return parts.join(" ").slice(0, 500);
24
+ }
25
+ return JSON.stringify(content).slice(0, 500);
26
+ }
27
+ function summarizeToolInput(name, input) {
28
+ if (!input)
29
+ return name;
30
+ switch (name) {
31
+ case "Read": return `Read ${input.file_path}`;
32
+ case "Write": return `Write ${input.file_path}`;
33
+ case "Edit": return `Edit ${input.file_path}`;
34
+ case "Bash": return `Bash: ${String(input.command).slice(0, 500)}`;
35
+ case "Glob": return `Glob ${input.pattern}`;
36
+ case "Grep": return `Grep "${input.pattern}"`;
37
+ case "WebSearch": return `WebSearch: ${input.query}`;
38
+ case "WebFetch": return `WebFetch: ${input.url}`;
39
+ default: return `${name}: ${JSON.stringify(input).slice(0, 500)}`;
40
+ }
41
+ }
42
+ export class LiveSession {
43
+ q;
44
+ pendingMessage = null;
45
+ currentRequest = null;
46
+ parts = [];
47
+ streamingText = "";
48
+ sessionId = null;
49
+ alive = true;
50
+ lastResult = null;
51
+ prevTotalCost = 0;
52
+ eventLoopDone;
53
+ sessionKey;
54
+ // Maps tool_use_id → tool name so we can label tool_result log lines
55
+ // (the result event only carries the use id, not the original name).
56
+ pendingToolNames = new Map();
57
+ constructor(options, sessionKey) {
58
+ this.sessionKey = sessionKey;
59
+ this.q = query({ prompt: this.messageGenerator(), options });
60
+ this.eventLoopDone = this.consumeEvents();
61
+ }
62
+ async *messageGenerator() {
63
+ while (this.alive) {
64
+ const msg = await new Promise((resolve) => {
65
+ this.pendingMessage = resolve;
66
+ });
67
+ this.pendingMessage = null;
68
+ yield msg;
69
+ }
70
+ }
71
+ async consumeEvents() {
72
+ try {
73
+ for await (const event of this.q) {
74
+ await this.handleEvent(event);
75
+ }
76
+ }
77
+ catch (err) {
78
+ // If there's a pending request, reject it
79
+ if (this.currentRequest) {
80
+ this.currentRequest.reject(err instanceof Error ? err : new Error(String(err)));
81
+ this.currentRequest = null;
82
+ }
83
+ }
84
+ this.alive = false;
85
+ }
86
+ async handleEvent(event) {
87
+ const req = this.currentRequest;
88
+ if (event.type === "stream_event") {
89
+ const se = event;
90
+ if (se.event?.type === "content_block_delta" && se.event.delta?.type === "text_delta" && se.event.delta.text) {
91
+ this.streamingText += se.event.delta.text;
92
+ req?.onText?.(this.streamingText);
93
+ }
94
+ }
95
+ if (event.type === "assistant" && event.message?.content) {
96
+ this.streamingText = "";
97
+ for (const block of event.message.content) {
98
+ if ("text" in block) {
99
+ this.parts.push(block.text);
100
+ // Signal block boundary so channels can seal the streamed message
101
+ // and start fresh on the next block (text → tool → text turns).
102
+ if (req?.onBlockComplete) {
103
+ await req.onBlockComplete(block.text);
104
+ }
105
+ }
106
+ else if ("type" in block && block.type === "tool_use") {
107
+ const tool = block;
108
+ if (tool.id && tool.name)
109
+ this.pendingToolNames.set(tool.id, tool.name);
110
+ log.info({ tool: tool.name }, summarizeToolInput(tool.name, tool.input));
111
+ }
112
+ }
113
+ }
114
+ if (event.type === "user" && event.message?.content && Array.isArray(event.message.content)) {
115
+ for (const block of event.message.content) {
116
+ if (block && typeof block === "object" && "type" in block && block.type === "tool_result") {
117
+ const tr = block;
118
+ const name = tr.tool_use_id ? this.pendingToolNames.get(tr.tool_use_id) : undefined;
119
+ if (tr.tool_use_id)
120
+ this.pendingToolNames.delete(tr.tool_use_id);
121
+ log.info({ tool: name ?? "?", is_error: tr.is_error ?? false }, `result: ${summarizeToolResult(tr.content)}`);
122
+ }
123
+ }
124
+ }
125
+ if (event.type === "system" && event.subtype === "compact_boundary") {
126
+ const compact = event;
127
+ log.info({ pre: compact.compact_metadata?.pre_tokens, post: compact.compact_metadata?.post_tokens }, "Context compacted");
128
+ }
129
+ if (event.type === "tool_use_summary") {
130
+ log.debug(event.summary);
131
+ }
132
+ if (event.type === "result") {
133
+ const result = event;
134
+ if (result.session_id) {
135
+ this.sessionId = result.session_id;
136
+ }
137
+ const u = result.usage;
138
+ const input = u?.input_tokens ?? 0;
139
+ const output = u?.output_tokens ?? 0;
140
+ const cacheRead = u?.cache_read_input_tokens ?? 0;
141
+ const cacheCreated = u?.cache_creation_input_tokens ?? 0;
142
+ // Compute per-turn cost as delta from cumulative total
143
+ const totalCost = result.total_cost_usd ?? 0;
144
+ const turnCost = totalCost - this.prevTotalCost;
145
+ this.prevTotalCost = totalCost;
146
+ // Store result stats, get context usage, then resolve
147
+ this.lastResult = {
148
+ costUsd: totalCost,
149
+ inputTokens: input,
150
+ outputTokens: output,
151
+ cacheReadTokens: cacheRead,
152
+ cacheCreationTokens: cacheCreated,
153
+ contextUsed: 0,
154
+ contextMax: 0,
155
+ };
156
+ // Await context usage before resolving so stats are complete
157
+ await this.logContextUsage(result, turnCost, totalCost, input, output, cacheRead, cacheCreated);
158
+ // Trim each block; drop empty ones; rejoin for the response string
159
+ // returned to callers (used for transcript storage and logging).
160
+ // Per-block delivery happens during the stream via `onBlockComplete`,
161
+ // not from this final snapshot.
162
+ const trimmed = this.parts.map((p) => p.trim()).filter((p) => p.length > 0);
163
+ const response = trimmed.join("\n").trim() || "I'm not sure how to respond to that.";
164
+ this.parts = [];
165
+ this.streamingText = "";
166
+ req?.resolve(response);
167
+ this.currentRequest = null;
168
+ }
169
+ }
170
+ async logContextUsage(result, turnCost, totalCost, input, output, cacheRead, cacheCreated) {
171
+ const contextInfo = await (async () => {
172
+ try {
173
+ const ctx = await this.q.getContextUsage();
174
+ const pct = Math.round(ctx.percentage);
175
+ if (this.lastResult) {
176
+ this.lastResult.contextUsed = ctx.totalTokens;
177
+ this.lastResult.contextMax = ctx.maxTokens;
178
+ this.lastResult.contextBreakdown = ctx.categories
179
+ .filter((c) => c.tokens > 0)
180
+ .map((c) => ({ name: c.name, tokens: c.tokens }));
181
+ }
182
+ if (pct >= 80) {
183
+ log.warn({ used: ctx.totalTokens, max: ctx.maxTokens, pct: `${pct}%` }, "Context nearing compaction");
184
+ }
185
+ return `${ctx.totalTokens}/${ctx.maxTokens} (${pct}%)`;
186
+ }
187
+ catch {
188
+ const approx = input + cacheRead + cacheCreated;
189
+ if (this.lastResult) {
190
+ this.lastResult.contextUsed = approx;
191
+ this.lastResult.contextMax = 1_000_000;
192
+ }
193
+ return `~${approx}/1000000`;
194
+ }
195
+ })();
196
+ log.info({
197
+ session: this.sessionKey,
198
+ turns: result.num_turns,
199
+ duration: `${result.duration_ms}ms`,
200
+ cost: `$${turnCost.toFixed(4)}`,
201
+ totalCost: `$${totalCost.toFixed(4)}`,
202
+ tokens: `in:${input} out:${output}`,
203
+ cache: `read:${cacheRead} created:${cacheCreated}`,
204
+ context: contextInfo,
205
+ }, "Run completed (%s)", result.subtype);
206
+ }
207
+ async send(text, onText, images, onBlockComplete) {
208
+ if (!this.alive)
209
+ throw new Error("Session is closed");
210
+ const TIMEOUT_MS = 10 * 60 * 1000; // 10 minute timeout per send()
211
+ const content = [];
212
+ if (images && images.length > 0) {
213
+ for (const img of images) {
214
+ content.push({
215
+ type: "image",
216
+ source: { type: "base64", media_type: img.mediaType, data: img.data },
217
+ });
218
+ }
219
+ }
220
+ content.push({ type: "text", text });
221
+ return new Promise((resolve, reject) => {
222
+ const timer = setTimeout(() => {
223
+ this.currentRequest = null;
224
+ reject(new Error("Query timed out after 10 minutes"));
225
+ }, TIMEOUT_MS);
226
+ const wrappedResolve = (val) => { clearTimeout(timer); resolve(val); };
227
+ const wrappedReject = (err) => { clearTimeout(timer); reject(err); };
228
+ this.currentRequest = {
229
+ message: { type: "user", message: { role: "user", content: content }, parent_tool_use_id: null },
230
+ onText,
231
+ onBlockComplete,
232
+ resolve: wrappedResolve,
233
+ reject: wrappedReject,
234
+ };
235
+ this.parts = [];
236
+ this.streamingText = "";
237
+ if (this.pendingMessage && this.currentRequest) {
238
+ this.pendingMessage(this.currentRequest.message);
239
+ }
240
+ else {
241
+ wrappedReject(new Error("Session not ready to receive messages"));
242
+ }
243
+ });
244
+ }
245
+ getSessionId() {
246
+ return this.sessionId;
247
+ }
248
+ isAlive() {
249
+ return this.alive;
250
+ }
251
+ close() {
252
+ this.alive = false;
253
+ this.q.close();
254
+ }
255
+ }
256
+ //# sourceMappingURL=live-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-session.js","sourceRoot":"","sources":["../../src/agent/live-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { McpSdkServerConfigWithInstance } from "@anthropic-ai/claude-agent-sdk";
2
+ /** True when the session uses our custom LCM compaction (DMs always; groups only
3
+ * if config.lcm.groupCompactStyle="lcm"). When false, SDK auto-compact is in
4
+ * charge and the harness skips its compaction nudges. */
5
+ export declare function usesLcmCompact(sessionKey: string): boolean;
6
+ declare function skillsCanUseTool(toolName: string, input: Record<string, unknown>): Promise<{
7
+ behavior: "allow";
8
+ updatedInput: Record<string, unknown>;
9
+ } | {
10
+ behavior: "deny";
11
+ message: string;
12
+ }>;
13
+ export interface SessionContext {
14
+ sessionKey: string;
15
+ sdkSessionId?: string;
16
+ /** Group metadata snapshot — present only for group sessions. */
17
+ group?: {
18
+ chatTitle?: string;
19
+ participants?: string[];
20
+ /** True for iMessage groups (always) and Telegram groups in
21
+ * config.passiveGroups. Drives the "stay silent unless useful" rule. */
22
+ isPassive: boolean;
23
+ };
24
+ }
25
+ export declare function sdkOptions(internalMcpServer: McpSdkServerConfigWithInstance, resumeSessionId?: string, model?: string, sessionContext?: SessionContext): {
26
+ env?: {
27
+ DISABLE_AUTO_COMPACT: string;
28
+ } | undefined;
29
+ resume?: string | undefined;
30
+ model: string;
31
+ cwd: string;
32
+ systemPrompt: string;
33
+ permissionMode: "bypassPermissions";
34
+ allowDangerouslySkipPermissions: boolean;
35
+ allowedTools: string[];
36
+ mcpServers: {
37
+ "tomo-internal": McpSdkServerConfigWithInstance;
38
+ };
39
+ settingSources: ("project")[];
40
+ settings: {
41
+ attribution: {
42
+ commit: string;
43
+ pr: string;
44
+ };
45
+ };
46
+ canUseTool: typeof skillsCanUseTool;
47
+ includePartialMessages: boolean;
48
+ maxTurns: number;
49
+ };
50
+ export {};
51
+ //# sourceMappingURL=sdk-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-options.d.ts","sourceRoot":"","sources":["../../src/agent/sdk-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAarF;;0DAE0D;AAC1D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG1D;AAID,iBAAe,gBAAgB,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAa/G;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,KAAK,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB;iFACyE;QACzE,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED,wBAAgB,UAAU,CACxB,iBAAiB,EAAE,8BAA8B,EACjD,eAAe,CAAC,EAAE,MAAM,EACxB,KAAK,CAAC,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,cAAc;;;;;;;;;;;;;;oBAiDE,CAAC,SAAS,CAAC,EAAE;;;;;;;;;;EAwB/C"}
@@ -0,0 +1,107 @@
1
+ import { config } from "../config.js";
2
+ import { buildSystemPrompt } from "../workspace/index.js";
3
+ import { isGroupSessionKey } from "../lcm/blocks.js";
4
+ import { TOMO_INTERNAL_MCP_NAME } from "../mcp/internal-server.js";
5
+ // DM sessions run our custom hierarchical LCM (daily/weekly/monthly/yearly
6
+ // rollups via skill), so SDK auto-compact is disabled for them via the
7
+ // DISABLE_AUTO_COMPACT env var — we don't want the SDK to collapse our
8
+ // rollup structure behind our back. Group sessions default to SDK auto-compact
9
+ // (config.lcm.groupCompactStyle="sdk"); set it to "lcm" to opt groups into the
10
+ // same hierarchical LCM flow as DMs.
11
+ /** True when the session uses our custom LCM compaction (DMs always; groups only
12
+ * if config.lcm.groupCompactStyle="lcm"). When false, SDK auto-compact is in
13
+ * charge and the harness skips its compaction nudges. */
14
+ export function usesLcmCompact(sessionKey) {
15
+ if (!isGroupSessionKey(sessionKey))
16
+ return true;
17
+ return config.lcm.groupCompactStyle === "lcm";
18
+ }
19
+ const SKILLS_DIR = `${config.workspaceDir}/.claude/skills/`;
20
+ async function skillsCanUseTool(toolName, input) {
21
+ const filePath = (input.file_path ?? input.notebook_path ?? input.path);
22
+ if (filePath && filePath.startsWith(SKILLS_DIR)) {
23
+ return { behavior: "allow", updatedInput: input };
24
+ }
25
+ // Bash mkdir / touch / etc. — allow if command targets the skills dir.
26
+ if (toolName === "Bash" && typeof input.command === "string" && input.command.includes(SKILLS_DIR)) {
27
+ return { behavior: "allow", updatedInput: input };
28
+ }
29
+ return {
30
+ behavior: "deny",
31
+ message: `Permission required for ${toolName}${filePath ? ` on ${filePath}` : ""} — only ${SKILLS_DIR}** is auto-approved at this step.`,
32
+ };
33
+ }
34
+ export function sdkOptions(internalMcpServer, resumeSessionId, model, sessionContext) {
35
+ let systemPrompt = buildSystemPrompt();
36
+ // Inject session context so the agent can use LCM tools
37
+ if (sessionContext) {
38
+ const lines = [
39
+ "\n\n# SESSION — Current Session Info",
40
+ `- Session key: ${sessionContext.sessionKey}`,
41
+ ];
42
+ if (sessionContext.sdkSessionId) {
43
+ lines.push(`- SDK session ID: ${sessionContext.sdkSessionId}`);
44
+ }
45
+ if (sessionContext.group) {
46
+ const g = sessionContext.group;
47
+ lines.push("");
48
+ lines.push("## Group Chat Context");
49
+ if (g.chatTitle)
50
+ lines.push(`- Group title: "${g.chatTitle}"`);
51
+ if (g.participants && g.participants.length > 0) {
52
+ lines.push(`- Known participants: ${g.participants.join(", ")} (more may join later — you'll see new senders prefixed in incoming messages)`);
53
+ }
54
+ lines.push("- Messages from each sender are prefixed with their name (e.g. `Alice: ...`).");
55
+ if (g.isPassive) {
56
+ lines.push("- **Listen mode: passive.** You see every message in this group; no @mention is required to address you. Reply only when you have something genuinely useful to add — reply `NO_REPLY` to stay silent. Do not respond to casual chatter, greetings, or messages not directed at you.");
57
+ }
58
+ else {
59
+ lines.push("- **Listen mode: mention-required.** You only receive messages that explicitly tag you; respond as you would in a DM.");
60
+ }
61
+ }
62
+ systemPrompt += lines.join("\n");
63
+ }
64
+ return {
65
+ model: model ?? config.model,
66
+ cwd: config.workspaceDir,
67
+ systemPrompt,
68
+ permissionMode: "bypassPermissions",
69
+ allowDangerouslySkipPermissions: true,
70
+ allowedTools: [
71
+ "Read", "Write", "Edit", "Bash", "Glob", "Grep",
72
+ "WebSearch", "WebFetch", "Agent", "NotebookEdit", "TodoWrite", "Skill",
73
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__send_message`,
74
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__list_sessions`,
75
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__rename_group_chat`,
76
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__react_to_latest_message`,
77
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__schedule_create`,
78
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__schedule_list`,
79
+ `mcp__${TOMO_INTERNAL_MCP_NAME}__schedule_remove`,
80
+ ],
81
+ mcpServers: { [TOMO_INTERNAL_MCP_NAME]: internalMcpServer },
82
+ settingSources: ["project"],
83
+ settings: {
84
+ attribution: {
85
+ commit: "Made by [Tomo](https://github.com/shuaiyuan17/tomo)",
86
+ pr: "Made by [Tomo](https://github.com/shuaiyuan17/tomo)",
87
+ },
88
+ },
89
+ // bypassPermissions auto-approves most tools at step 3 of the permission
90
+ // flow, but writes to `.claude/`, `.git/`, etc. are protected and fall
91
+ // through to step 5 (canUseTool). We narrowly re-allow `.claude/skills/`
92
+ // here so tomo can manage its own skill library, while leaving every
93
+ // other protected path on its default (deny). See:
94
+ // https://code.claude.com/docs/en/agent-sdk/permissions#permission-modes
95
+ canUseTool: skillsCanUseTool,
96
+ includePartialMessages: true,
97
+ maxTurns: config.maxTurns,
98
+ ...(resumeSessionId ? { resume: resumeSessionId } : {}),
99
+ // Note: SDK `env` fully replaces the child's env (not merged despite the
100
+ // d.ts claim), so we must spread process.env ourselves — otherwise the
101
+ // child CLI spawns with an empty env and fails to locate its runtime.
102
+ ...(sessionContext && usesLcmCompact(sessionContext.sessionKey)
103
+ ? { env: { ...process.env, DISABLE_AUTO_COMPACT: "1" } }
104
+ : {}),
105
+ };
106
+ }
107
+ //# sourceMappingURL=sdk-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-options.js","sourceRoot":"","sources":["../../src/agent/sdk-options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare function isSilentReply(text: string): boolean;
2
+ export declare const MEDIA_RE: RegExp;
3
+ export declare const STICKER_RE: RegExp;
4
+ export declare const ATTACHMENT_TAG_RE: RegExp;
5
+ export declare function extractMedia(text: string): {
6
+ cleanText: string;
7
+ mediaPaths: string[];
8
+ };
9
+ export declare function extractAttachments(text: string): {
10
+ cleanText: string;
11
+ mediaPaths: string[];
12
+ stickerIds: string[];
13
+ };
14
+ //# sourceMappingURL=text-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-utils.d.ts","sourceRoot":"","sources":["../../src/agent/text-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,eAAO,MAAM,QAAQ,QAA6C,CAAC;AACnE,eAAO,MAAM,UAAU,QAA+C,CAAC;AACvE,eAAO,MAAM,iBAAiB,QAAqD,CAAC;AAEpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAOtF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAYlH"}
@@ -0,0 +1,28 @@
1
+ export function isSilentReply(text) {
2
+ return /^\s*NO_REPLY\s*$/i.test(text);
3
+ }
4
+ export const MEDIA_RE = /\bMEDIA:\s*(?:"([^"\n]+)"|([^\s\n"]+))/gi;
5
+ export const STICKER_RE = /\bSTICKER:\s*(?:"([^"\n]+)"|([^\s\n"]+))/gi;
6
+ export const ATTACHMENT_TAG_RE = /\b(?:MEDIA|STICKER):\s*(?:"[^"\n]+"|[^\s\n"]+)/gi;
7
+ export function extractMedia(text) {
8
+ const mediaPaths = [];
9
+ const cleanText = text.replace(MEDIA_RE, (_match, quotedPath, unquotedPath) => {
10
+ mediaPaths.push(String(quotedPath ?? unquotedPath).trim());
11
+ return "";
12
+ }).trim();
13
+ return { cleanText, mediaPaths };
14
+ }
15
+ export function extractAttachments(text) {
16
+ const mediaPaths = [];
17
+ const stickerIds = [];
18
+ const withoutMedia = text.replace(MEDIA_RE, (_match, quotedPath, unquotedPath) => {
19
+ mediaPaths.push(String(quotedPath ?? unquotedPath).trim());
20
+ return "";
21
+ });
22
+ const cleanText = withoutMedia.replace(STICKER_RE, (_match, quotedId, unquotedId) => {
23
+ stickerIds.push(String(quotedId ?? unquotedId).trim());
24
+ return "";
25
+ }).trim();
26
+ return { cleanText, mediaPaths, stickerIds };
27
+ }
28
+ //# sourceMappingURL=text-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-utils.js","sourceRoot":"","sources":["../../src/agent/text-utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,0CAA0C,CAAC;AACnE,MAAM,CAAC,MAAM,UAAU,GAAG,4CAA4C,CAAC;AACvE,MAAM,CAAC,MAAM,iBAAiB,GAAG,kDAAkD,CAAC;AAEpF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE;QAC5E,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3D,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE;QAC/E,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3D,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE;QAClF,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC/C,CAAC"}