arisa 5.1.60 → 5.1.64

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 (32) hide show
  1. package/README.md +9 -0
  2. package/package.json +1 -1
  3. package/src/core/agent/agent-manager.js +10 -0
  4. package/src/core/config/config-defaults.js +28 -1
  5. package/src/core/tasks/task-runner.js +17 -2
  6. package/src/core/tasks/task-store.js +80 -14
  7. package/src/core/tools/tool-registry.js +20 -5
  8. package/src/core/tools/weighted-resource-governor.js +153 -0
  9. package/src/index.js +20 -0
  10. package/src/official-tools.lock.json +56 -42
  11. package/src/runtime/create-app.js +1 -1
  12. package/src/runtime/create-headless-app.js +2 -2
  13. package/src/runtime/paths.js +4 -0
  14. package/src/runtime/service-manager.js +3 -1
  15. package/src/runtime/service-supervisor.js +98 -0
  16. package/src/transport/telegram/bot.js +77 -9
  17. package/src/transport/telegram/chat-queue.js +13 -2
  18. package/src/transport/telegram/reply-topic-routing.js +111 -0
  19. package/src/transport/telegram/task-dispatcher.js +26 -3
  20. package/src/transport/telegram/telegram-session-bridge.js +7 -0
  21. package/src/transport/telegram/workspace-topic-store.js +228 -0
  22. package/test/chat-queue.test.js +32 -0
  23. package/test/model-selection.test.js +9 -1
  24. package/test/official-tool-dependencies.test.js +1 -1
  25. package/test/paths.test.js +8 -0
  26. package/test/service-manager.test.js +48 -0
  27. package/test/task-store.test.js +56 -4
  28. package/test/telegram-reply-topic-routing.test.js +94 -0
  29. package/test/telegram-task-dispatcher.test.js +51 -0
  30. package/test/telegram-workspace-topic-store.test.js +124 -0
  31. package/test/tool-registry-run.test.js +41 -0
  32. package/test/weighted-resource-governor.test.js +95 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "repository": "https://github.com/clasen/Arisa.git",
4
- "commit": "43b032cbaa5e4ec42345a822119f06a13f49ff8f",
4
+ "commit": "410d18e354374b43a225c31be574deb4b8f69aba",
5
5
  "tools": {
6
6
  "browser-session-bridge": {
7
7
  "version": "0.1.0",
@@ -31,24 +31,26 @@
31
31
  }
32
32
  },
33
33
  "campaign-draft-runner": {
34
- "version": "0.3.3",
34
+ "version": "0.5.0",
35
35
  "toolDependencies": {
36
36
  "pr-campaign": "^0.1.0",
37
37
  "gmail-workspace": "^0.1.0"
38
38
  },
39
39
  "files": {
40
- "README.md": "fb73d47f6825a1c9b419f6667a319c2cbd959dc7b56c488321db72a6e2210a41",
41
- "config.js": "23b95ad1851e498310eb400b1d53c648c280ddcc73cf91f6fa0ae5dc470ae6d2",
42
- "index.js": "28d58096adb76e00d74b23ef10816199a5af57e13866ed6ab829da2535d73890",
40
+ "README.md": "2c977357f774c6bc3369460ba9cd784c9b75b5f12b338e089e634e09f2a8cbcd",
41
+ "batch-skip.js": "ad428464b739e7355e00ac7cf399247ce611fcef0470d97a73893701e5b07627",
42
+ "config.js": "9b9c2d9686cebf17c3ba2630088c7028281883608f61054e733283aeb8d383cb",
43
+ "index.js": "669bf964a0e1e1a0bacc5997c6e49539022649f405543e5914ba01aa11a99330",
43
44
  "operation-timeout.js": "9b1ced30ec9ed710bb9118b91f6f3feded5111a08e1e21abf163e4cfb91d7ad2",
44
45
  "package.json": "a25b7ed1dd2c30cd68c3c10feb8ee1752cac8cd55cd4344690ffad649e231ec1",
45
46
  "product-facts.js": "69339e20e7b70a6b9833878d72b10b70ea4ede4aa448097975d0961b6aaced59",
46
47
  "search-quality.js": "ab5412d45d5144e976cccef52992e634c11aca0e21b73fd53f9dc2d489c56dd7",
47
48
  "source-exhaustion.js": "407f940f5205eb01715a797672d0dad8bd0b0b897024b5fcb87052a062ecd054",
48
- "telemetry.js": "bab44341ae2176c8a29eed19b89db4e7e251c9938af163ecd85293917d94bfb5",
49
- "test/selection-policy.test.js": "27dacdc6ac3b1c570229ef624267c5bf7e4b9606dbb71214c2a451a30ac3762b",
50
- "test/telemetry.test.js": "e297727e382163f18ab0ec4a21f55bf7e47790939510b615fd8e38c4dfc1998f",
51
- "tool.manifest.json": "1eacb9ccdf0b37bba9745ba20ce3462548bac28c7abb15214e5502e7e53d3e0d"
49
+ "telemetry.js": "984772e73d1cbb761771d6f62954a604333a301182ee74368a41ffab762da367",
50
+ "test/batch-skip.test.js": "638d185dc0b38900afe6ee9058e733ae36583d0762a0ff0347fdf245df064abc",
51
+ "test/selection-policy.test.js": "1283f18b861925aee810d156eb64c54b147304ff7352a8f83b9c2311e1f51de2",
52
+ "test/telemetry.test.js": "daa84b83658226ecd979f15d4e12d94740ffe5280d467ad29677fc1d77734a5b",
53
+ "tool.manifest.json": "71520f3be858b9c2193223e370c10d938484a2af9281087e6dc14410b45cb2ae"
52
54
  }
53
55
  },
54
56
  "creator-scout": {
@@ -65,34 +67,39 @@
65
67
  }
66
68
  },
67
69
  "gmail-workspace": {
68
- "version": "0.1.1",
70
+ "version": "0.1.2",
69
71
  "files": {
70
72
  "config.js": "6ad295ec4cedbc936835d8f86c7528e0939ef09d5792c003308d2ee85dc5eea4",
71
- "index.js": "c4b1c27a3c5d2309501b695058da73f6287e2a6239f6e2384c05daae8ee25d43",
72
- "package.json": "ba87edd5a148da9e99af6b3ab71a0024b86e1a469d56f548c7eeb253bb431f5d",
73
+ "index.js": "729f345fbd82d8e9525d46412b96efac0a30387477385208a4bb1c83fd794992",
74
+ "package.json": "991b2a5c03a049faa9363444ff5fd7517fd3556228dd8bad3d0f554cd3e2dd73",
73
75
  "secretary-state.js": "afc27732670420bccf1ce56ce715676dd2266de9f2d36a92015a93842755094a",
74
76
  "test/secretary-state.test.mjs": "3aea05524a4ad5ebcb79c6aa1db94fcd009ac8ae69414c8f950b32a32af94370",
75
- "tool.manifest.json": "2860c5684b9b9532ab4675f5cc5b96238320209086b132a5eda74ba99b6c294a"
77
+ "tool.manifest.json": "1f9316a957b3a7167e41ece7c2b83f90e69fb390d6dd8c7f99789c538b7a4800",
78
+ "arguments.js": "b4089006818020cc1cd4e5a4cc6e22242939b45a4b42d216fba2269b8cb99e64",
79
+ "test/arguments.test.mjs": "dde82737924d75747490f5535bee2420c71838b3e758925132c825ee8c869bad"
76
80
  }
77
81
  },
78
82
  "magnific-mcp": {
79
- "version": "0.1.1",
83
+ "version": "0.4.1",
80
84
  "toolDependencies": {
81
- "mcp-client": "^0.1.0"
85
+ "mcp-client": "^0.2.0"
82
86
  },
83
87
  "files": {
84
- "README.md": "519b68d10c80112f475ab173394e937b0fc5930758415c1fc65ea2f5536b6371",
88
+ "README.md": "98327aacc89cadd761db2eb49f3892818c72d410f88b86a8b6fe904c4efb3630",
85
89
  "config.js": "98a629781a1b2541806050952aa78a1e6e1b1671a6f1df7ba36c70dd116936a7",
86
90
  "delivery-claims.js": "52f05a5bd1ece1ab21f7d9a49f362f9c55f101ef5723efd642dbecf0236921cd",
91
+ "generation-request.js": "a6daa889b68fa188111b4389c6446daba87fc46fbddcf4bcccba817cf3f0b607",
87
92
  "generation-watch-close.js": "0dd780280eef870d2f11cb1800e6e9de19e5ead7b9d02b9983852cc8ddbdc79b",
88
93
  "generation-watch-plan.js": "eb60660341f254adc224181ca19dbbb969ef1d676aeaab60b71a8567f52648fe",
89
- "index.js": "0dcbd84ad03ab59919df57081d34a45c3a8d59b3c37ac31249409210ae7107e6",
90
- "magnific-api.js": "39fa9a122577029111965c3e8487565c88ce46b61dec5ca685acf1c76cd3b636",
94
+ "index.js": "2eead50413d076173648623285ac65977ec56d77b1275f3ff801c70f900f4bb5",
95
+ "magnific-api.js": "062ba561037760de347542b5bd7db5def1f940e1d8f147a131e5603dfccd22ab",
96
+ "media-output.js": "01aaaeffa64845a200261258dccedf3116f030fafd783db97e846582ec5c9a26",
91
97
  "network.js": "b45e43b49eea98ca7d80921ebfa6daf8b0a85d2079d4653f9eda682bc82101ae",
92
- "package.json": "9c44eb43a3555cf79bd327318f9a30d3060f22dd2fd534923e303cca1eed7de9",
98
+ "package.json": "1842a0efe6021e197a14b54f6661fb9ccdcd26e92aa09a5ac50af6d3cee4367a",
93
99
  "state-store.js": "c3ee744509f7a6be251cd895406bd1c70827f53cbdc1ac9d1c2a6e48479bfe1e",
94
- "test/magnific-mcp.test.js": "7a61dd19a2f6f9f48213cabf0de6e87e0b6fb73a94f0ea9dbfe9c8568c2cc32d",
95
- "tool.manifest.json": "ff886d95250d279eff097430e6f89405a2bdd488d5f0949277cbc3f57f918950"
100
+ "test/magnific-mcp.test.js": "3af971e5a92cf6bd34111b159723892b3a5310952b036386ae98d3592708ddd0",
101
+ "test/media-output.test.js": "ba3802da6c3e7d3c6ef94fcceb10a0b8547921d0995aa24b8484ddb2fb1a28d9",
102
+ "tool.manifest.json": "7d26e0a101c8df81bcfdf502d6864a157f0e690e5636ce297e28b1ed07f7abc2"
96
103
  }
97
104
  },
98
105
  "master-slave": {
@@ -132,35 +139,39 @@
132
139
  }
133
140
  },
134
141
  "mcp-client": {
135
- "version": "0.1.0",
142
+ "version": "0.2.0",
143
+ "toolDependencies": {},
136
144
  "files": {
137
- "README.md": "798125171049f939f4487e1a023cb3a11b3c28aae96b2687df1681a11bad8dc0",
145
+ "README.md": "f7317946d27742eebcc443d69e152c0f9f7e1d55b07192a3875cc1a0d623ce3a",
138
146
  "config.js": "b5b05fb242034e2d04b6301e848da9701c95be195ba1cdac2421964e9a7db8c9",
139
- "index.js": "ba828f353ef23cd7acc3a064f859f553705836e4ede5d31ea0824a761cc88c2a",
147
+ "index.js": "eaca434b3f0a7697955e0dd4cf4aa8dd7c59ab297c10f646b8cc4ec3189311e3",
140
148
  "mcp-session.js": "51765b46f410e1cd49647235b611db0b10af20c9707efcacfbe0ff7dabe3c1ff",
141
149
  "network-security.js": "60a020d4e3c7a4784a5a4e7b7246ee34e63ac73ee2316e09a09d10668dc8a7bc",
142
150
  "oauth-device.js": "698aa9255bae47190c63527db7680aaab4f06392a108a0ff6444e32d31b7b89f",
143
151
  "oauth-watch-plan.js": "b1bbed52eeb0e2e50dd52d733897ebd70573fea3ff5f023b3c63347f92633d3e",
144
152
  "package-lock.json": "fd7b6bf4545de4056908bd656a4a4e21b9f84acc7b06d8c1d581a8c84c295e90",
145
- "package.json": "16d423b77a32902671a751b1d673077c789a5bc3c79254bde296e4a4207fc4e3",
153
+ "package.json": "85ac369f959a40da258d268a3ab1d523abba7c225d5c44a903ddae0bcc512d1b",
146
154
  "state-store.js": "a8fb63096b8efdec8de1cb386eff371f1552f3fd71d96f25036e1c724285e0af",
147
- "test/mcp-client.test.js": "188b253343f551303d1ee6ab2adc34e7153282461ba7399b598fe292e78a22a1",
148
- "tool.manifest.json": "5659c398922c914c5bf893404519a30071ee685ab49f34a650ecafd585b4d6ec"
155
+ "test/mcp-client.test.js": "26a7fe3ffa33ad454eced32c7fab12975ac5c0914041dea68cb7e5467e753a10",
156
+ "tool-routing.js": "b3405c9879443cdc257a1e97d4968859a8654e62ff7cfb90ec3a7a992a62e311",
157
+ "tool.manifest.json": "2d64dc40453fba1d5a6743145f759a28b1f6d137fd84c0e15dd41745bd1692d7"
149
158
  }
150
159
  },
151
160
  "official-tool-sync": {
152
- "version": "1.0.0",
161
+ "version": "1.1.0",
153
162
  "toolDependencies": {
154
163
  "trash": "^1.0.0"
155
164
  },
156
165
  "files": {
157
166
  ".gitignore": "4d56952b0fb13bf8f9b6c13a6d4c34a075bac3af447636a1df4335d7576e2f97",
158
167
  "config.js": "744c445b1d2ff65b77f38a5473c24e50a32f4288c4a08c25a874a478086c08c8",
159
- "index.js": "fdd4167c63cd33c8e7640cb7ce09fd618dbd416030aa920407392e4591543e94",
160
- "package.json": "4447cb5d4e0a04526604dfb9820c44af88349c27e208f27baa148686846559da",
168
+ "dependency-installer.js": "dd91072612d4cba2ce83748ffae569067cbb2ebf63d59084572c3a9b77611927",
169
+ "dependency-installer.test.js": "9dc8a8c7d3ea29cfa2bb2d4daef3c02a6e996f9148a27cc499c8fd628fac857f",
170
+ "index.js": "12186107d449868621af98ce8f434fc84b5de09c62a2cb6b2cb67219b2e856a6",
171
+ "package.json": "0e95a190a04ebaf1eea81a90ecdb7b260595eb1ea0afe77f4d4215ccd6a9e949",
161
172
  "sync-lib.js": "c9f2cd7e19370088ab5fd10f6b2ada52c07d2e405674a356d0989f9a0f4984f6",
162
173
  "sync-lib.test.js": "c97cf850d9f42f6b121a458863b048bdcaa6d7d0475642f20742c5862ebf7cc4",
163
- "tool.manifest.json": "8e0f94ad786bdaf9edb1a65cd03438e26c1e9927e8db9a5a39e349e7ef3c6cd3"
174
+ "tool.manifest.json": "00086b52dc969275e6dffc70720ef916493870fff3a27d076fed4ec04a160c75"
164
175
  }
165
176
  },
166
177
  "pr-campaign": {
@@ -189,18 +200,20 @@
189
200
  }
190
201
  },
191
202
  "telemetry-ledger": {
192
- "version": "0.1.0",
203
+ "version": "0.1.1",
193
204
  "files": {
194
- "README.md": "4cf1b8c7addaa0f6a2d802a7ae261293822c2662efac9c10afa844f9fbcf4e1d",
205
+ "README.md": "e74bcf93bc89de151d73a03d60a74a2699436775aa5f93d27cf3dbb59250aec4",
195
206
  "analysis.js": "c0effbff3af519b018e59c0460d2c277b4f7e1422ca2680de217bcb5e8a13136",
196
207
  "config.js": "ddc8543cde5aeda634b8df60628d0024bb03e65b8710cf4878b4c6a7d953a641",
197
- "index.js": "7df14d142a70b725b32ee40741ce1007fd96a080f96a9b2e3e34d917d88621b5",
198
- "package.json": "e662652038c1c5a6a2a4502b308ae89e1acbd3cb6d3fc494512a730ebe9ccb8a",
208
+ "index.js": "87f302af297233f2a1bc89d9be52b1b724478ed72658123ff841fcbcaa13389b",
209
+ "package.json": "f093c7b767dc042518ff6c19ac302b972a03ba34c45a73545079dd76bf525b75",
199
210
  "stats.js": "fc41b065d1f280dc278c53dc4c4baaa385a5bf25416c3e4ff8a153ea624967fb",
200
211
  "storage.js": "099187e270ccc2bd6d7300b78a2ee31a837f98694a23a3b40b20a646cdb1db1e",
201
212
  "test/telemetry.test.js": "312eef22ad960b19a6fe777aecd520952ae20ed03168edbbef8a0f747bee01c0",
202
- "tool.manifest.json": "e4b321a56db9030e5b39b764547769c76bcf282ed019ebba0323bf16a73dbe05",
203
- "validation.js": "1a50c75a5ca1bb7249bb8bfc3f2927b31ff4accd0efb56a48c36df5adae78f61"
213
+ "tool.manifest.json": "960b714ead9b0491581bf9f0f4dc882ce58bfc29cef4b2f5267cd0dbc9c37d48",
214
+ "validation.js": "1a50c75a5ca1bb7249bb8bfc3f2927b31ff4accd0efb56a48c36df5adae78f61",
215
+ "output.js": "e932098d51efdb99f8c39faaae0c5574d7e263f0e8c0875fc57e7a4311e715ea",
216
+ "test/output.test.js": "c18ae93cdf74de3748c1571833dfc1063ec6690219b03fbc7772bae504b6259f"
204
217
  }
205
218
  },
206
219
  "trash": {
@@ -230,17 +243,18 @@
230
243
  }
231
244
  },
232
245
  "x-dm": {
233
- "version": "0.2.0",
246
+ "version": "0.2.1",
234
247
  "toolDependencies": {
235
248
  "browser-session-bridge": "^0.1.0"
236
249
  },
237
250
  "files": {
238
- "config.js": "69a2dcccd10aa2f1ddcc24d52f4ce8fe4a6f5f5a85be64abbdf9dc9e19de0cd4",
239
- "index.js": "83e3eb7fbeea8a5183303e0e56b1d6854ca7ccb800e4cd2ad689a5f13637380f",
240
- "package.json": "3a76a921b1e78e91b1d2a5ae1926b298dfd6b4ee3f716d5382159b474ee400f4",
251
+ "config.js": "ac6eb70c1bc94230bc6df89fc73fc22ac53ccab49926abc3c453f85aca220596",
252
+ "index.js": "d89f70fed0754c61bd7ce31739304e4512ddabf16bc79a1488c164e32d31672d",
253
+ "package.json": "af4d72a99318d04b522773b8489288d191210435ae5543b564e2ce747164386c",
241
254
  "README.md": "503b0ed25ef9a8df3079523ea653178e6e029854b4d199cb8d20be82cc837f9e",
242
255
  "test/x-dm.test.mjs": "06352996e14777358eb62ad5e5d757b0dab5c0696927ed54ff40a5b7cdefcfd0",
243
- "tool.manifest.json": "177e0abac770f89a0f06aa445363393fa38e7f05ad7cd773ea907fa56faed505"
256
+ "tool.manifest.json": "227dc3c4a48948cf72544d4b99ba2c3c5a255f8425e795304881ede73d82d011",
257
+ "browser-cache.js": "f71c20f9ab5f04d9d46c5e2551b348d47baba89a10a07173070298098a6d5551"
244
258
  }
245
259
  }
246
260
  }
@@ -114,7 +114,7 @@ export async function createApp({ logger, runtimeOverrides, requestRestart } = {
114
114
  }
115
115
 
116
116
  const artifactStore = new ArtifactStore();
117
- const toolRegistry = new ToolRegistry({ logger });
117
+ const toolRegistry = new ToolRegistry({ logger, executionPolicy: config.toolExecution });
118
118
  const toolProcessSupervisor = createToolProcessSupervisor({ logger, policy: config.daemons, toolRegistry });
119
119
  const taskStore = new TaskStore();
120
120
  await toolRegistry.load();
@@ -14,7 +14,7 @@ export async function createHeadlessApp({
14
14
  configLoader = loadConfig,
15
15
  artifactStoreFactory = () => new ArtifactStore(),
16
16
  taskStoreFactory = () => new TaskStore(),
17
- toolRegistryFactory = () => new ToolRegistry({ logger }),
17
+ toolRegistryFactory = (options) => new ToolRegistry(options),
18
18
  supervisorFactory = (options) => createToolProcessSupervisor(options),
19
19
  capabilitiesFactory = (options) => createArisaCapabilities(options),
20
20
  ipcServerFactory = (options) => createIpcServer(options)
@@ -23,7 +23,7 @@ export async function createHeadlessApp({
23
23
  const config = await configLoader();
24
24
  const artifactStore = artifactStoreFactory();
25
25
  const taskStore = taskStoreFactory();
26
- const toolRegistry = toolRegistryFactory();
26
+ const toolRegistry = toolRegistryFactory({ logger, executionPolicy: config.toolExecution });
27
27
  await toolRegistry.load();
28
28
  const toolProcessSupervisor = supervisorFactory({ logger, policy: config.daemons, toolRegistry });
29
29
  const toolExecutor = createHeadlessToolExecutor({ artifactStore, taskStore, toolRegistry });
@@ -65,6 +65,10 @@ export function getChatToolResourceNotesFile(chatId) {
65
65
  return path.join(getChatDir(chatId), "state", "tool-resource-notes.json");
66
66
  }
67
67
 
68
+ export function getChatTelegramWorkspacesFile(chatId) {
69
+ return path.join(getChatDir(chatId), "state", "telegram-workspaces.json");
70
+ }
71
+
68
72
  export function getChatToolStateDir(chatId, toolName) {
69
73
  return path.join(getChatDir(chatId), "state", "tools", requireToolName(toolName));
70
74
  }
@@ -78,7 +78,9 @@ export async function handoffServiceRestart({ verbose = true, cliArgs = [] } = {
78
78
  } = {}) {
79
79
  await ensureHome();
80
80
  const status = await getStatus();
81
- if (!status.running || status.pid !== currentPid) {
81
+ const supervisorPid = Number.parseInt(environment.ARISA_SUPERVISOR_PID || "", 10);
82
+ const isActiveWorker = Number.isSafeInteger(supervisorPid) && supervisorPid === status.pid;
83
+ if (!status.running || (status.pid !== currentPid && !isActiveWorker)) {
82
84
  throw new Error("Service restart handoff requires the active background service process");
83
85
  }
84
86
  const logHandle = await openLog(serviceLogFile, "a");
@@ -0,0 +1,98 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+
7
+ function errorMessage(error) {
8
+ return error instanceof Error ? error.message : String(error);
9
+ }
10
+
11
+ function boundedInteger(value, fallback, minimum, maximum) {
12
+ const parsed = Number(value);
13
+ return Number.isSafeInteger(parsed) && parsed >= minimum ? Math.min(parsed, maximum) : fallback;
14
+ }
15
+
16
+ export function createServiceSupervisor({
17
+ command,
18
+ args,
19
+ env = process.env,
20
+ restartLimit = 3,
21
+ restartBackoffMs = 2_000,
22
+ restartBackoffMaxMs = 60_000,
23
+ stableRuntimeMs = 60_000,
24
+ logger,
25
+ spawnProcess = spawn,
26
+ wait = sleep
27
+ }) {
28
+ const maximumRestarts = boundedInteger(restartLimit, 3, 0, 10);
29
+ const initialBackoffMs = boundedInteger(restartBackoffMs, 2_000, 1, 60_000);
30
+ const maximumBackoffMs = boundedInteger(restartBackoffMaxMs, 60_000, initialBackoffMs, 5 * 60_000);
31
+ const stableAfterMs = boundedInteger(stableRuntimeMs, 60_000, 1_000, 60 * 60_000);
32
+ let child = null;
33
+ let stopping = false;
34
+ let wakeBackoff = null;
35
+ let resolveDone;
36
+ const done = new Promise((resolve) => { resolveDone = resolve; });
37
+
38
+ async function run() {
39
+ let consecutiveFailures = 0;
40
+ while (!stopping) {
41
+ const startedAt = Date.now();
42
+ child = spawnProcess(command, args, {
43
+ stdio: "inherit",
44
+ env: { ...env, ARISA_SUPERVISOR_PID: String(process.pid) }
45
+ });
46
+ logger?.log("service", `worker started (pid ${child.pid})`);
47
+
48
+ const outcome = await new Promise((resolve) => {
49
+ child.once("error", (error) => resolve({ error }));
50
+ child.once("exit", (code, signal) => resolve({ code, signal }));
51
+ });
52
+ child = null;
53
+ if (stopping) break;
54
+
55
+ const runtimeMs = Date.now() - startedAt;
56
+ if (runtimeMs >= stableAfterMs) consecutiveFailures = 0;
57
+ consecutiveFailures += 1;
58
+ const detail = outcome.error
59
+ ? errorMessage(outcome.error)
60
+ : `code=${outcome.code ?? "null"} signal=${outcome.signal || "none"}`;
61
+ logger?.error("service", `worker exited unexpectedly (${detail})`);
62
+ if (consecutiveFailures > maximumRestarts) {
63
+ logger?.error("service", `worker restart limit reached after ${consecutiveFailures} consecutive exits`);
64
+ break;
65
+ }
66
+
67
+ const delay = Math.min(maximumBackoffMs, initialBackoffMs * (2 ** (consecutiveFailures - 1)));
68
+ logger?.log("service", `restarting worker in ${delay}ms`);
69
+ await Promise.race([
70
+ wait(delay),
71
+ new Promise((resolve) => { wakeBackoff = resolve; })
72
+ ]);
73
+ wakeBackoff = null;
74
+ }
75
+ resolveDone();
76
+ }
77
+
78
+ return {
79
+ async start() {
80
+ run().catch((error) => {
81
+ logger?.error("service", `supervisor failed: ${errorMessage(error)}`);
82
+ resolveDone();
83
+ });
84
+ return done;
85
+ },
86
+ async stop() {
87
+ stopping = true;
88
+ wakeBackoff?.();
89
+ if (child) {
90
+ const activeChild = child;
91
+ const exited = new Promise((resolve) => activeChild.once("exit", resolve));
92
+ activeChild.kill("SIGTERM");
93
+ await exited;
94
+ }
95
+ resolveDone();
96
+ }
97
+ };
98
+ }
@@ -18,6 +18,12 @@ import { createTelegramSessionBridgeController } from "./telegram-session-bridge
18
18
  import { createTelegramToolsCommandHandler } from "./telegram-tools-command.js";
19
19
  import { createTelegramWorkspaceController } from "./telegram-workspace-controller.js";
20
20
  import { resolveTelegramWorkspaceRoute } from "./workspace-group.js";
21
+ import {
22
+ appendGeneralReplyRoutingInstruction,
23
+ isGeneralWorkspaceRoute,
24
+ routeGeneralWorkspaceReply
25
+ } from "./reply-topic-routing.js";
26
+ import { migrateLegacyReplyTopics, WorkspaceTopicStore } from "./workspace-topic-store.js";
21
27
  import {
22
28
  buildNewSessionPrompt,
23
29
  buildPrompt,
@@ -174,6 +180,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
174
180
  const bot = new Bot(config.telegram.token);
175
181
  const perChatState = createChatStateStore();
176
182
  const sessionSeeds = new SessionSeedStore();
183
+ const workspaceTopics = new WorkspaceTopicStore();
177
184
  const notifiedPromptErrors = new WeakSet();
178
185
  let taskTimer = null;
179
186
  const { authorizeContext, contextRoute, registerRoute } = createTelegramWorkspaceController({
@@ -276,7 +283,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
276
283
  if (normalizationRequired && !transcript) {
277
284
  logger?.log("telegram", `media normalization unavailable for chat ${route.transportChatId}: ${toolResult?.error || toolResult?.missingConfig?.join(", ") || "unknown error"}`);
278
285
  }
279
- return buildPrompt({ ctx, artifact, transcript, toolResult });
286
+ const prompt = buildPrompt({ ctx, artifact, transcript, toolResult });
287
+ if (!isGeneralWorkspaceRoute(route)) return prompt;
288
+ const [topics, recentProposals] = await Promise.all([
289
+ workspaceTopics.listTopics(route.ownerChatId, route.transportChatId),
290
+ workspaceTopics.listRecentProposals(route.ownerChatId, route.transportChatId)
291
+ ]);
292
+ return appendGeneralReplyRoutingInstruction(prompt, topics, recentProposals);
280
293
  }
281
294
 
282
295
  const {
@@ -290,6 +303,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
290
303
  agentManager,
291
304
  artifactStore,
292
305
  sessionSeeds,
306
+ workspaceTopics,
293
307
  getChatState,
294
308
  buildTopicInitializationHandoff,
295
309
  logger
@@ -354,13 +368,29 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
354
368
  }
355
369
  executionReceipt?.resolve({ status: "executed" });
356
370
  if (text) {
357
- await createWorkspaceAccessGuard(route)();
371
+ const topics = route.workspace && route.ownerChatId
372
+ ? await workspaceTopics.listTopics(route.ownerChatId, route.transportChatId)
373
+ : [];
374
+ const routedReply = routeGeneralWorkspaceReply({ route, text, topics });
375
+ if (routedReply.proposal) {
376
+ await workspaceTopics.recordProposal(route.ownerChatId, route.transportChatId, routedReply.proposal);
377
+ logger?.log("telegram", `recorded topic proposal ${routedReply.proposal} for workspace ${route.transportChatId}`);
378
+ }
379
+ if (!routedReply.text) return;
380
+ const deliveryRoute = routedReply.route;
381
+ const deliveryOptions = (extra = {}) => deliveryRoute.workspace && deliveryRoute.threadId
382
+ ? { ...extra, message_thread_id: deliveryRoute.threadId }
383
+ : extra;
384
+ await createWorkspaceAccessGuard(deliveryRoute)();
385
+ if (routedReply.topic) {
386
+ logger?.log("telegram", `routing General reply to topic ${routedReply.topic.threadId} (${routedReply.topic.name})`);
387
+ }
358
388
  await sendTextReply({
359
- sendText: (message, extra) => bot.api.sendMessage(route.transportChatId, message, messageOptions(extra)),
360
- sendDocument: (file, extra) => bot.api.sendDocument(route.transportChatId, file, messageOptions(extra)),
389
+ sendText: (message, extra) => bot.api.sendMessage(deliveryRoute.transportChatId, message, deliveryOptions(extra)),
390
+ sendDocument: (file, extra) => bot.api.sendDocument(deliveryRoute.transportChatId, file, deliveryOptions(extra)),
361
391
  chatId: sessionId,
362
- artifactChatId: route.scopeChatId,
363
- text
392
+ artifactChatId: deliveryRoute.scopeChatId,
393
+ text: routedReply.text
364
394
  });
365
395
  }
366
396
  };
@@ -377,10 +407,11 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
377
407
  replaceQueued = false,
378
408
  busyMessageMode = "queue",
379
409
  waitForExecution = false,
410
+ onExecutionStart = null,
380
411
  coalesceQueued = false
381
412
  }) {
382
413
  const chatState = getChatState(chatId);
383
- const receipt = waitForExecution ? createPromptExecutionReceipt() : null;
414
+ const receipt = waitForExecution ? createPromptExecutionReceipt(onExecutionStart) : null;
384
415
 
385
416
  if (chatState.processing) {
386
417
  const incomingRoute = ctx ? contextRoute(ctx) : null;
@@ -474,6 +505,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
474
505
  });
475
506
  }
476
507
 
508
+ async function migrateConfiguredReplyTopics() {
509
+ const migratedGroups = await migrateLegacyReplyTopics(config, workspaceTopics);
510
+ if (!migratedGroups) return;
511
+ await saveConfig(config);
512
+ logger?.log("telegram", `migrated configured reply topics for ${migratedGroups} workspace group(s)`);
513
+ }
514
+
477
515
  async function sendStartupMessages() {
478
516
  for (const chatId of config.telegram.authorizedChatIds || []) {
479
517
  try {
@@ -505,7 +543,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
505
543
  timer.unref?.();
506
544
  }
507
545
 
508
- async function enqueueAsyncPrompt({ chatId, prompt, label, route: taskRoute }) {
546
+ async function enqueueAsyncPrompt({ chatId, prompt, label, route: taskRoute, timeoutMs }) {
509
547
  let ctx = { chat: { id: chatId }, api: bot.api };
510
548
  const destination = taskRoute?.transport === "telegram" ? taskRoute.destination : null;
511
549
  if (destination?.chatId && destination?.threadId) {
@@ -522,7 +560,32 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
522
560
  const route = contextRoute(ctx);
523
561
  const chatState = getChatState(route.sessionId);
524
562
  if (chatState.processing) await ensureQueuedTelegramTyping(chatState, ctx);
525
- return enqueuePrompt({ chatId: route.sessionId, prompt, label, ctx, waitForExecution: true });
563
+ let timer = null;
564
+ const execution = enqueuePrompt({
565
+ chatId: route.sessionId,
566
+ prompt,
567
+ label,
568
+ ctx,
569
+ waitForExecution: true,
570
+ onExecutionStart: ({ reject }) => {
571
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return;
572
+ timer = setTimeout(() => {
573
+ const error = new Error(`${label} exceeded its ${timeoutMs}ms execution deadline`);
574
+ error.retryable = false;
575
+ error.outcomeUncertain = true;
576
+ reject(error);
577
+ agentManager.abortSession(route.sessionId).catch((abortError) => {
578
+ logger?.error("tasks", `${label} abort failed: ${getErrorMessage(abortError)}`);
579
+ });
580
+ }, timeoutMs);
581
+ timer.unref?.();
582
+ }
583
+ });
584
+ execution.then(
585
+ () => { if (timer) clearTimeout(timer); },
586
+ () => { if (timer) clearTimeout(timer); }
587
+ );
588
+ return execution;
526
589
  }
527
590
 
528
591
  const { dispatchDueTasks } = createTelegramTaskDispatcher({
@@ -533,6 +596,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
533
596
  toolRegistry,
534
597
  resourceNotes,
535
598
  agentManager,
599
+ taskTimeouts: config.tasks,
536
600
  logger
537
601
  });
538
602
 
@@ -737,6 +801,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
737
801
  bot.on("message", async (ctx) => {
738
802
  const auth = await authorizeContext(ctx);
739
803
  if (!auth.ok) return;
804
+ await workspaceTopics.observeMessage(contextRoute(ctx), ctx.message).catch((error) => {
805
+ logger?.error("telegram", `workspace topic observation failed: ${getErrorMessage(error)}`);
806
+ });
740
807
  if (!isProcessableTelegramMessage(ctx.message)) return;
741
808
 
742
809
  const command = getTelegramCommand(ctx);
@@ -765,6 +832,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
765
832
  return {
766
833
  async start({ skipAgentStartupPrompts = false } = {}) {
767
834
  config.telegram.chatMeta ||= {};
835
+ await migrateConfiguredReplyTopics();
768
836
  await bot.api.setMyCommands(telegramCommands);
769
837
  if (!taskTimer) {
770
838
  taskTimer = setInterval(() => {
@@ -31,14 +31,24 @@ export function createChatStateStore() {
31
31
  };
32
32
  }
33
33
 
34
- export function createPromptExecutionReceipt() {
34
+ export function createPromptExecutionReceipt(onStart = null) {
35
35
  let resolve;
36
36
  let reject;
37
+ let started = false;
37
38
  const promise = new Promise((resolvePromise, rejectPromise) => {
38
39
  resolve = resolvePromise;
39
40
  reject = rejectPromise;
40
41
  });
41
- return { promise, resolve, reject };
42
+ return {
43
+ promise,
44
+ resolve,
45
+ reject,
46
+ start() {
47
+ if (started) return;
48
+ started = true;
49
+ onStart?.({ resolve, reject, promise });
50
+ }
51
+ };
42
52
  }
43
53
 
44
54
  function rejectQueuedReceipts(chatState, error) {
@@ -169,6 +179,7 @@ export async function drainChatPromptQueue({
169
179
  chatState.continueAfterClose = false;
170
180
  }
171
181
  try {
182
+ currentReceipt?.start?.();
172
183
  await processPrompt({ prompt: currentPrompt, ctx: currentCtx, receipt: currentReceipt });
173
184
  currentReceipt?.resolve({ status: "completed" });
174
185
  } catch (error) {