twinny 1.0.0 → 1.1.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.
Files changed (103) hide show
  1. package/README.en.md +293 -0
  2. package/README.md +134 -112
  3. package/dist/app/startup-error-telemetry.d.ts +21 -0
  4. package/dist/app/startup-error-telemetry.js +169 -0
  5. package/dist/app/startup-error-telemetry.js.map +1 -0
  6. package/dist/app/startup-probe.js +3 -0
  7. package/dist/app/startup-probe.js.map +1 -1
  8. package/dist/app/wiring.d.ts +7 -0
  9. package/dist/app/wiring.js +68 -19
  10. package/dist/app/wiring.js.map +1 -1
  11. package/dist/cli/commands.js +72 -18
  12. package/dist/cli/commands.js.map +1 -1
  13. package/dist/cli/install-guide.d.ts +5 -1
  14. package/dist/cli/install-guide.js +8 -3
  15. package/dist/cli/install-guide.js.map +1 -1
  16. package/dist/cli/install-wizard.d.ts +165 -6
  17. package/dist/cli/install-wizard.js +912 -115
  18. package/dist/cli/install-wizard.js.map +1 -1
  19. package/dist/cli/update.d.ts +26 -0
  20. package/dist/cli/update.js +112 -0
  21. package/dist/cli/update.js.map +1 -0
  22. package/dist/codex/appserver.js +6 -1
  23. package/dist/codex/appserver.js.map +1 -1
  24. package/dist/codex/goal.d.ts +2 -1
  25. package/dist/codex/goal.js +10 -12
  26. package/dist/codex/goal.js.map +1 -1
  27. package/dist/codex/turn.d.ts +11 -1
  28. package/dist/codex/turn.js +64 -5
  29. package/dist/codex/turn.js.map +1 -1
  30. package/dist/config/loader.d.ts +7 -1
  31. package/dist/config/loader.js +83 -1
  32. package/dist/config/loader.js.map +1 -1
  33. package/dist/config/paths.js +9 -1
  34. package/dist/config/paths.js.map +1 -1
  35. package/dist/config/secrets.d.ts +21 -1
  36. package/dist/config/secrets.js +81 -1
  37. package/dist/config/secrets.js.map +1 -1
  38. package/dist/conversation/manager.d.ts +121 -5
  39. package/dist/conversation/manager.js +1316 -149
  40. package/dist/conversation/manager.js.map +1 -1
  41. package/dist/lark/cards.js +12 -5
  42. package/dist/lark/cards.js.map +1 -1
  43. package/dist/lark/doc-comments.d.ts +58 -0
  44. package/dist/lark/doc-comments.js +540 -0
  45. package/dist/lark/doc-comments.js.map +1 -0
  46. package/dist/lark/events.d.ts +2 -1
  47. package/dist/lark/events.js +14 -2
  48. package/dist/lark/events.js.map +1 -1
  49. package/dist/lark/filters.d.ts +11 -1
  50. package/dist/lark/filters.js +54 -0
  51. package/dist/lark/filters.js.map +1 -1
  52. package/dist/lark/index.d.ts +1 -0
  53. package/dist/lark/index.js +1 -0
  54. package/dist/lark/index.js.map +1 -1
  55. package/dist/lark/messages.d.ts +2 -0
  56. package/dist/lark/messages.js +38 -0
  57. package/dist/lark/messages.js.map +1 -1
  58. package/dist/lark/openapi.js +28 -3
  59. package/dist/lark/openapi.js.map +1 -1
  60. package/dist/lark/types.d.ts +8 -3
  61. package/dist/lark/types.js +21 -2
  62. package/dist/lark/types.js.map +1 -1
  63. package/dist/launchd/install.d.ts +14 -6
  64. package/dist/launchd/install.js +102 -42
  65. package/dist/launchd/install.js.map +1 -1
  66. package/dist/launchd/plist.d.ts +5 -0
  67. package/dist/launchd/plist.js +27 -1
  68. package/dist/launchd/plist.js.map +1 -1
  69. package/dist/observability/health.d.ts +1 -1
  70. package/dist/observability/health.js +11 -8
  71. package/dist/observability/health.js.map +1 -1
  72. package/dist/service/index.d.ts +2 -0
  73. package/dist/service/index.js +3 -0
  74. package/dist/service/index.js.map +1 -0
  75. package/dist/service/install.d.ts +17 -0
  76. package/dist/service/install.js +64 -0
  77. package/dist/service/install.js.map +1 -0
  78. package/dist/service/lock.d.ts +6 -0
  79. package/dist/service/lock.js +23 -0
  80. package/dist/service/lock.js.map +1 -0
  81. package/dist/store/db.d.ts +29 -2
  82. package/dist/store/db.js +136 -4
  83. package/dist/store/db.js.map +1 -1
  84. package/dist/store/migrations.d.ts +4 -4
  85. package/dist/store/migrations.js +52 -2
  86. package/dist/store/migrations.js.map +1 -1
  87. package/dist/store/repositories.d.ts +27 -4
  88. package/dist/store/repositories.js +204 -3
  89. package/dist/store/repositories.js.map +1 -1
  90. package/dist/systemd/install.d.ts +17 -0
  91. package/dist/systemd/install.js +82 -0
  92. package/dist/systemd/install.js.map +1 -0
  93. package/dist/systemd/unit.d.ts +9 -0
  94. package/dist/systemd/unit.js +60 -0
  95. package/dist/systemd/unit.js.map +1 -0
  96. package/dist/types.d.ts +51 -1
  97. package/dist/types.js.map +1 -1
  98. package/dist/version.js +1 -1
  99. package/dist/version.json +1 -1
  100. package/migrations/0002_lark_doc_watcher.sql +15 -0
  101. package/migrations/0003_lark_message_doc_comment_id.sql +6 -0
  102. package/package.json +3 -10
  103. package/README.zh-CN.md +0 -252
package/README.en.md ADDED
@@ -0,0 +1,293 @@
1
+ # Twinny
2
+
3
+ ![Twinny banner](./configs/banner.png)
4
+
5
+ [![npm version](https://img.shields.io/npm/v/twinny.svg)](https://www.npmjs.com/package/twinny)
6
+
7
+ [简体中文](./README.md)
8
+
9
+ ## Requirements
10
+
11
+ - macOS or Linux. The installer manages a macOS LaunchAgent, or a `--system-daemon` LaunchDaemon, on macOS and a systemd user service on Linux.
12
+ - Node.js 22.18.0 or newer. Twinny uses Node.js' built-in `node:sqlite` module and does not require an extra SQLite native addon.
13
+ - Codex CLI 0.130.0 or newer in `PATH`, or set `CODEX_BINARY`; the installer can install Codex automatically if it is missing.
14
+ - A Feishu/Lark bot app with the permissions and event subscriptions listed below.
15
+
16
+ ## Installation
17
+
18
+ ### Installation guide for Codex
19
+
20
+ Copy this prompt into Codex and let it run the agent installer:
21
+
22
+ ```text
23
+ Read https://raw.githubusercontent.com/hachiwii/twinny/master/agent_installation_guide.md and follow instructions in it.
24
+ ```
25
+
26
+ ### Install Manually
27
+
28
+ Run the interactive installer with `npx`:
29
+
30
+ ```sh
31
+ npx twinny@latest install
32
+ ```
33
+
34
+ On macOS, Twinny installs as a LaunchAgent in the current GUI session by default. In SSH, CI, or another environment without a GUI LaunchAgent, the installer exits and asks you to use system daemon mode:
35
+
36
+ ```sh
37
+ npx twinny@latest install --system-daemon
38
+ ```
39
+
40
+ `--system-daemon` writes the plist to `/Library/LaunchDaemons` through `sudo` and sets `UserName` to the current user. Later `start`, `stop`, `restart`, and `status` commands keep using LaunchDaemon based on the service settings in `config.toml`.
41
+
42
+ Useful daemon commands:
43
+
44
+ ```sh
45
+ npx twinny@latest doctor
46
+ npx twinny@latest status
47
+ npx twinny@latest update
48
+ npx twinny@latest start
49
+ npx twinny@latest stop
50
+ npx twinny@latest restart
51
+ npx twinny@latest uninstall
52
+ ```
53
+
54
+ Use `TWINNY_HOME=/path/to/home` with any command when you are not using the default home.
55
+
56
+ Secrets are stored outside `config.toml`. Non-macOS installs store the Lark `app_secret` in the `lark_app_secret` field in `TWINNY_HOME/auth.json`. macOS installs use the system Keychain by default; with `--disable-keychain`, or if the Keychain write fails, the installer stores the secret in `auth.json` instead. On startup, Twinny reads `auth.json` first, then falls back to `TWINNY_LARK_APP_SECRET` and the legacy secret store (macOS Keychain, `runtime/secrets.json` on other platforms).
57
+
58
+ ## Feishu/Lark App Configuration
59
+
60
+ You need to grant these API permissions in the Feishu/Lark developer console:
61
+
62
+ ```text
63
+ im:message.p2p_msg:readonly
64
+ im:message.group_msg
65
+ im:message:readonly
66
+ im:message:send_as_bot
67
+ im:message:update
68
+ im:message:recall
69
+ im:message.reactions:write_only
70
+ im:chat:read
71
+ im:chat:create
72
+ im:chat:update
73
+ im:resource
74
+ docs:document.comment:read
75
+ docs:document.comment:create
76
+ docs:document.comment:write_only
77
+ wiki:node:read
78
+ ```
79
+
80
+ Subscribe to these events/callbacks:
81
+
82
+ ```text
83
+ im.message.receive_v1
84
+ im.message.recalled_v1
85
+ drive.notice.comment_add_v1
86
+ application.bot.menu_v6
87
+ card.action.trigger
88
+ ```
89
+
90
+ Use the Feishu/Lark event long connection (WebSocket) mode. Twinny does not require a public HTTP callback URL for message events.
91
+
92
+ Optional bot shortcut menu entries can use these `event_key` values:
93
+
94
+
95
+ | Event key | Action |
96
+ | --------- | ---------------------------------------------------- |
97
+ | `help` | Send command help. |
98
+ | `status` | Show the current conversation and thread status. |
99
+ | `queue` | Toggle queue-next-message mode. |
100
+ | `new` | Open a new Codex thread in the current conversation. |
101
+ | `stop` | Stop the active turn and clear queued work. |
102
+
103
+
104
+ ## Usage
105
+
106
+ Send normal messages to the bot to start or continue a Codex turn. In groups, the owner must activate the group before ordinary messages are routed to Codex.
107
+
108
+ ### Conversation Commands
109
+
110
+
111
+ | Command | Usage |
112
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
113
+ | `/help` | Show available commands. |
114
+ | `/status` | Show conversation, Codex thread, model, token, and queue status. |
115
+ | `/new` | Stop the current task, clear queued messages, and open a new Codex thread. |
116
+ | `/stop [all\|<side_id>]` | Stop the active task and clear queued messages. Use `all` to stop side turns too, or a side id to stop one side turn. |
117
+ | `/next` | Interrupt the current task and start the next queued message. |
118
+ | `/steer` | Inject the next queued batch into the currently running Codex turn. |
119
+ | `/queue [message]` | Without a message, queue your next message. With a message, add that message to the next turn. |
120
+ | `/goal <objective>` | Set and run a Codex goal. A later `/goal` while the goal is active updates the objective. |
121
+ | `/plan [message]` | Enter plan mode. If a message is provided, process it in plan mode immediately. |
122
+ | `/exit` | Exit plan mode in the next queued control step. |
123
+ | `/side <message>` or `/btw <message>` | Start an ephemeral side conversation forked from the current Codex thread. |
124
+ | `/compact` | Compact the current Codex thread context in the next queued control step. |
125
+ | `/thread [message]` | Create a new Lark topic backed by a new Codex thread. If `message` is present, proxy it into that new topic. |
126
+ | `/fork [message]` | Fork the current Codex thread into a new Lark topic. If `message` is present, proxy it into that new topic. |
127
+ | `/watch <lark_doc_url> [owner\|all\|none]` | Watch @bot comments on a Feishu/Lark document and route them to the current thread. Without arguments, list watchers for the current thread. `owner` responds only to the owner, `all` responds to everyone, and `none` disables the watcher. |
128
+ | `/model <model> <effort>` | Set the model and reasoning effort for future turns in the current thread. |
129
+ | `/logo` | Send the Twinny logo image. |
130
+ | `/twinny` or `/banner` | Send the Twinny banner card. |
131
+
132
+
133
+ ### Group Administration
134
+
135
+ Only the configured owner can run these commands:
136
+
137
+
138
+ | Command | Usage |
139
+ | ------------------------------- | ---------------------------------------------------------------------- |
140
+ | `/activate <owner_at\|owner\|all_at\|all> [profile]` | Activate a group, set who can route messages to Codex, refresh the group name, and optionally bind the group to a profile. |
141
+ | `/deactivate` | Disable Twinny in the current group and clear pending work. |
142
+ | `/pair {guest_ou_id} <profile>` | Authorize a non-owner P2P user and bind that user to a profile. |
143
+ | `/reload [profile]` | Reload all Codex profiles, or one named profile, after editing config. |
144
+
145
+
146
+ Response modes:
147
+
148
+ - `owner_at`: only owner messages that mention the bot.
149
+ - `owner`: all owner messages.
150
+ - `all_at`: messages from any group member that mention the bot.
151
+ - `all`: all messages from any group member.
152
+
153
+ ## Recommended Practice
154
+
155
+ Create a dedicated Feishu/Lark group for a project. Write an [AGENTS.md](http://AGENTS.md) inside the group's workspace. Let the owner activate the group with the least permissive useful mode, then create one topic per development task:
156
+
157
+ ```text
158
+ /activate all host
159
+ /thread fix the login callback race
160
+ /thread add the GitHub README
161
+ ```
162
+
163
+ Use `/fork` when a task needs an alternative direction while preserving the original Codex thread history:
164
+
165
+ ```text
166
+ /fork try the smaller refactor path
167
+ ```
168
+
169
+ Keep each task's discussion inside its topic. This keeps Codex context, local workspace state, Lark discussion, and status cards separated by task.
170
+
171
+ ## Security Notes
172
+
173
+ Twinny runs on the owner's local machine. Treat it as a local automation bridge, not as a hardened multi-tenant execution service.
174
+
175
+ The current default configuration is not fully ready for broad multi-user sharing. In particular, if you activate a group with the `host` profile and `all` response mode, every group member who can speak in that group can run work with the same Codex execution authority as the owner:
176
+
177
+ ```text
178
+ /activate all host
179
+ ```
180
+
181
+ Be careful with `all_at` as well: every group member who can mention the bot can submit work when the group is bound to a powerful profile.
182
+
183
+ Before using Twinny in shared groups:
184
+
185
+ - Prefer a dedicated `codex_home` for guest or team profiles instead of sharing the owner's `~/.codex`.
186
+ - Configure Codex sandbox, filesystem, network, and approval-related settings for that profile.
187
+ - Add workspace-level `.codex` overrides where your Codex setup supports project-local safety policy.
188
+ - Keep `permissions.p2p_default_profile = "none"` unless you intentionally want unpaired P2P users to get access.
189
+ - Use `owner_at` or `owner` instead of `all` or `all_at` unless the group is tightly controlled.
190
+
191
+ ## Advanced Configuration
192
+
193
+ Twinny reads `config.toml` from `TWINNY_HOME`.
194
+
195
+ Recognized fields:
196
+
197
+
198
+ | Field | Meaning and values |
199
+ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
200
+ | `[codex].binary` | Codex CLI executable path or command name. Defaults to `codex`. Use an absolute path when the LaunchAgent cannot find Codex through `PATH`. |
201
+ | `[lark.reaction].working` | Lark emoji type added while Twinny is working. Defaults to `JubilantRabbit`. |
202
+ | `[lark.reaction].queued` | Lark emoji type added to queued messages. Defaults to `OneSecond`. |
203
+ | `[lark.redaction].email` | Redaction strategy for email addresses in outgoing Lark payloads. `mask` keeps the domain and masks the local part, for example `alice@example.com` becomes `a***e@example.com`; `whitespace` inserts spaces, for example `alice @ example.com`; `none` sends raw email addresses. Feishu may reject bot messages that contain raw email addresses or phone numbers. Defaults to `mask`. |
204
+ | `[lark.redaction].chinese_phone_number` | Redaction strategy for Chinese phone numbers in outgoing Lark payloads. `mask` keeps the first 3 and last 4 digits, for example `138****5678`; `whitespace` inserts spaces, for example `138 1234 5678`; `none` sends raw phone numbers. Feishu may reject bot messages that contain raw email addresses or phone numbers. Defaults to `mask`. |
205
+ | `[permissions].p2p_default_profile` | Profile used when an unpaired P2P user first messages Twinny. Use `none` to deny by default, or a configured profile name to auto-authorize. Defaults to `none`. |
206
+ | `[service.launchd].mode` | macOS launchd placement. `gui` uses the current `gui/<uid>` LaunchAgent by default; `daemon` uses a system LaunchDaemon. Usually written by `twinny install --system-daemon`. |
207
+ | `[service.launchd].user_name` | The plist `UserName` when `mode = "daemon"`. Usually written automatically by `twinny install --system-daemon` from the current user. |
208
+ | `[profiles.<name>].codex_home` | `CODEX_HOME` for that profile. Absolute paths are used as-is; relative paths are resolved under `TWINNY_HOME`. `host` defaults to `~/.codex`; other profiles inherit `host` unless set. |
209
+ | `[profiles.<name>].default_model` | Default model for new threads in that profile. `host` defaults to `gpt-5.5`; other profiles inherit `host` unless set. |
210
+ | `[profiles.<name>].default_effort` | Default reasoning effort for new threads in that profile. Common values are `minimal`, `low`, `medium`, `high`, and `xhigh`; `host` defaults to `medium`; other profiles inherit `host` unless set. |
211
+ | `[telemetry].enabled` | Boolean opt-out for telemetry-capable builds. Set to `false` to disable event capture. See [Telemetry](#telemetry). |
212
+
213
+
214
+ Telemetry data scope and opt-out settings are covered in [Telemetry](#telemetry).
215
+
216
+ Example `config.toml`:
217
+
218
+ ```toml
219
+ [codex]
220
+ binary = "/opt/homebrew/bin/codex"
221
+
222
+ [lark.reaction]
223
+ working = "JubilantRabbit"
224
+ queued = "OneSecond"
225
+
226
+ [lark.redaction]
227
+ email = "mask"
228
+ chinese_phone_number = "mask"
229
+
230
+ [permissions]
231
+ p2p_default_profile = "none"
232
+
233
+ [profiles.host]
234
+ codex_home = "~/.codex"
235
+ default_model = "gpt-5.5"
236
+ default_effort = "medium"
237
+
238
+ [profiles.guest]
239
+ codex_home = "./profiles/guest-codex"
240
+ default_model = "gpt-5.5"
241
+ default_effort = "medium"
242
+ ```
243
+
244
+ Relative `codex_home` paths are resolved under `TWINNY_HOME`. Each profile starts its own Codex app-server process with `CODEX_HOME` set to that profile's `codex_home`.
245
+
246
+ After editing profile config, run `/reload [profile]` from Lark or restart the daemon.
247
+
248
+ ## Multiple Instances With `TWINNY_HOME`
249
+
250
+ Run multiple isolated Twinny instances by giving each instance its own home:
251
+
252
+ ```sh
253
+ TWINNY_HOME="$HOME/.twinny-work" npx twinny@latest install
254
+ TWINNY_HOME="$HOME/.twinny-personal" npx twinny@latest install
255
+
256
+ TWINNY_HOME="$HOME/.twinny-work" npx twinny@latest status
257
+ TWINNY_HOME="$HOME/.twinny-personal" npx twinny@latest logs
258
+ ```
259
+
260
+ Each home gets separate config and needs a separate Feishu bot app.
261
+
262
+ ## Telemetry
263
+
264
+ Twinny builds with telemetry enabled may send anonymous, best-effort usage and reliability events. The data is used to monitor product quality, understand failure patterns, and support the maintainer's personal research interests around local-agent workflows.
265
+
266
+ Twinny does not collect or upload conversation content or credentials. This includes Lark message text, prompts, Codex answers, Feishu/Lark app secrets or tokens, Codex credentials or session tokens, chat names, sender names, raw Lark or Codex IDs, raw local paths, environment variable values, API keys, and other secrets. Identifiers such as install, conversation, thread, turn, sender, message, and Codex binary are salted and hashed locally before upload.
267
+
268
+ Telemetry may include:
269
+
270
+ - install and launch lifecycle status, startup duration, and LaunchAgent setup state;
271
+ - runtime health signals such as heartbeat, uptime, queue and active-turn counts, memory usage, and Lark/Codex readiness;
272
+ - message routing metadata such as conversation type, message or action type, route kind, queue depth, and resource counts;
273
+ - turn metadata such as status, type, model, reasoning effort, token counts, duration, generated image count, and error code/category when a turn fails;
274
+ - environment metadata such as Twinny, Codex, Node, OS version/platform/arch, Lark brand, and profile count.
275
+
276
+ Telemetry failures are ignored by the product path and should not affect install, launch, message handling, or Codex turns.
277
+
278
+ Disable telemetry in `config.toml`:
279
+
280
+ ```toml
281
+ [telemetry]
282
+ enabled = false
283
+ ```
284
+
285
+ Or disable it for a process with an environment variable:
286
+
287
+ ```sh
288
+ TWINNY_TELEMETRY_ENABLED=false npx twinny@latest start
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT