mono-pilot 0.2.9 → 0.2.12
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 +270 -7
- package/dist/src/agents-paths.js +36 -0
- package/dist/src/brief/blocks.js +83 -0
- package/dist/src/brief/defaults.js +60 -0
- package/dist/src/brief/frontmatter.js +53 -0
- package/dist/src/brief/paths.js +10 -0
- package/dist/src/brief/reflection.js +27 -0
- package/dist/src/cli.js +62 -5
- package/dist/src/cluster/bus.js +102 -0
- package/dist/src/cluster/follower.js +137 -0
- package/dist/src/cluster/init.js +182 -0
- package/dist/src/cluster/leader.js +97 -0
- package/dist/src/cluster/log.js +49 -0
- package/dist/src/cluster/protocol.js +34 -0
- package/dist/src/cluster/services/bus.js +243 -0
- package/dist/src/cluster/services/embedding.js +12 -0
- package/dist/src/cluster/socket.js +86 -0
- package/dist/src/cluster/test-bus.js +175 -0
- package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
- package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
- package/dist/src/cluster_v2/connection.js +159 -0
- package/dist/src/cluster_v2/connection.test.js +55 -0
- package/dist/src/cluster_v2/events.js +102 -0
- package/dist/src/cluster_v2/index.js +2 -0
- package/dist/src/cluster_v2/observability.js +99 -0
- package/dist/src/cluster_v2/observability.test.js +46 -0
- package/dist/src/cluster_v2/rpc.js +389 -0
- package/dist/src/cluster_v2/rpc.test.js +110 -0
- package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
- package/dist/src/cluster_v2/runtime.js +531 -0
- package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
- package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
- package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
- package/dist/src/cluster_v2/services/bus.js +450 -0
- package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
- package/dist/src/cluster_v2/services/discord/collector.js +569 -0
- package/dist/src/cluster_v2/services/discord/index.js +1 -0
- package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
- package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
- package/dist/src/cluster_v2/services/embedding.js +66 -0
- package/dist/src/cluster_v2/services/registry-cache.js +107 -0
- package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
- package/dist/src/cluster_v2/services/registry.js +36 -0
- package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
- package/dist/src/cluster_v2/services/twitter/index.js +1 -0
- package/dist/src/config/digest.js +78 -0
- package/dist/src/config/discord.js +143 -0
- package/dist/src/config/image-gen.js +48 -0
- package/dist/src/config/mono-pilot.js +31 -0
- package/dist/src/config/twitter.js +100 -0
- package/dist/src/extensions/cluster.js +311 -0
- package/dist/src/extensions/commands/build-memory.js +76 -0
- package/dist/src/extensions/commands/digest/backfill.js +779 -0
- package/dist/src/extensions/commands/digest/index.js +1133 -0
- package/dist/src/extensions/commands/image-model.js +214 -0
- package/dist/src/extensions/game/bus-injection.js +47 -0
- package/dist/src/extensions/game/identity.js +83 -0
- package/dist/src/extensions/game/mailbox.js +61 -0
- package/dist/src/extensions/game/system-prompt.js +134 -0
- package/dist/src/extensions/game/tools.js +28 -0
- package/dist/src/extensions/lifecycle.js +337 -0
- package/dist/src/extensions/mode-runtime.js +26 -2
- package/dist/src/extensions/mono-game.js +66 -0
- package/dist/src/extensions/mono-pilot.js +100 -18
- package/dist/src/extensions/nvim.js +47 -0
- package/dist/src/extensions/session-hints.js +60 -35
- package/dist/src/extensions/sftp.js +897 -0
- package/dist/src/extensions/status.js +676 -0
- package/dist/src/extensions/system-events.js +478 -0
- package/dist/src/extensions/system-prompt.js +24 -14
- package/dist/src/extensions/user-message.js +94 -50
- package/dist/src/lsp/client.js +235 -0
- package/dist/src/lsp/index.js +165 -0
- package/dist/src/lsp/runtime.js +67 -0
- package/dist/src/lsp/server.js +242 -0
- package/dist/src/mcp/config.js +112 -0
- package/dist/src/{utils/mcp-client.js → mcp/protocol.js} +1 -100
- package/dist/src/mcp/servers.js +90 -0
- package/dist/src/memory/build-memory.js +103 -0
- package/dist/src/memory/config/defaults.js +55 -0
- package/dist/src/memory/config/loader.js +29 -0
- package/dist/src/memory/config/paths.js +9 -0
- package/dist/src/memory/config/resolve.js +90 -0
- package/dist/src/memory/config/types.js +1 -0
- package/dist/src/memory/embeddings/batch-runner.js +39 -0
- package/dist/src/memory/embeddings/cache.js +47 -0
- package/dist/src/memory/embeddings/chunk-limits.js +26 -0
- package/dist/src/memory/embeddings/input-limits.js +48 -0
- package/dist/src/memory/embeddings/local.js +108 -0
- package/dist/src/memory/embeddings/types.js +1 -0
- package/dist/src/memory/index-manager.js +552 -0
- package/dist/src/memory/indexing/embeddings.js +67 -0
- package/dist/src/memory/indexing/files.js +180 -0
- package/dist/src/memory/indexing/index-file.js +105 -0
- package/dist/src/memory/log.js +38 -0
- package/dist/src/memory/paths.js +15 -0
- package/dist/src/memory/runtime/index.js +299 -0
- package/dist/src/memory/runtime/thread.js +116 -0
- package/dist/src/memory/search/fts.js +57 -0
- package/dist/src/memory/search/hybrid.js +50 -0
- package/dist/src/memory/search/text.js +30 -0
- package/dist/src/memory/search/vector.js +43 -0
- package/dist/src/memory/session/content-hash.js +7 -0
- package/dist/src/memory/session/entry.js +33 -0
- package/dist/src/memory/session/flush-policy.js +34 -0
- package/dist/src/memory/session/hook.js +191 -0
- package/dist/src/memory/session/paths.js +15 -0
- package/dist/src/memory/session/session-reader.js +88 -0
- package/dist/src/memory/session/transcript/content-hash.js +7 -0
- package/dist/src/memory/session/transcript/entry.js +28 -0
- package/dist/src/memory/session/transcript/flush.js +56 -0
- package/dist/src/memory/session/transcript/paths.js +28 -0
- package/dist/src/memory/session/transcript/reader.js +112 -0
- package/dist/src/memory/session/transcript/state.js +31 -0
- package/dist/src/memory/store/schema.js +89 -0
- package/dist/src/memory/store/sqlite.js +89 -0
- package/dist/src/memory/types.js +1 -0
- package/dist/src/memory/warm.js +25 -0
- package/dist/src/rules/discovery.js +41 -0
- package/dist/{tools → src/tools}/README.md +29 -3
- package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
- package/dist/{tools → src/tools}/apply-patch.js +174 -104
- package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
- package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
- package/dist/src/tools/ast-grep.js +357 -0
- package/dist/src/tools/brief-write.js +122 -0
- package/dist/src/tools/bus-send.js +100 -0
- package/dist/{tools → src/tools}/call-mcp-tool.js +40 -124
- package/dist/src/tools/codex-apply-patch-description.md +52 -0
- package/dist/src/tools/codex-apply-patch.js +540 -0
- package/dist/{tools → src/tools}/delete.js +24 -0
- package/dist/src/tools/exit-plan-mode.js +83 -0
- package/dist/{tools → src/tools}/fetch-mcp-resource.js +56 -100
- package/dist/src/tools/generate-image.js +567 -0
- package/dist/{tools → src/tools}/glob.js +55 -1
- package/dist/{tools → src/tools}/list-mcp-resources.js +46 -57
- package/dist/{tools → src/tools}/list-mcp-tools.js +52 -63
- package/dist/src/tools/ls.js +48 -0
- package/dist/src/tools/lsp-diagnostics.js +67 -0
- package/dist/src/tools/lsp-symbols.js +54 -0
- package/dist/src/tools/mailbox.js +85 -0
- package/dist/src/tools/memory-get.js +90 -0
- package/dist/src/tools/memory-search.js +180 -0
- package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
- package/dist/{tools → src/tools}/read-file.js +8 -19
- package/dist/{tools → src/tools}/rg.js +10 -20
- package/dist/{tools → src/tools}/shell.js +19 -42
- package/dist/{tools → src/tools}/subagent.js +255 -6
- package/dist/{tools → src/tools}/switch-mode.js +37 -6
- package/dist/{tools → src/tools}/web-fetch.js +105 -7
- package/dist/{tools → src/tools}/web-search.js +29 -1
- package/package.json +21 -9
- /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
- /package/dist/{tools → src/tools}/rg.test.js +0 -0
- /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
- /package/dist/{tools → src/tools}/semantic-search.js +0 -0
- /package/dist/{tools → src/tools}/shell-description.md +0 -0
- /package/dist/{tools → src/tools}/subagent-description.md +0 -0
package/README.md
CHANGED
|
@@ -27,9 +27,25 @@ mono-pilot
|
|
|
27
27
|
# Interactive
|
|
28
28
|
mono-pilot
|
|
29
29
|
|
|
30
|
+
# Interactive game profile (murder mystery, role-play)
|
|
31
|
+
mono-pilot --mono-mode game
|
|
32
|
+
|
|
30
33
|
# One-shot prompt
|
|
31
34
|
mono-pilot -p "Refactor this module"
|
|
32
35
|
|
|
36
|
+
# One-shot game prompt
|
|
37
|
+
mono-pilot --mono-mode game -p "你现在是侦探,先做自我介绍"
|
|
38
|
+
|
|
39
|
+
# Game mode with explicit channel override
|
|
40
|
+
mono-pilot --mono-mode game --game-channel game:office-004
|
|
41
|
+
|
|
42
|
+
# Game mode with identity auto-load from workspace
|
|
43
|
+
# expects .mono-game/profile.json and optional .mono-game/identity.md
|
|
44
|
+
mono-pilot --mono-mode game
|
|
45
|
+
|
|
46
|
+
# Optional: limit tools per role
|
|
47
|
+
# profile.json example: { "displayName": "侦探", "tools": ["BusSend", "MailBox", "ReadFile"] }
|
|
48
|
+
|
|
33
49
|
# Continue previous session
|
|
34
50
|
mono-pilot --continue
|
|
35
51
|
```
|
|
@@ -46,9 +62,14 @@ If you pass `--tools`, MonoPilot removes built-in `edit`, `write`, `read`, `grep
|
|
|
46
62
|
|
|
47
63
|
- `src/cli.ts` – launcher that wraps `pi`
|
|
48
64
|
- `src/extensions/mono-pilot.ts` – extension entrypoint (tool wiring)
|
|
65
|
+
- `src/extensions/mono-game.ts` – game-oriented extension entrypoint (story + bus tools)
|
|
49
66
|
- `src/extensions/system-prompt.ts` – provider-agnostic prompt stack
|
|
50
67
|
- `src/extensions/user-message.ts` – user message envelope assembly
|
|
51
|
-
- `
|
|
68
|
+
- `src/brief/` – persistent agent memory ("brief" system), inspired by [letta-ai/letta-code](https://github.com/letta-ai/letta-code.git)'s memory architecture, renamed from "memory" to "brief" to distinguish condensed knowledge from conversation history
|
|
69
|
+
- `src/memory/` – memory search indexing + retrieval (builtin, SQLite + FTS)
|
|
70
|
+
- `src/mcp/` – config loading, JSON-RPC transport, server resolution
|
|
71
|
+
- `src/rules/` – rule file discovery (shared by envelope and session hints)
|
|
72
|
+
- `src/tools/` – tool implementations and descriptions (see `src/tools/README.md`)
|
|
52
73
|
|
|
53
74
|
## Cursor-styled tools
|
|
54
75
|
|
|
@@ -69,11 +90,15 @@ The full Cursor-styled tool list exposed by the extension:
|
|
|
69
90
|
- `Shell` – execute shell commands in the workspace
|
|
70
91
|
- `Glob` – find paths by glob pattern
|
|
71
92
|
- `rg` – search file content with ripgrep
|
|
93
|
+
- `AstGrep` – search code structure with ast-grep (`run`/`scan`, read-only)
|
|
72
94
|
- `ReadFile` – read file content with pagination
|
|
73
95
|
- `Delete` – delete files or directories
|
|
74
96
|
- `SemanticSearch` – semantic search by intent
|
|
97
|
+
- `memory_search` – search indexed memory snippets
|
|
98
|
+
- `memory_get` – read a snippet from memory files
|
|
75
99
|
- `WebSearch` – search the web with snippets
|
|
76
100
|
- `WebFetch` – fetch and render web content
|
|
101
|
+
- `GenerateImage` – generate images via Gemini API or OpenRouter
|
|
77
102
|
- `AskQuestion` – collect structured multiple-choice answers
|
|
78
103
|
- `Subagent` – launch delegated subprocesses
|
|
79
104
|
- `ListMcpResources` – list MCP resources from config
|
|
@@ -81,21 +106,257 @@ The full Cursor-styled tool list exposed by the extension:
|
|
|
81
106
|
- `ListMcpTools` – discover MCP tools and schemas
|
|
82
107
|
- `CallMcpTool` – invoke MCP tools by name
|
|
83
108
|
- `SwitchMode` – switch interaction mode (`option + m`, cycles Plan → Ask → Agent)
|
|
84
|
-
- `
|
|
109
|
+
- `ExitPlanMode` – exit Plan mode and switch back to Agent mode
|
|
110
|
+
- `ApplyPatch` – apply single-file patches (supports diff-style @@ line hints)
|
|
111
|
+
- `CodexApplyPatch` – apply codex-style multi-file patches with `Add/Delete/Update/Move` operations
|
|
112
|
+
- `MailBox` – read queued bus messages (game mailbox)
|
|
85
113
|
|
|
86
114
|
## User rules
|
|
87
115
|
|
|
88
|
-
MonoPilot
|
|
116
|
+
MonoPilot injects user rules into the runtime envelope on each turn (handled by `src/extensions/user-message.ts`).
|
|
117
|
+
|
|
118
|
+
Rules are loaded from two locations:
|
|
89
119
|
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
- `~/.pi/rules/*.rule.txt` – user-level rules (applies to all projects)
|
|
121
|
+
- `.pi/rules/*.rule.txt` – project-level rules (workspace root)
|
|
122
|
+
|
|
123
|
+
When the same filename exists in both, the project rule wins. Each file becomes one `<user_rule>` block inside a `<rules>` envelope, sorted by filename. Empty files are ignored; the envelope is omitted if no rules are found.
|
|
94
124
|
|
|
95
125
|
## MCP
|
|
96
126
|
|
|
97
127
|
- The user message envelope issues a lightweight MCP server `initialize` request to collect server instructions.
|
|
98
128
|
- MCP tools then progressively load and surface resources, schemas, and execution only when needed.
|
|
129
|
+
- MCP configs are loaded from `.pi/mcp.json` (project) and `~/.pi/mcp.json` (user); project entries take precedence on name conflicts.
|
|
130
|
+
|
|
131
|
+
## Memory search
|
|
132
|
+
|
|
133
|
+
- Builtin memory search reads `~/.mono-pilot/config.json` (`memorySearch` field). If missing, defaults are used.
|
|
134
|
+
- Local embeddings use `node-llama-cpp`; configure `memorySearch.local.modelPath` (and optional `modelCacheDir`).
|
|
135
|
+
- Session flush triggers are configured via `memorySearch.flush` (`onSessionSwitch`, `onSessionCompact`, `deltaBytes`, `deltaMessages`).
|
|
136
|
+
- Use `/build-memory --mode full|dirty` to rebuild or incrementally sync the current agent's memory index partition.
|
|
137
|
+
|
|
138
|
+
## Cluster v2 logging
|
|
139
|
+
|
|
140
|
+
- Enable cluster_v2 with `MONO_PILOT_CLUSTER_V2=1` or `MONO_PILOT_CLUSTER_VERSION=2`.
|
|
141
|
+
- cluster_v2 logs are persisted to `~/.mono-pilot/logs/cluster_v2.YYYY-MM-DD.log`.
|
|
142
|
+
- Default behavior is file-only logging (keeps TUI clean). Set `MONO_PILOT_CLUSTER_V2_LOG_STDIO=1` to also mirror logs to stdout/stderr.
|
|
143
|
+
- `/cluster` operational subcommands include `status`, `services`, `reelect`, `stepdown`, and `close`.
|
|
144
|
+
|
|
145
|
+
## System events (TUI)
|
|
146
|
+
|
|
147
|
+
- MonoPilot keeps an in-session system event queue for low-disruption operational signals (for example SFTP, memory warmup/build/session flush, cluster failover, Discord collector progress, and Twitter pull status/failures).
|
|
148
|
+
- In interactive mode, info/warning/error system events show a bottom-right overlay toast (non-capturing), with latest-only replacement.
|
|
149
|
+
- Overlay can be dismissed with `Esc` or clicking header `[×]`; a 2-minute timeout remains as a fallback.
|
|
150
|
+
- Use `/events` to inspect recent events, `/events <N>` for more lines, and `/events clear` to reset.
|
|
151
|
+
|
|
152
|
+
## Discord intelligence collector (leader-local)
|
|
153
|
+
|
|
154
|
+
When `cluster_v2` is enabled, leader can run a local Discord IPC collector that subscribes to
|
|
155
|
+
message events and persists them as JSONL for intelligence gathering.
|
|
156
|
+
|
|
157
|
+
- No follower push
|
|
158
|
+
- No Discord channel interaction
|
|
159
|
+
- Local persistence only
|
|
160
|
+
|
|
161
|
+
Config in `~/.mono-pilot/config.json`:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"discord": {
|
|
166
|
+
"enabled": true,
|
|
167
|
+
"clientId": "YOUR_DISCORD_APP_ID",
|
|
168
|
+
"channels": [
|
|
169
|
+
{
|
|
170
|
+
"id": "123456789012345678",
|
|
171
|
+
"alias": "My Server / general"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"events": ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE"],
|
|
175
|
+
"scopes": ["rpc", "messages.read", "identify"],
|
|
176
|
+
"outputPath": "~/.mono-pilot/discord/messages.jsonl",
|
|
177
|
+
"includeRawPayload": false,
|
|
178
|
+
"systemEventBatchSize": 20
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Notes:
|
|
184
|
+
|
|
185
|
+
- Requires a locally running Discord desktop client.
|
|
186
|
+
- If `accessToken` is omitted, collector first tries cached auth from `~/.mono-pilot/auth.json`.
|
|
187
|
+
- If cache is missing/invalid, collector runs RPC `AUTHORIZE` + OAuth token exchange, then stores tokens back to `~/.mono-pilot/auth.json`.
|
|
188
|
+
- For first-time OAuth token exchange, many Discord apps require `clientSecret` (and sometimes `redirectUri`) in config.
|
|
189
|
+
- `accessToken` and cached tokens are never printed in logs.
|
|
190
|
+
- `channels[].alias` is persisted as `channelAlias` in each JSONL event record.
|
|
191
|
+
- Collector enriches records by resolving `channelId -> channelName` and `guildId -> guildName` via RPC (`GET_CHANNEL` / `GET_GUILD`) when available.
|
|
192
|
+
- Collector writes with daily rotation: `outputPath` is treated as base name and actual files are `<base>.YYYY-MM-DD.jsonl`.
|
|
193
|
+
- Collector emits system-events when a channel accumulates `systemEventBatchSize` MESSAGE_CREATE events, then resets the channel counter and continues.
|
|
194
|
+
- Collector service is registered as `discord_intel` in `/cluster services` when active.
|
|
195
|
+
|
|
196
|
+
## Twitter intelligence collector (leader-local)
|
|
197
|
+
|
|
198
|
+
When `cluster_v2` is enabled, leader can run a local Twitter/X collector via `bird` CLI.
|
|
199
|
+
The collector verifies browser profile/cookie access at startup, then periodically pulls the
|
|
200
|
+
"For You" timeline and persists JSONL records locally.
|
|
201
|
+
|
|
202
|
+
Config in `~/.mono-pilot/config.json`:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"twitter": {
|
|
207
|
+
"enabled": true,
|
|
208
|
+
"outputPath": "~/.mono-pilot/twitter/home.jsonl",
|
|
209
|
+
"pullIntervalMinutes": 10,
|
|
210
|
+
"pullCount": 10,
|
|
211
|
+
"commandTimeoutMs": 30000,
|
|
212
|
+
"requestTimeoutMs": 15000,
|
|
213
|
+
"chromeProfile": "Profile 4",
|
|
214
|
+
"chromeProfileDir": "~/Library/Application Support/Google/Chrome",
|
|
215
|
+
"cookieSource": ["chrome"],
|
|
216
|
+
"cookieTimeoutMs": 5000,
|
|
217
|
+
"includeRawPayload": false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Notes:
|
|
223
|
+
|
|
224
|
+
- Startup runs `bird check`; if browser profile/cookies are not readable, collector startup fails and is skipped (no in-process retry loop).
|
|
225
|
+
- On successful startup, collector runs one timeline pull immediately and then every `pullIntervalMinutes`; it treats `bird home` and `bird home --following` as two channels and switches between them when the current channel returns 0 tweets.
|
|
226
|
+
- Persisted `tweets[]` keeps bird top-level tweet objects as-is, including nested quote structure (not flattened).
|
|
227
|
+
- Collector uses depth-1 enrichment (`tweet` + `quotedTweet`) and short-link resolution: it records `shortLinkMappings[]` (`shortUrl -> resolvedUrl -> tweetId?`) for all detected t.co links, and when a mapping resolves to a tweet id it attaches `shortLinkMappings[].tweetFull` (deduped by tweet id within a pull cycle, excluding self-reference where `tweetId` equals the host tweet id).
|
|
228
|
+
- Before each pull, collector loads archived `tweetId` snapshots from the recent two-day window (`today` + `yesterday`) and skips re-archiving duplicates that already have `tweetFull`; duplicate ids with missing historical `tweetFull` are kept for backfill attempts.
|
|
229
|
+
- Each persisted batch includes a unique `snapshotId` to avoid downstream overwrite collisions.
|
|
230
|
+
- Persisted `feed` reflects the actual source timeline for the batch (`for_you` or `following`).
|
|
231
|
+
- Per-cycle pull errors are reported as system-events/log warnings and skipped until next scheduled cycle (no immediate retry).
|
|
232
|
+
- Collector writes with daily rotation: `outputPath` is treated as base name and actual files are `<base>.YYYY-MM-DD.jsonl`.
|
|
233
|
+
- Collector service is registered as `twitter_intel` in `/cluster services` when active.
|
|
234
|
+
|
|
235
|
+
## Digest draft command
|
|
236
|
+
|
|
237
|
+
Use `/digest draft` to generate the digest draft in TUI.
|
|
238
|
+
Use `/digest backfill` to serially backfill missing archive fields in-place (`tweetFull`, `quotedTweetFull`, `shortLinkMappings`).
|
|
239
|
+
|
|
240
|
+
`/digest draft` reads the daily JSONL file (`<outputBase>.YYYY-MM-DD.jsonl`) and classifies tweets with TUI progress events.
|
|
241
|
+
`/digest backfill` scans archive files and writes updates back to the same JSONL lines (serial mode to keep upstream pressure low).
|
|
242
|
+
|
|
243
|
+
Config in `~/.mono-pilot/config.json`:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"twitter": {
|
|
248
|
+
"digest": {
|
|
249
|
+
"classifier": {
|
|
250
|
+
"provider": "openai",
|
|
251
|
+
"model": "gpt-4o-mini",
|
|
252
|
+
"temperature": 0,
|
|
253
|
+
"maxTokens": 300,
|
|
254
|
+
"concurrency": 4
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Command examples:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
/digest draft
|
|
265
|
+
/digest draft --date 2026-03-09
|
|
266
|
+
/digest draft --file ~/.mono-pilot/twitter/home.2026-03-09.jsonl --concurrency 8 --sample 6
|
|
267
|
+
/digest backfill
|
|
268
|
+
/digest backfill --date 2026-03-09
|
|
269
|
+
/digest backfill --file ~/.mono-pilot/twitter/home.2026-03-09.jsonl
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Notes:
|
|
273
|
+
|
|
274
|
+
- Classification categories are fixed to 7 classes: 技术 / 产品 / 融资并购 / 开源生态 / 组织动态 / 政策监管 / 学术研究.
|
|
275
|
+
- Depth-1 enriched fields (`tweetFull`, `quotedTweetFull`) are used when present.
|
|
276
|
+
- Draft rendering rewrites `t.co` short links to `shortLinkMappings[].resolvedUrl`; if the resolved link points to the host tweet itself, that short link is removed from draft text.
|
|
277
|
+
- Draft mode now starts in background and rejects duplicate runs while active; check progress via `/events`.
|
|
278
|
+
- Draft artifacts are written to `~/.mono-pilot/twitter/draft.md` (primary draft) and `~/.mono-pilot/twitter/draft-debug.md` (classification debug details).
|
|
279
|
+
- Backfill mode is serial (non-concurrent), starts in background, and reuses per-run tweetId cache to avoid repeated `bird read` calls.
|
|
280
|
+
- `/events` includes backfill start/file progress/end summary plus low-frequency item-level progress updates.
|
|
281
|
+
|
|
282
|
+
## Image generation config
|
|
283
|
+
|
|
284
|
+
`GenerateImage` can read defaults from `~/.mono-pilot/config.json`:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"imageGen": {
|
|
289
|
+
"provider": "openrouter",
|
|
290
|
+
"model": "google/gemini-3.1-flash-image-preview"
|
|
291
|
+
},
|
|
292
|
+
"imageGenProviders": {
|
|
293
|
+
"openrouter": {
|
|
294
|
+
"baseUrl": "https://openrouter.ai/api/v1",
|
|
295
|
+
"apiKey": "",
|
|
296
|
+
"authHeader": true,
|
|
297
|
+
"models": [
|
|
298
|
+
{ "id": "google/gemini-3.1-flash-image-preview", "name": "Nano Banana 2" }
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Use `/image-model` to switch the active provider/model stored in config:
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
/image-model
|
|
309
|
+
/image-model list
|
|
310
|
+
/image-model use openrouter google/gemini-3.1-flash-image-preview
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## SFTP sync on ApplyPatch
|
|
314
|
+
|
|
315
|
+
If `.vscode/sftp.json` exists and a profile has `uploadOnSave: true`, patch tools can trigger uploads after successful writes:
|
|
316
|
+
|
|
317
|
+
- `ApplyPatch`: uploads the affected file.
|
|
318
|
+
- `CodexApplyPatch`: uploads files in `added[]` and `modified[]` (`deleted[]` is intentionally not auto-deleted remotely).
|
|
319
|
+
|
|
320
|
+
Set `interactiveAuth: true` to enable OTP prompts for `/sftp` commands and patch-triggered sync when needed.
|
|
321
|
+
|
|
322
|
+
Manual commands:
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
/sftp
|
|
326
|
+
/sftp upload path/to/file-or-dir
|
|
327
|
+
/sftp download path/to/file-or-dir
|
|
328
|
+
/sftp target <targetName>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`/sftp` (without args) opens an interactive target selector (up/down to choose, enter to confirm).
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
[
|
|
335
|
+
{
|
|
336
|
+
"name": "prod",
|
|
337
|
+
"protocol": "sftp",
|
|
338
|
+
"host": "10.0.0.130",
|
|
339
|
+
"port": 22,
|
|
340
|
+
"username": "wanqian",
|
|
341
|
+
"privateKeyPath": "~/.ssh/id_rsa",
|
|
342
|
+
"passphrase": "...",
|
|
343
|
+
"remotePath": "/home/wanqian/project",
|
|
344
|
+
"uploadOnSave": true,
|
|
345
|
+
"interactiveAuth": true,
|
|
346
|
+
"hop": {
|
|
347
|
+
"host": "36.151.163.132",
|
|
348
|
+
"port": 22,
|
|
349
|
+
"username": "wanqian",
|
|
350
|
+
"privateKeyPath": "/Users/wanqian/.ssh/id_rsa",
|
|
351
|
+
"passphrase": "...",
|
|
352
|
+
"interactiveAuth": true
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`hop` enables single-jump SFTP tunneling (`local -> hop -> target`).
|
|
359
|
+
Both target and hop authentication are performed by the local client, so `privateKeyPath` values must point to local files.
|
|
99
360
|
|
|
100
361
|
## Local development
|
|
101
362
|
|
|
@@ -122,6 +383,8 @@ npm run dev:continue
|
|
|
122
383
|
npm run dev:watch:continue
|
|
123
384
|
```
|
|
124
385
|
|
|
386
|
+
When running in interactive mode, `option+o` opens the current workspace directory in `nvim` (falls back to `vim` when `nvim` is unavailable).
|
|
387
|
+
|
|
125
388
|
## Prompt inspection
|
|
126
389
|
|
|
127
390
|
```bash
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Derive a stable, traceable agent ID from a project path.
|
|
6
|
+
* Uses the same convention as pi-coding-agent session directories:
|
|
7
|
+
* `/Users/wanqian/foo` → `--Users-wanqian-foo--`
|
|
8
|
+
*/
|
|
9
|
+
export function deriveAgentId(cwd) {
|
|
10
|
+
return `-${resolve(cwd).replaceAll("/", "-")}--`;
|
|
11
|
+
}
|
|
12
|
+
export function getAllAgentsDir() {
|
|
13
|
+
return join(homedir(), ".mono-pilot", "agents");
|
|
14
|
+
}
|
|
15
|
+
export function getAgentDir(agentId) {
|
|
16
|
+
return join(getAllAgentsDir(), agentId);
|
|
17
|
+
}
|
|
18
|
+
export function getAgentMemoryDir(agentId) {
|
|
19
|
+
return join(getAgentDir(agentId), "memory");
|
|
20
|
+
}
|
|
21
|
+
export function getAgentTerminalsDir(agentId, sessionId) {
|
|
22
|
+
return join(getAgentDir(agentId), "terminals", sessionId);
|
|
23
|
+
}
|
|
24
|
+
export async function listAgentIds() {
|
|
25
|
+
const baseDir = getAllAgentsDir();
|
|
26
|
+
try {
|
|
27
|
+
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
28
|
+
return entries
|
|
29
|
+
.filter((entry) => entry.isDirectory())
|
|
30
|
+
.map((entry) => entry.name)
|
|
31
|
+
.sort((a, b) => a.localeCompare(b));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { deriveAgentId, getAgentBriefDir } from "./paths.js";
|
|
4
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
5
|
+
import { ensureDefaults } from "./defaults.js";
|
|
6
|
+
/** Recursively collect .md files under a directory, returning parsed body content. */
|
|
7
|
+
function collectMarkdownFiles(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
return [];
|
|
10
|
+
const results = [];
|
|
11
|
+
function walk(currentDir) {
|
|
12
|
+
let entries;
|
|
13
|
+
try {
|
|
14
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const fullPath = join(currentDir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
walk(fullPath);
|
|
23
|
+
}
|
|
24
|
+
else if (entry.name.endsWith(".md")) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = readFileSync(fullPath, "utf-8");
|
|
27
|
+
const { body } = parseFrontmatter(raw);
|
|
28
|
+
const trimmed = body.trim();
|
|
29
|
+
if (trimmed.length > 0) {
|
|
30
|
+
results.push({ relativePath: relative(dir, fullPath), body: trimmed });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// skip unreadable files
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
walk(dir);
|
|
40
|
+
return results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
41
|
+
}
|
|
42
|
+
function buildSection(heading, pathHint, files) {
|
|
43
|
+
if (files.length === 0)
|
|
44
|
+
return "";
|
|
45
|
+
const lines = [`## ${heading} [${pathHint}]`];
|
|
46
|
+
for (const f of files) {
|
|
47
|
+
lines.push(`### ${f.relativePath}`);
|
|
48
|
+
lines.push(f.body);
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
const BRIEF_GUIDELINE = "When applying this brief in responses, integrate it naturally — like a colleague who recalls shared context. " +
|
|
53
|
+
'Never draw attention to the brief system itself or use phrases like "I remember that..." — just use what you know seamlessly.';
|
|
54
|
+
const BRIEF_CHECK = "Review this conversation for information worth storing in your brief. " +
|
|
55
|
+
"Update silently (no confirmation needed) via brief_write if you learned:\n" +
|
|
56
|
+
"- **User info**: Name, role, preferences, working style, current goals\n" +
|
|
57
|
+
"- **Project details**: Architecture, patterns, gotchas, dependencies, conventions\n" +
|
|
58
|
+
"- **Corrections**: User corrected you or clarified something important\n" +
|
|
59
|
+
"- **Preferences**: How they want you to behave, communicate, or approach tasks\n\n" +
|
|
60
|
+
'Ask yourself: "If I started a new session tomorrow, what from this conversation would I want to remember?" ' +
|
|
61
|
+
"If the answer is meaningful, update the appropriate brief file(s) now.";
|
|
62
|
+
/**
|
|
63
|
+
* Read all core brief files and build the <brief> block for system prompt injection.
|
|
64
|
+
* Creates default template files if they don't exist yet.
|
|
65
|
+
*/
|
|
66
|
+
export function buildBriefBlock(cwd) {
|
|
67
|
+
const agentId = deriveAgentId(cwd);
|
|
68
|
+
ensureDefaults(agentId);
|
|
69
|
+
const briefDir = getAgentBriefDir(agentId);
|
|
70
|
+
const sections = [];
|
|
71
|
+
const humanSection = buildSection("User Context", `~/.mono-pilot/agents/${agentId}/brief/human/`, collectMarkdownFiles(join(briefDir, "human")));
|
|
72
|
+
if (humanSection)
|
|
73
|
+
sections.push(humanSection);
|
|
74
|
+
const projectSection = buildSection("Project Context", `~/.mono-pilot/agents/${agentId}/brief/project/`, collectMarkdownFiles(join(briefDir, "project")));
|
|
75
|
+
if (projectSection)
|
|
76
|
+
sections.push(projectSection);
|
|
77
|
+
const tasksSection = buildSection("Current Tasks", `~/.mono-pilot/agents/${agentId}/brief/tasks/`, collectMarkdownFiles(join(briefDir, "tasks")));
|
|
78
|
+
if (tasksSection)
|
|
79
|
+
sections.push(tasksSection);
|
|
80
|
+
if (sections.length === 0)
|
|
81
|
+
return "";
|
|
82
|
+
return `<brief>\n${sections.join("\n\n")}\n\n${BRIEF_GUIDELINE}\n\n${BRIEF_CHECK}\n</brief>`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getAgentBriefDir } from "./paths.js";
|
|
4
|
+
const DEFAULTS = {
|
|
5
|
+
"human/identity.md": {
|
|
6
|
+
description: "What I know about the person I'm working with. Update when learning about their background, role, or identity.",
|
|
7
|
+
limit: 40,
|
|
8
|
+
content: "I haven't gotten to know this person yet.",
|
|
9
|
+
},
|
|
10
|
+
"human/prefs/communication.md": {
|
|
11
|
+
description: "How this person prefers to communicate. Update when they express language, verbosity, or style preferences.",
|
|
12
|
+
limit: 30,
|
|
13
|
+
content: "No communication preferences learned yet.",
|
|
14
|
+
},
|
|
15
|
+
"human/prefs/coding-style.md": {
|
|
16
|
+
description: "Coding conventions and style preferences. Update when they show patterns in naming, formatting, or tooling choices.",
|
|
17
|
+
limit: 30,
|
|
18
|
+
content: "No coding style preferences learned yet.",
|
|
19
|
+
},
|
|
20
|
+
"project/overview.md": {
|
|
21
|
+
description: "High-level understanding of this project. Update after exploring the codebase structure, tech stack, and architecture.",
|
|
22
|
+
limit: 50,
|
|
23
|
+
content: "I'm still getting to know this codebase.\nIf there's an AGENTS.md, CLAUDE.md, or README, I should read it early.",
|
|
24
|
+
},
|
|
25
|
+
"project/commands.md": {
|
|
26
|
+
description: "Build, test, lint, and run commands. Update when discovering or confirming project commands.",
|
|
27
|
+
limit: 30,
|
|
28
|
+
content: "No commands discovered yet.",
|
|
29
|
+
},
|
|
30
|
+
"project/conventions.md": {
|
|
31
|
+
description: "Code conventions, commit style, and recurring patterns. Update when observing how this project does things.",
|
|
32
|
+
limit: 40,
|
|
33
|
+
content: "No conventions learned yet.",
|
|
34
|
+
},
|
|
35
|
+
"project/gotchas.md": {
|
|
36
|
+
description: "Known pitfalls, warnings, and surprising behavior. Update when hitting unexpected issues.",
|
|
37
|
+
limit: 40,
|
|
38
|
+
content: "No gotchas discovered yet.",
|
|
39
|
+
},
|
|
40
|
+
"tasks/current.md": {
|
|
41
|
+
description: "Current task progress and status. Update when starting, progressing, or completing tasks.",
|
|
42
|
+
limit: 50,
|
|
43
|
+
content: "No active tasks.",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
function writeDefaultFile(baseDir, relativePath, template) {
|
|
47
|
+
const filePath = join(baseDir, relativePath);
|
|
48
|
+
if (existsSync(filePath))
|
|
49
|
+
return;
|
|
50
|
+
const dir = dirname(filePath);
|
|
51
|
+
mkdirSync(dir, { recursive: true });
|
|
52
|
+
const content = `---\ndescription: ${template.description}\nlimit: ${template.limit}\n---\n${template.content}\n`;
|
|
53
|
+
writeFileSync(filePath, content, "utf-8");
|
|
54
|
+
}
|
|
55
|
+
export function ensureDefaults(agentId) {
|
|
56
|
+
const dir = getAgentBriefDir(agentId);
|
|
57
|
+
for (const [path, template] of Object.entries(DEFAULTS)) {
|
|
58
|
+
writeDefaultFile(dir, path, template);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse YAML frontmatter from a brief file.
|
|
3
|
+
* Supports only `description` (string) and `limit` (positive integer).
|
|
4
|
+
*/
|
|
5
|
+
export function parseFrontmatter(content) {
|
|
6
|
+
const trimmed = content.trimStart();
|
|
7
|
+
if (!trimmed.startsWith("---")) {
|
|
8
|
+
return { frontmatter: {}, body: content };
|
|
9
|
+
}
|
|
10
|
+
const endIndex = trimmed.indexOf("\n---", 3);
|
|
11
|
+
if (endIndex === -1) {
|
|
12
|
+
return { frontmatter: {}, body: content };
|
|
13
|
+
}
|
|
14
|
+
const yamlBlock = trimmed.slice(4, endIndex).trim();
|
|
15
|
+
const body = trimmed.slice(endIndex + 4).trimStart();
|
|
16
|
+
const frontmatter = {};
|
|
17
|
+
for (const line of yamlBlock.split("\n")) {
|
|
18
|
+
const colonIndex = line.indexOf(":");
|
|
19
|
+
if (colonIndex === -1)
|
|
20
|
+
continue;
|
|
21
|
+
const key = line.slice(0, colonIndex).trim();
|
|
22
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
23
|
+
if (key === "limit") {
|
|
24
|
+
const num = parseInt(value, 10);
|
|
25
|
+
if (!isNaN(num) && num > 0)
|
|
26
|
+
frontmatter.limit = num;
|
|
27
|
+
}
|
|
28
|
+
else if (key === "description") {
|
|
29
|
+
frontmatter.description = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { frontmatter, body };
|
|
33
|
+
}
|
|
34
|
+
export function serializeWithFrontmatter(frontmatter, body) {
|
|
35
|
+
const lines = ["---"];
|
|
36
|
+
if (frontmatter.description) {
|
|
37
|
+
lines.push(`description: ${frontmatter.description}`);
|
|
38
|
+
}
|
|
39
|
+
if (frontmatter.limit !== undefined) {
|
|
40
|
+
lines.push(`limit: ${frontmatter.limit}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push("---");
|
|
43
|
+
if (body.length > 0) {
|
|
44
|
+
lines.push(body);
|
|
45
|
+
}
|
|
46
|
+
return lines.join("\n") + "\n";
|
|
47
|
+
}
|
|
48
|
+
export function countBodyLines(body) {
|
|
49
|
+
const trimmed = body.trim();
|
|
50
|
+
if (trimmed.length === 0)
|
|
51
|
+
return 0;
|
|
52
|
+
return trimmed.split("\n").length;
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { getAgentDir } from "../agents-paths.js";
|
|
3
|
+
// Re-export agent-level paths for backward compatibility during migration
|
|
4
|
+
export { deriveAgentId, getAgentDir, getAllAgentsDir } from "../agents-paths.js";
|
|
5
|
+
export function getAgentBriefDir(agentId) {
|
|
6
|
+
return join(getAgentDir(agentId), "brief");
|
|
7
|
+
}
|
|
8
|
+
export function resolveBriefPath(relativePath, agentId) {
|
|
9
|
+
return join(getAgentBriefDir(agentId), relativePath);
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const REFLECTION_INTERVAL = 25;
|
|
2
|
+
let turnCount = 0;
|
|
3
|
+
let pendingCompactionReflection = false;
|
|
4
|
+
/** Flag that a compaction just occurred — next turn will get a reflection reminder. */
|
|
5
|
+
export function onCompaction() {
|
|
6
|
+
pendingCompactionReflection = true;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Return a reflection reminder if triggered by compaction event
|
|
10
|
+
* or periodic turn interval. Compaction trigger takes priority.
|
|
11
|
+
*/
|
|
12
|
+
export function getBriefReflectionReminder() {
|
|
13
|
+
turnCount++;
|
|
14
|
+
const triggered = pendingCompactionReflection || turnCount % REFLECTION_INTERVAL === 0;
|
|
15
|
+
if (!triggered)
|
|
16
|
+
return undefined;
|
|
17
|
+
const source = pendingCompactionReflection ? "post-compaction" : `turn ${turnCount}`;
|
|
18
|
+
pendingCompactionReflection = false;
|
|
19
|
+
return `<brief_reminder>
|
|
20
|
+
[${source}] Review this conversation for information worth remembering across sessions. If you learned anything important, use brief_write to update the relevant file:
|
|
21
|
+
- User info / preferences -> human/identity.md or human/prefs/
|
|
22
|
+
- Project context / architecture -> project/overview.md, project/conventions.md, etc.
|
|
23
|
+
- Task progress -> tasks/current.md
|
|
24
|
+
Ask yourself: "If I started a new session tomorrow, what from this conversation would I want to remember?"
|
|
25
|
+
Keep notes factual and concise. Append new info rather than overwriting existing knowledge.
|
|
26
|
+
</brief_reminder>`;
|
|
27
|
+
}
|