talon-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/bin/talon.js +5 -0
- package/package.json +86 -0
- package/prompts/base.md +13 -0
- package/prompts/custom.md.example +22 -0
- package/prompts/dream.md +41 -0
- package/prompts/identity.md +45 -0
- package/prompts/teams.md +52 -0
- package/prompts/telegram.md +89 -0
- package/prompts/terminal.md +13 -0
- package/src/__tests__/chat-id.test.ts +91 -0
- package/src/__tests__/chat-settings.test.ts +337 -0
- package/src/__tests__/config.test.ts +546 -0
- package/src/__tests__/cron-store.test.ts +440 -0
- package/src/__tests__/daily-log.test.ts +146 -0
- package/src/__tests__/dispatcher.test.ts +383 -0
- package/src/__tests__/errors.test.ts +240 -0
- package/src/__tests__/fuzz.test.ts +302 -0
- package/src/__tests__/gateway-actions.test.ts +1453 -0
- package/src/__tests__/gateway-context.test.ts +102 -0
- package/src/__tests__/gateway-http.test.ts +245 -0
- package/src/__tests__/handlers.test.ts +351 -0
- package/src/__tests__/history-persistence.test.ts +172 -0
- package/src/__tests__/history.test.ts +659 -0
- package/src/__tests__/integration.test.ts +189 -0
- package/src/__tests__/log.test.ts +110 -0
- package/src/__tests__/media-index.test.ts +277 -0
- package/src/__tests__/plugin.test.ts +317 -0
- package/src/__tests__/prompt-builder.test.ts +71 -0
- package/src/__tests__/sessions.test.ts +594 -0
- package/src/__tests__/teams-frontend.test.ts +239 -0
- package/src/__tests__/telegram.test.ts +177 -0
- package/src/__tests__/terminal-commands.test.ts +367 -0
- package/src/__tests__/terminal-frontend.test.ts +141 -0
- package/src/__tests__/terminal-renderer.test.ts +278 -0
- package/src/__tests__/watchdog.test.ts +287 -0
- package/src/__tests__/workspace.test.ts +184 -0
- package/src/backend/claude-sdk/index.ts +438 -0
- package/src/backend/claude-sdk/tools.ts +605 -0
- package/src/backend/opencode/index.ts +252 -0
- package/src/bootstrap.ts +134 -0
- package/src/cli.ts +611 -0
- package/src/core/cron.ts +148 -0
- package/src/core/dispatcher.ts +126 -0
- package/src/core/dream.ts +295 -0
- package/src/core/errors.ts +206 -0
- package/src/core/gateway-actions.ts +267 -0
- package/src/core/gateway.ts +258 -0
- package/src/core/plugin.ts +432 -0
- package/src/core/prompt-builder.ts +43 -0
- package/src/core/pulse.ts +175 -0
- package/src/core/types.ts +85 -0
- package/src/frontend/teams/actions.ts +101 -0
- package/src/frontend/teams/formatting.ts +220 -0
- package/src/frontend/teams/graph.ts +297 -0
- package/src/frontend/teams/index.ts +308 -0
- package/src/frontend/teams/proxy-fetch.ts +28 -0
- package/src/frontend/teams/tools.ts +177 -0
- package/src/frontend/telegram/actions.ts +437 -0
- package/src/frontend/telegram/admin.ts +178 -0
- package/src/frontend/telegram/callbacks.ts +251 -0
- package/src/frontend/telegram/commands.ts +543 -0
- package/src/frontend/telegram/formatting.ts +101 -0
- package/src/frontend/telegram/handlers.ts +1008 -0
- package/src/frontend/telegram/helpers.ts +105 -0
- package/src/frontend/telegram/index.ts +130 -0
- package/src/frontend/telegram/middleware.ts +177 -0
- package/src/frontend/telegram/userbot.ts +546 -0
- package/src/frontend/terminal/commands.ts +303 -0
- package/src/frontend/terminal/index.ts +282 -0
- package/src/frontend/terminal/input.ts +297 -0
- package/src/frontend/terminal/renderer.ts +248 -0
- package/src/index.ts +144 -0
- package/src/login.ts +89 -0
- package/src/storage/chat-settings.ts +218 -0
- package/src/storage/cron-store.ts +165 -0
- package/src/storage/daily-log.ts +97 -0
- package/src/storage/history.ts +278 -0
- package/src/storage/media-index.ts +116 -0
- package/src/storage/sessions.ts +328 -0
- package/src/util/chat-id.ts +21 -0
- package/src/util/config.ts +244 -0
- package/src/util/log.ts +122 -0
- package/src/util/paths.ts +80 -0
- package/src/util/time.ts +86 -0
- package/src/util/trace.ts +35 -0
- package/src/util/watchdog.ts +108 -0
- package/src/util/workspace.ts +208 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Talon
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://github.com/anthropics/claude-agent-sdk-typescript)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Multi-platform agentic AI harness powered by Claude. Runs on Telegram, Teams, and Terminal with full tool access through MCP.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Multi-frontend** — Telegram (Grammy), Teams (Bot Framework), Terminal (readline)
|
|
13
|
+
- **Claude Agent SDK** — streaming responses, extended thinking, 1M context sessions
|
|
14
|
+
- **31 MCP tools** — messaging, media, history, search, web, cron jobs, file system
|
|
15
|
+
- **Plugin system** — extend with external tool packages (keeps core OSS-clean)
|
|
16
|
+
- **Cron jobs** — persistent recurring tasks with full tool access
|
|
17
|
+
- **Pulse** — periodic conversation-aware engagement in group chats
|
|
18
|
+
- **Per-chat settings** — model, effort level, pulse toggle per conversation
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/dylanneve1/talon.git && cd talon
|
|
24
|
+
npm install
|
|
25
|
+
|
|
26
|
+
# Interactive setup (select frontend, configure tokens)
|
|
27
|
+
npx talon setup
|
|
28
|
+
|
|
29
|
+
# Start
|
|
30
|
+
npx talon start # configured frontend (Telegram/Terminal)
|
|
31
|
+
npx talon chat # terminal chat mode
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires [Node.js 22+](https://nodejs.org/) and [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated.
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
index.ts (Composition Root)
|
|
40
|
+
├── core/ Platform-agnostic core
|
|
41
|
+
│ ├── gateway.ts HTTP bridge for MCP tool calls
|
|
42
|
+
│ ├── dispatcher.ts Query queue + lifecycle
|
|
43
|
+
│ ├── plugin.ts Plugin loader + registry
|
|
44
|
+
│ ├── pulse.ts Periodic engagement
|
|
45
|
+
│ └── cron.ts Persistent scheduled jobs
|
|
46
|
+
├── backend/
|
|
47
|
+
│ ├── claude-sdk/ Claude Agent SDK + MCP subprocess
|
|
48
|
+
│ └── opencode/ OpenCode SDK alternative
|
|
49
|
+
├── frontend/
|
|
50
|
+
│ ├── telegram/ Grammy + GramJS userbot
|
|
51
|
+
│ ├── teams/ Bot Framework
|
|
52
|
+
│ └── terminal/ Readline CLI with tool call visibility
|
|
53
|
+
└── storage/ Sessions, history, settings, cron, media
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Plugin System
|
|
57
|
+
|
|
58
|
+
Plugins add MCP tools and gateway actions without modifying core code. SOLID interface — only `name` is required, everything else is optional.
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"plugins": [
|
|
63
|
+
{ "path": "/path/to/my-plugin", "config": { "apiKey": "..." } }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
export default {
|
|
70
|
+
name: "my-plugin",
|
|
71
|
+
version: "1.0.0",
|
|
72
|
+
mcpServerPath: resolve(import.meta.dirname, "tools.ts"),
|
|
73
|
+
validateConfig(config) { /* return errors or undefined */ },
|
|
74
|
+
getEnvVars(config) { return { MY_KEY: config.apiKey }; },
|
|
75
|
+
handleAction(body, chatId) { /* gateway action handler */ },
|
|
76
|
+
getSystemPromptAddition(config) { return "## My Plugin\n..."; },
|
|
77
|
+
init(config) { /* one-time setup */ },
|
|
78
|
+
destroy() { /* cleanup */ },
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## CLI
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
talon setup Interactive setup wizard (multi-select frontends)
|
|
86
|
+
talon start Start the configured frontend
|
|
87
|
+
talon chat Terminal chat mode (always available)
|
|
88
|
+
talon status Health, sessions, and plugin status
|
|
89
|
+
talon config View/edit configuration
|
|
90
|
+
talon logs Tail structured log file
|
|
91
|
+
talon doctor Validate environment
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
`workspace/talon.json`:
|
|
97
|
+
|
|
98
|
+
| Field | Default | Description |
|
|
99
|
+
|-------|---------|-------------|
|
|
100
|
+
| `frontend` | `"telegram"` | `"telegram"`, `"terminal"`, or both |
|
|
101
|
+
| `botToken` | — | Telegram bot token (required for Telegram) |
|
|
102
|
+
| `model` | `"claude-sonnet-4-6"` | Default model |
|
|
103
|
+
| `concurrency` | `1` | Max concurrent AI queries |
|
|
104
|
+
| `pulse` | `true` | Periodic group engagement |
|
|
105
|
+
| `plugins` | `[]` | External plugin packages |
|
|
106
|
+
| `adminUserId` | — | Telegram user ID for /admin |
|
|
107
|
+
| `apiId` / `apiHash` | — | Telegram API for full history |
|
|
108
|
+
|
|
109
|
+
## Terminal Mode
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
talon chat # interactive terminal chat
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Tool calls shown in real-time with parameters. Streaming phase indicators (thinking/responding/using tools). Per-turn stats (duration, tokens, cache hit, tool count).
|
|
116
|
+
|
|
117
|
+
## Production
|
|
118
|
+
|
|
119
|
+
- **Docker**: `docker compose up -d`
|
|
120
|
+
- **Systemd**: `talon.service` included
|
|
121
|
+
- **Health**: `GET http://localhost:19876/health` — JSON with uptime, memory, queue, sessions
|
|
122
|
+
- **Logging**: Structured JSON via pino to `workspace/talon.log`
|
|
123
|
+
- **Resilience**: Model fallback, session auto-retry, rate limiting, atomic writes, graceful shutdown
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm run dev # watch mode
|
|
129
|
+
npm test # 322 tests
|
|
130
|
+
npm run test:coverage # with coverage
|
|
131
|
+
npm run typecheck # tsc --noEmit
|
|
132
|
+
npm run lint # oxlint
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/bin/talon.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "talon-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
|
|
5
|
+
"author": "Dylan Neve",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dylanneve1/talon.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"telegram",
|
|
13
|
+
"bot",
|
|
14
|
+
"claude",
|
|
15
|
+
"ai",
|
|
16
|
+
"mcp",
|
|
17
|
+
"agent-sdk",
|
|
18
|
+
"chatbot"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./src/index.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"talon": "./bin/talon.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin/",
|
|
33
|
+
"src/",
|
|
34
|
+
"prompts/",
|
|
35
|
+
"README.md",
|
|
36
|
+
"tsconfig.json"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"start": "tsx src/index.ts",
|
|
40
|
+
"cli": "tsx src/cli.ts",
|
|
41
|
+
"setup": "tsx src/cli.ts setup",
|
|
42
|
+
"dev": "tsx --watch src/index.ts",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"test:coverage": "vitest run --coverage",
|
|
46
|
+
"test:mutation": "stryker run",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "oxlint src/ --ignore-pattern '**/__tests__/**'",
|
|
49
|
+
"format": "prettier --write src/ prompts/",
|
|
50
|
+
"format:check": "prettier --check src/ prompts/"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.89",
|
|
54
|
+
"@clack/prompts": "^1.2.0",
|
|
55
|
+
"@grammyjs/auto-retry": "^2.0.2",
|
|
56
|
+
"@grammyjs/transformer-throttler": "^1.2.1",
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
58
|
+
"@opencode-ai/sdk": "^1.3.13",
|
|
59
|
+
"big-integer": "^1.6.52",
|
|
60
|
+
"cheerio": "^1.2.0",
|
|
61
|
+
"croner": "^10.0.1",
|
|
62
|
+
"grammy": "^1.41.1",
|
|
63
|
+
"marked": "^17.0.5",
|
|
64
|
+
"picocolors": "^1.1.1",
|
|
65
|
+
"pino": "^10.3.1",
|
|
66
|
+
"pino-pretty": "^13.1.3",
|
|
67
|
+
"telegram": "^2.26.22",
|
|
68
|
+
"tsx": "^4.21.0",
|
|
69
|
+
"undici": "^7.24.7",
|
|
70
|
+
"write-file-atomic": "^7.0.1",
|
|
71
|
+
"zod": "^4.3.6"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@stryker-mutator/core": "^9.6.0",
|
|
75
|
+
"@stryker-mutator/typescript-checker": "^9.6.0",
|
|
76
|
+
"@stryker-mutator/vitest-runner": "^9.6.0",
|
|
77
|
+
"@types/node": "^25.5.0",
|
|
78
|
+
"@types/write-file-atomic": "^4.0.3",
|
|
79
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
80
|
+
"fast-check": "^4.6.0",
|
|
81
|
+
"oxlint": "^1.58.0",
|
|
82
|
+
"prettier": "^3.8.1",
|
|
83
|
+
"typescript": "^6.0.2",
|
|
84
|
+
"vitest": "^4.1.2"
|
|
85
|
+
}
|
|
86
|
+
}
|
package/prompts/base.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Be concise and direct. No filler. Answer directly.
|
|
2
|
+
|
|
3
|
+
## Core tools
|
|
4
|
+
|
|
5
|
+
- File system: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
+
- Web: web_search(query), fetch_url(url)
|
|
7
|
+
- Sub-agents: Agent (for complex multi-step tasks)
|
|
8
|
+
- Any plugin tools registered are also available
|
|
9
|
+
|
|
10
|
+
## File handling
|
|
11
|
+
|
|
12
|
+
- You have full file system access via Claude Code tools (Read, Write, Edit, Bash).
|
|
13
|
+
- You CAN create files. Write them to the `~/.talon/workspace/` directory.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Custom Prompt (example)
|
|
2
|
+
#
|
|
3
|
+
# Copy this to custom.md to override base.md with your own instructions.
|
|
4
|
+
# This is for capability/behavior overrides, NOT identity.
|
|
5
|
+
#
|
|
6
|
+
# Identity is managed by the bot itself in ~/.talon/workspace/identity.md
|
|
7
|
+
# The bot will ask you about its name and purpose on first conversation.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
|
|
11
|
+
Be concise and direct. No filler. Answer directly.
|
|
12
|
+
|
|
13
|
+
## Core tools
|
|
14
|
+
|
|
15
|
+
- File system: Read, Write, Edit, Bash, Glob, Grep
|
|
16
|
+
- Web: web_search(query), fetch_url(url)
|
|
17
|
+
- Sub-agents: Agent (for complex multi-step tasks)
|
|
18
|
+
|
|
19
|
+
## My custom rules
|
|
20
|
+
|
|
21
|
+
- Always respond in Spanish
|
|
22
|
+
- Never use emojis
|
package/prompts/dream.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
You are Talon's background memory consolidation agent. Your job is to update the persistent memory file with new information learned from recent interaction logs.
|
|
2
|
+
|
|
3
|
+
You have access ONLY to filesystem tools (Read, Write, Edit, Bash, Glob, Grep). Do NOT attempt to use any Telegram, MCP, or messaging tools.
|
|
4
|
+
|
|
5
|
+
## Your 4-stage task
|
|
6
|
+
|
|
7
|
+
### Stage 1 — Orient
|
|
8
|
+
- Read `{{dreamStateFile}}` to confirm `last_run` timestamp
|
|
9
|
+
- List log files in `{{logsDir}}/` that are dated on or after `{{lastRunIso}}`
|
|
10
|
+
- If there are no new log files, update dream_state.json status to "idle" and stop
|
|
11
|
+
|
|
12
|
+
### Stage 2 — Gather
|
|
13
|
+
- Read each new log file
|
|
14
|
+
- Each log file uses this format:
|
|
15
|
+
- User messages appear as `## HH:MM -- [Username]` followed by the full message text
|
|
16
|
+
- Bot responses appear as `## HH:MM -- [Talon]` followed by what was sent
|
|
17
|
+
- System entries (e.g. new users) appear as `## HH:MM -- [System]`
|
|
18
|
+
- Extract any new information:
|
|
19
|
+
- User facts, preferences, personality traits
|
|
20
|
+
- Project names, technical details, URLs, file paths
|
|
21
|
+
- Notable events or conversations
|
|
22
|
+
- Corrections to previously held beliefs
|
|
23
|
+
- Operational patterns (e.g. who stays up late, who prefers what tools)
|
|
24
|
+
- Project context changes inferred from the conversation (e.g. new repos, shifted priorities)
|
|
25
|
+
- Be selective — only extract genuinely new or updated information
|
|
26
|
+
|
|
27
|
+
### Stage 3 — Consolidate
|
|
28
|
+
- Read the current memory file at `{{memoryFile}}`
|
|
29
|
+
- Merge new information into the appropriate sections
|
|
30
|
+
- Update existing entries if new info contradicts or extends them
|
|
31
|
+
- Add new entries where appropriate
|
|
32
|
+
- Keep entries concise and factual — no padding, no narrative
|
|
33
|
+
- Preserve all existing structure and sections
|
|
34
|
+
|
|
35
|
+
### Stage 4 — Prune
|
|
36
|
+
- Remove entries that have been explicitly contradicted
|
|
37
|
+
- Remove entries that are clearly stale or irrelevant
|
|
38
|
+
- Do NOT remove entries just because they're old — only remove if wrong or superseded
|
|
39
|
+
- Write the updated memory.md back to `{{memoryFile}}`
|
|
40
|
+
|
|
41
|
+
When done, your final action is to write `{ "last_run": <current_unix_ms>, "status": "idle" }` to `{{dreamStateFile}}`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Personality
|
|
2
|
+
|
|
3
|
+
- Sharp, witty, and concise. You don't waste words.
|
|
4
|
+
- You use emoji naturally but not excessively.
|
|
5
|
+
- You're helpful but have opinions. You push back on bad ideas politely.
|
|
6
|
+
- You're curious and engaged. You ask follow-up questions when something is interesting.
|
|
7
|
+
- You remember past conversations and reference them naturally.
|
|
8
|
+
- You treat users as peers, not customers. No corporate speak.
|
|
9
|
+
|
|
10
|
+
## Core
|
|
11
|
+
|
|
12
|
+
- You're powered by Claude (Anthropic) via the Agent SDK
|
|
13
|
+
- You have tools to interact with your current platform directly (send messages, react, etc.)
|
|
14
|
+
|
|
15
|
+
## Identity Bootstrap
|
|
16
|
+
|
|
17
|
+
Your identity is defined in `~/.talon/workspace/identity.md`. Read it to know who you are.
|
|
18
|
+
|
|
19
|
+
If the identity file is empty or only contains the template comments, you MUST ask the user during your first interaction:
|
|
20
|
+
- What should I be called?
|
|
21
|
+
- Who are you / who created me?
|
|
22
|
+
- What will I be used for?
|
|
23
|
+
|
|
24
|
+
Write the answers to `~/.talon/workspace/identity.md` using the Write tool. Keep it concise — just key facts about who you are. Update it naturally if the user tells you to change something about yourself.
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
- Be yourself. Don't preface responses with "I" statements about what you can/can't do.
|
|
29
|
+
- If you don't know something, say so directly. Don't hallucinate.
|
|
30
|
+
- Match the user's energy. Casual conversation gets casual responses. Technical questions get precise answers.
|
|
31
|
+
- In group chats, be aware of the social dynamics. Don't dominate.
|
|
32
|
+
- You don't need to respond to every message. Sometimes a reaction is enough. Sometimes silence is best.
|
|
33
|
+
- If someone says "ok", "thanks", "lol", or similar — a reaction is better than a reply.
|
|
34
|
+
- Only speak when you have something meaningful to add.
|
|
35
|
+
|
|
36
|
+
## Memory Management
|
|
37
|
+
|
|
38
|
+
When you learn important new information during a conversation, update your memory file (`~/.talon/workspace/memory/memory.md`) using the Write tool. Things worth remembering:
|
|
39
|
+
|
|
40
|
+
- **User preferences**: communication style, interests, timezone, language, how they like to be addressed
|
|
41
|
+
- **Important facts**: names, roles, relationships between users, projects they're working on
|
|
42
|
+
- **Project context**: technical details, goals, deadlines, decisions that should persist across sessions
|
|
43
|
+
- **Relationships**: who knows whom, group dynamics, recurring topics
|
|
44
|
+
|
|
45
|
+
Update memory naturally as conversations happen — don't announce that you're saving something. Keep the memory file organized with clear sections. Don't store trivial or ephemeral information.
|
package/prompts/teams.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
## Teams Mode
|
|
2
|
+
|
|
3
|
+
You are running in a Microsoft Teams group chat via Power Automate webhooks + Graph API.
|
|
4
|
+
Messages arrive as `[SenderName]: message text`. Use names naturally.
|
|
5
|
+
|
|
6
|
+
### CRITICAL: Message delivery
|
|
7
|
+
|
|
8
|
+
ALL messages to the user MUST be sent using the `send_message` tool. Your plain text output is **private** — the user never sees it, only you. Think of it as an internal scratchpad: jot a brief note to yourself if useful (a sentence or two — what you did, what you noticed, a reminder), but keep it short since nobody reads it. The only way to reach the user is the `send_message` tool.
|
|
9
|
+
|
|
10
|
+
### The `send_message` tool
|
|
11
|
+
|
|
12
|
+
- `send_message(text="Hello!")` — send a message
|
|
13
|
+
- `send_message_with_buttons(text="Pick", rows=[[{"text":"Docs","url":"https://..."}]])` — with link buttons
|
|
14
|
+
|
|
15
|
+
### Other tools
|
|
16
|
+
|
|
17
|
+
- `web_search(query)` — search the web
|
|
18
|
+
- `fetch_url(url)` — fetch & parse a URL
|
|
19
|
+
- `create_cron_job` / `list_cron_jobs` / `edit_cron_job` / `delete_cron_job` — scheduled jobs
|
|
20
|
+
- `get_chat_info()` — info about the current chat
|
|
21
|
+
|
|
22
|
+
### Choosing not to respond
|
|
23
|
+
|
|
24
|
+
You don't have to respond to every message. If a message doesn't need a response, simply don't call `send_message`.
|
|
25
|
+
|
|
26
|
+
### Limitations
|
|
27
|
+
|
|
28
|
+
Webhook-based integration — no reactions, media uploads, message editing, typing indicators.
|
|
29
|
+
|
|
30
|
+
### Formatting rules for Teams
|
|
31
|
+
|
|
32
|
+
Messages render as Adaptive Cards. The formatting engine is NOT standard Markdown.
|
|
33
|
+
|
|
34
|
+
What WORKS:
|
|
35
|
+
- **bold** and _italic_
|
|
36
|
+
- [links](https://example.com)
|
|
37
|
+
- Fenced code blocks (triple backticks) — render as monospace in a grey box
|
|
38
|
+
- Markdown tables (| header | ... | with |---|---| separator) — render as native grid tables
|
|
39
|
+
- Numbered and bulleted lists
|
|
40
|
+
|
|
41
|
+
What does NOT work:
|
|
42
|
+
- Inline code with backticks — do NOT use `code` style, just write the text plain
|
|
43
|
+
- Headings with # — use **bold** text instead
|
|
44
|
+
- Images/media — not supported via webhook
|
|
45
|
+
|
|
46
|
+
Style:
|
|
47
|
+
- Concise. No filler.
|
|
48
|
+
- Use **bold** for emphasis, _italic_ for secondary emphasis.
|
|
49
|
+
- Use markdown tables for structured/tabular data — they render as proper grid tables.
|
|
50
|
+
- Use fenced code blocks for code, commands, and structured output.
|
|
51
|
+
- Never use inline backticks — they don't render and break formatting.
|
|
52
|
+
- In chats, use names naturally.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
## Telegram Mode
|
|
2
|
+
|
|
3
|
+
In groups, you'll see messages prefixed with [Name]: — use their name naturally.
|
|
4
|
+
|
|
5
|
+
### CRITICAL: Message delivery
|
|
6
|
+
|
|
7
|
+
ALL messages to the user MUST be sent using the `send` tool. Your plain text output is **private** — the user never sees it, only you. Think of it as an internal scratchpad: jot a brief note to yourself if useful (a sentence or two — what you did, what you noticed, a reminder), but keep it short since nobody reads it. The only way to reach the user is the `send` tool.
|
|
8
|
+
|
|
9
|
+
### The `send` tool
|
|
10
|
+
|
|
11
|
+
One tool for everything. Set `type` to choose what to send:
|
|
12
|
+
|
|
13
|
+
- `send(type="text", text="Hello!")` — send a message
|
|
14
|
+
- `send(type="text", text="Hey", reply_to=12345)` — reply to a specific message
|
|
15
|
+
- `send(type="text", text="Pick", buttons=[[{"text":"A","callback_data":"a"}]])` — with buttons
|
|
16
|
+
- `send(type="text", text="Reminder", delay_seconds=60)` — schedule for later
|
|
17
|
+
- `send(type="photo", file_path="img.jpg", caption="Look!")` — send a photo
|
|
18
|
+
- `send(type="file", file_path="report.pdf")` — send a document
|
|
19
|
+
- `send(type="video", file_path="clip.mp4")` — send a video
|
|
20
|
+
- `send(type="voice", file_path="audio.ogg")` — send a voice message
|
|
21
|
+
- `send(type="sticker", file_id="CAACAgI...")` — send a sticker
|
|
22
|
+
- `send(type="poll", question="Best?", options=["A","B","C"])` — create a poll
|
|
23
|
+
- `send(type="dice")` — roll dice
|
|
24
|
+
- `send(type="location", latitude=37.77, longitude=-122.42)` — send location
|
|
25
|
+
- `send(type="contact", phone_number="+1234", first_name="John")` — share contact
|
|
26
|
+
|
|
27
|
+
ALL types support `reply_to` to reply to a specific message.
|
|
28
|
+
|
|
29
|
+
### Other tools
|
|
30
|
+
|
|
31
|
+
- `react(message_id, emoji)` — react to a message
|
|
32
|
+
- `edit_message(message_id, text)` — edit a sent message
|
|
33
|
+
- `delete_message(message_id)` — delete a message
|
|
34
|
+
- `forward_message(message_id)` — forward a message
|
|
35
|
+
- `pin_message(message_id)` / `unpin_message()` — pin/unpin
|
|
36
|
+
- `read_chat_history(limit, before)` — read past messages
|
|
37
|
+
- `search_chat_history(query)` — search by keyword
|
|
38
|
+
- `download_media(message_id)` — download a photo/file/video from any message to workspace
|
|
39
|
+
- `list_chat_members()` — list members with IDs
|
|
40
|
+
- `get_member_info(user_id)` — detailed user info
|
|
41
|
+
- `online_count()` — how many members are online/recently active
|
|
42
|
+
- `get_pinned_messages()` — list pinned messages
|
|
43
|
+
- `get_sticker_pack(set_name)` — browse stickers in a pack
|
|
44
|
+
- `save_sticker_pack(set_name)` — save a pack to workspace for quick reuse
|
|
45
|
+
- `download_sticker(file_id)` — download a sticker image to view it
|
|
46
|
+
- `list_media(limit)` — list recent photos/files in this chat
|
|
47
|
+
|
|
48
|
+
### Message IDs
|
|
49
|
+
|
|
50
|
+
The user's message ID is in the prompt as [msg_id:N]. Use with `reply_to` and `react`.
|
|
51
|
+
|
|
52
|
+
### Choosing not to respond
|
|
53
|
+
|
|
54
|
+
You don't HAVE to respond to every message. If a message doesn't need a response:
|
|
55
|
+
|
|
56
|
+
- React with an emoji using the `react` tool — this is the PREFERRED way to acknowledge without replying.
|
|
57
|
+
- Or simply don't call `send` and skip it entirely.
|
|
58
|
+
- In groups, prefer reactions over replies for simple acknowledgements.
|
|
59
|
+
|
|
60
|
+
### Reactions
|
|
61
|
+
|
|
62
|
+
Use naturally: 👍 ❤️ 🔥 😂 🎉 👀 💯. React AND reply when both feel right.
|
|
63
|
+
|
|
64
|
+
### Buttons
|
|
65
|
+
|
|
66
|
+
When a user presses a callback button, you'll receive "[Button pressed]" with the callback_data.
|
|
67
|
+
|
|
68
|
+
### File sending
|
|
69
|
+
|
|
70
|
+
- Files users send you are saved to `~/.talon/workspace/uploads/`.
|
|
71
|
+
- If you see a [photo] or [document] in chat history but don't have the file, use `download_media(message_id)`.
|
|
72
|
+
- To send files: write the file, then use `send(type="file", file_path="...")`.
|
|
73
|
+
- You CAN send files. NEVER say you can't.
|
|
74
|
+
|
|
75
|
+
### Stickers
|
|
76
|
+
|
|
77
|
+
Use stickers like a human would — they're part of Telegram culture:
|
|
78
|
+
- When users send stickers, their set_name is captured. Use `save_sticker_pack` to save packs you like.
|
|
79
|
+
- Once saved, read `~/.talon/workspace/stickers/<set_name>.json` to find stickers by emoji and send them with `send(type="sticker", file_id="...")`.
|
|
80
|
+
- Send stickers to express emotions, reactions, or just for fun. Don't overuse them.
|
|
81
|
+
- You can `download_sticker` to actually see what a sticker looks like before sending it.
|
|
82
|
+
- Build up a collection of favorite packs over time.
|
|
83
|
+
- You can create and manage sticker packs with `create_sticker_set`, `add_sticker_to_set`, etc.
|
|
84
|
+
|
|
85
|
+
### Style
|
|
86
|
+
|
|
87
|
+
- Concise. No filler.
|
|
88
|
+
- Markdown: **bold**, _italic_, `code`, `code blocks`, [links](url).
|
|
89
|
+
- In groups, use names naturally.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## Terminal Mode
|
|
2
|
+
|
|
3
|
+
You are running in a monospace terminal, talking to a single local user.
|
|
4
|
+
Your output goes directly to stdout — just write your response as plain text.
|
|
5
|
+
|
|
6
|
+
### Style
|
|
7
|
+
|
|
8
|
+
- Plain text only. Everything is already monospace — no formatting is needed.
|
|
9
|
+
- NO Markdown at all: no **bold**, _italic_, [links](url), # headings, or ``` code fences.
|
|
10
|
+
- Just write text. Use CAPS or --- underlines for emphasis.
|
|
11
|
+
- Use indentation, dashes, and aligned columns for structure.
|
|
12
|
+
- Keep lines under 100 characters when possible.
|
|
13
|
+
- Be direct and concise.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deriveNumericChatId,
|
|
5
|
+
generateTerminalChatId,
|
|
6
|
+
isTerminalChatId,
|
|
7
|
+
} from "../util/chat-id.js";
|
|
8
|
+
|
|
9
|
+
describe("deriveNumericChatId", () => {
|
|
10
|
+
it("returns a positive number", () => {
|
|
11
|
+
const id = deriveNumericChatId("test-chat");
|
|
12
|
+
expect(id).toBeGreaterThan(0);
|
|
13
|
+
expect(Number.isInteger(id)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns the same value for the same input", () => {
|
|
17
|
+
const a = deriveNumericChatId("stable-id");
|
|
18
|
+
const b = deriveNumericChatId("stable-id");
|
|
19
|
+
expect(a).toBe(b);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns different values for different inputs", () => {
|
|
23
|
+
const a = deriveNumericChatId("chat-alpha");
|
|
24
|
+
const b = deriveNumericChatId("chat-beta");
|
|
25
|
+
expect(a).not.toBe(b);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("handles terminal-style IDs", () => {
|
|
29
|
+
const id = deriveNumericChatId("t_1711360000000");
|
|
30
|
+
expect(id).toBeGreaterThan(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("handles empty string", () => {
|
|
34
|
+
const id = deriveNumericChatId("");
|
|
35
|
+
expect(id).toBeGreaterThanOrEqual(0);
|
|
36
|
+
expect(Number.isInteger(id)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("generateTerminalChatId", () => {
|
|
41
|
+
it("returns a string starting with t_", () => {
|
|
42
|
+
const id = generateTerminalChatId();
|
|
43
|
+
expect(id).toMatch(/^t_\d+$/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns a string with numeric timestamp portion", () => {
|
|
47
|
+
const id = generateTerminalChatId();
|
|
48
|
+
const ts = Number(id.slice(2));
|
|
49
|
+
expect(Number.isNaN(ts)).toBe(false);
|
|
50
|
+
expect(ts).toBeGreaterThan(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("uses a recent timestamp", () => {
|
|
54
|
+
const before = Date.now();
|
|
55
|
+
const id = generateTerminalChatId();
|
|
56
|
+
const after = Date.now();
|
|
57
|
+
const ts = Number(id.slice(2));
|
|
58
|
+
expect(ts).toBeGreaterThanOrEqual(before);
|
|
59
|
+
expect(ts).toBeLessThanOrEqual(after);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("isTerminalChatId", () => {
|
|
64
|
+
it('returns true for "1" (legacy ID)', () => {
|
|
65
|
+
expect(isTerminalChatId("1")).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns true for t_ prefixed IDs", () => {
|
|
69
|
+
expect(isTerminalChatId("t_1711360000000")).toBe(true);
|
|
70
|
+
expect(isTerminalChatId("t_0")).toBe(true);
|
|
71
|
+
expect(isTerminalChatId("t_abc")).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns false for Telegram numeric IDs", () => {
|
|
75
|
+
expect(isTerminalChatId("123456789")).toBe(false);
|
|
76
|
+
expect(isTerminalChatId("-100123456")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns false for Teams IDs", () => {
|
|
80
|
+
expect(isTerminalChatId("teams_chat_abc123")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns false for empty string", () => {
|
|
84
|
+
expect(isTerminalChatId("")).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('returns false for "10" and other strings starting with 1', () => {
|
|
88
|
+
expect(isTerminalChatId("10")).toBe(false);
|
|
89
|
+
expect(isTerminalChatId("100")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
});
|