@coinseeker/opencode-telegram-plugin 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/telegram-remote.js +62 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,15 +15,15 @@ Configure the npm package in `~/.config/opencode/opencode.json`:
|
|
|
15
15
|
|
|
16
16
|
```json
|
|
17
17
|
{
|
|
18
|
-
"plugin": ["@coinseeker/opencode-telegram-plugin@1.1.
|
|
18
|
+
"plugin": ["@coinseeker/opencode-telegram-plugin@1.1.4"]
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Current stable version: `@coinseeker/opencode-telegram-plugin@1.1.
|
|
22
|
+
Current stable version: `@coinseeker/opencode-telegram-plugin@1.1.4`.
|
|
23
23
|
|
|
24
24
|
Restart OpenCode after editing the config. OpenCode resolves npm package plugins on startup.
|
|
25
25
|
|
|
26
|
-
To update an existing install, replace the previous pinned package entry with `@coinseeker/opencode-telegram-plugin@1.1.
|
|
26
|
+
To update an existing install, replace the previous pinned package entry with `@coinseeker/opencode-telegram-plugin@1.1.4`, keep the rest of the `plugin` array unchanged, and restart OpenCode.
|
|
27
27
|
|
|
28
28
|
## Configure Telegram
|
|
29
29
|
|
|
@@ -62,7 +62,7 @@ Keep this file private. Never commit or share your Telegram bot token.
|
|
|
62
62
|
- Multi-session-safe Telegram polling through a file-lock leader model.
|
|
63
63
|
- Log file output instead of stdout terminal spam.
|
|
64
64
|
- Cross-process remote session listing via `/sessions`, `/status N`, `/start_work N`, `/help` slash commands.
|
|
65
|
-
- Safety-gated remote `/start-work` execution: verifies agent
|
|
65
|
+
- Safety-gated remote `/start-work` execution: verifies a raw `plan` agent or Prometheus Plan Builder label, idle status, incomplete plan, and no active boulder before dispatching.
|
|
66
66
|
|
|
67
67
|
## Logs
|
|
68
68
|
|
package/dist/telegram-remote.js
CHANGED
|
@@ -384,6 +384,8 @@ function parsePending(text) {
|
|
|
384
384
|
throw new Error("Invalid pending permission: requestID");
|
|
385
385
|
if (typeof parsed.sessionID !== "string")
|
|
386
386
|
throw new Error("Invalid pending permission: sessionID");
|
|
387
|
+
if (parsed.directory !== void 0 && typeof parsed.directory !== "string")
|
|
388
|
+
throw new Error("Invalid pending permission: directory");
|
|
387
389
|
if (typeof parsed.title !== "string") throw new Error("Invalid pending permission: title");
|
|
388
390
|
if (typeof parsed.permission !== "string")
|
|
389
391
|
throw new Error("Invalid pending permission: permission");
|
|
@@ -537,10 +539,34 @@ function replyLabel(reply) {
|
|
|
537
539
|
if (reply === "always") return "Always allowed";
|
|
538
540
|
return "Rejected";
|
|
539
541
|
}
|
|
542
|
+
async function upgradeLegacyPendingPermission(permission, ctx) {
|
|
543
|
+
const found = await ctx.pendingPermissions.findByRequestID(
|
|
544
|
+
permission.requestID,
|
|
545
|
+
permission.sessionID,
|
|
546
|
+
ctx.serverUrl.href
|
|
547
|
+
);
|
|
548
|
+
if (!found || found.data.endpoint === "request") return;
|
|
549
|
+
await ctx.pendingPermissions.savePending(found.shortHash, {
|
|
550
|
+
...found.data,
|
|
551
|
+
directory: ctx.directory,
|
|
552
|
+
title: permission.title,
|
|
553
|
+
permission: permission.permission,
|
|
554
|
+
patterns: permission.patterns,
|
|
555
|
+
always: permission.always,
|
|
556
|
+
endpoint: "request"
|
|
557
|
+
});
|
|
558
|
+
ctx.logger.info("permission pending upgraded to request endpoint", {
|
|
559
|
+
requestID: permission.requestID,
|
|
560
|
+
sessionID: permission.sessionID
|
|
561
|
+
});
|
|
562
|
+
}
|
|
540
563
|
async function handleNormalizedPermission(permission, ctx) {
|
|
541
564
|
const permissionKey = `${ctx.serverUrl.href}:${permission.sessionID}:${permission.requestID}`;
|
|
542
565
|
const claimed = await claimOnce({ claimsDir: ctx.claimsDir, key: `permission:${permissionKey}` });
|
|
543
|
-
if (!claimed)
|
|
566
|
+
if (!claimed) {
|
|
567
|
+
if (permission.endpoint === "request") await upgradeLegacyPendingPermission(permission, ctx);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
544
570
|
const shortHash = createPermissionShortHash(
|
|
545
571
|
permission.requestID,
|
|
546
572
|
permission.sessionID,
|
|
@@ -558,6 +584,7 @@ async function handleNormalizedPermission(permission, ctx) {
|
|
|
558
584
|
requestID: permission.requestID,
|
|
559
585
|
sessionID: permission.sessionID,
|
|
560
586
|
serverUrl: ctx.serverUrl.href,
|
|
587
|
+
directory: ctx.directory,
|
|
561
588
|
title: permission.title,
|
|
562
589
|
permission: permission.permission,
|
|
563
590
|
patterns: permission.patterns,
|
|
@@ -641,7 +668,8 @@ function createPermissionDispatcher(ctx) {
|
|
|
641
668
|
pending.sessionID,
|
|
642
669
|
reply,
|
|
643
670
|
pending.endpoint,
|
|
644
|
-
pending.serverUrl
|
|
671
|
+
pending.serverUrl,
|
|
672
|
+
pending.directory
|
|
645
673
|
);
|
|
646
674
|
await ctx.bot.editMessageRemoveKeyboard(
|
|
647
675
|
messageId,
|
|
@@ -661,7 +689,10 @@ ${pending.permission}: ${pending.title}`
|
|
|
661
689
|
);
|
|
662
690
|
ctx.logger.error("failed to send permission reply", {
|
|
663
691
|
error: String(err),
|
|
664
|
-
requestID: pending.requestID
|
|
692
|
+
requestID: pending.requestID,
|
|
693
|
+
endpoint: pending.endpoint,
|
|
694
|
+
serverUrl: pending.serverUrl,
|
|
695
|
+
directory: pending.directory
|
|
665
696
|
});
|
|
666
697
|
} finally {
|
|
667
698
|
await ctx.pendingPermissions.deletePending(shortHash);
|
|
@@ -1398,6 +1429,13 @@ async function handleSessionError(event, ctx) {
|
|
|
1398
1429
|
ctx.logger.info("session abort recorded", { sessionId: event.properties.sessionID ?? "global" });
|
|
1399
1430
|
}
|
|
1400
1431
|
|
|
1432
|
+
// src/lib/plan-agent.ts
|
|
1433
|
+
function isPlanSessionAgent(agent) {
|
|
1434
|
+
if (!agent) return false;
|
|
1435
|
+
const normalized = agent.trim().replace(/[–—]/g, "-").replace(/\s+/g, " ").toLowerCase();
|
|
1436
|
+
return normalized === "plan" || normalized === "prometheus" || normalized === "prometheus - plan builder" || normalized === "prometheus (plan builder)";
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1401
1439
|
// src/lib/pending-start-work.ts
|
|
1402
1440
|
import { createHash as createHash4 } from "crypto";
|
|
1403
1441
|
import { mkdir as mkdir5, readdir as readdir5, readFile as readFile4, rename as rename4, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -1624,7 +1662,7 @@ async function sendIdleNotification(sessionId, ctx) {
|
|
|
1624
1662
|
if (!claimed) return;
|
|
1625
1663
|
const title = ctx.sessionTitleService.getSessionTitle(sessionId);
|
|
1626
1664
|
const agent = ctx.sessionTitleService.getSessionAgent(sessionId);
|
|
1627
|
-
const isPlanSession = agent
|
|
1665
|
+
const isPlanSession = isPlanSessionAgent(agent);
|
|
1628
1666
|
const text = isPlanSession ? planCompleteMessage(title) : agentFinishedMessage(title, agent);
|
|
1629
1667
|
try {
|
|
1630
1668
|
if (isPlanSession) {
|
|
@@ -2378,7 +2416,7 @@ function createStatusDispatcher(deps) {
|
|
|
2378
2416
|
projectRoot,
|
|
2379
2417
|
sessionId: entry.sessionId,
|
|
2380
2418
|
planHint: rawTitle,
|
|
2381
|
-
allowLatestFallback: rawAgent
|
|
2419
|
+
allowLatestFallback: isPlanSessionAgent(rawAgent)
|
|
2382
2420
|
});
|
|
2383
2421
|
const userSnippet = buildSnippet(findLastByRole(messages, "user"));
|
|
2384
2422
|
const assistantSnippet = buildSnippet(findLastByRole(messages, "assistant"));
|
|
@@ -2497,10 +2535,10 @@ function createStartWorkCommandDispatcher(deps) {
|
|
|
2497
2535
|
return;
|
|
2498
2536
|
}
|
|
2499
2537
|
const agent = deps.sessionTitleService.getSessionAgent(sessionId) ?? agentFromSession3(session) ?? entry.agent;
|
|
2500
|
-
if (agent
|
|
2538
|
+
if (!isPlanSessionAgent(agent)) {
|
|
2501
2539
|
await sendPlain(
|
|
2502
2540
|
bot,
|
|
2503
|
-
`${index}\uBC88 \uC138\uC158\uC758 \uC5D0\uC774\uC804\uD2B8\uB294
|
|
2541
|
+
`${index}\uBC88 \uC138\uC158\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 plan builder \uAC00 \uC544\uB2D9\uB2C8\uB2E4 (\uD604\uC7AC: ${agent ?? "unknown"}). /start_work \uB294 plan \uC138\uC158\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4`
|
|
2504
2542
|
);
|
|
2505
2543
|
return;
|
|
2506
2544
|
}
|
|
@@ -2546,7 +2584,7 @@ var HELP_TEXT = `<b>OpenCode Telegram Plugin \u2014 \uBA85\uB839 \uB3C4\uC6C0\uB
|
|
|
2546
2584
|
|
|
2547
2585
|
<b>/start_work <\uBC88\uD638></b>
|
|
2548
2586
|
\uD574\uB2F9 \uC138\uC158\uC5D0 opencode <code>/start-work</code> \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC \uC804\uC1A1.
|
|
2549
|
-
\uC548\uC804 \uAC8C\uC774\uD2B8:
|
|
2587
|
+
\uC548\uC804 \uAC8C\uC774\uD2B8: raw plan agent \uB610\uB294 Prometheus Plan Builder \uB77C\uBCA8 AND status=idle AND .omo/plans \uC5D0 \uBBF8\uC644\uB8CC plan \uC874\uC7AC AND .omo/boulder.json \uBD80\uC7AC.
|
|
2550
2588
|
\uC870\uAC74 \uBBF8\uCDA9\uC871\uC2DC \uAD6C\uCCB4\uC801 \uC0AC\uC720 \uC548\uB0B4.
|
|
2551
2589
|
(Telegram \uBD07 \uBA85\uB839\uC740 <code>/start_work</code>, \uB0B4\uBD80 \uD2B8\uB9AC\uAC70 \uB300\uC0C1\uC740 opencode \uC758 <code>/start-work</code>)
|
|
2552
2590
|
|
|
@@ -3097,6 +3135,12 @@ var SessionTitleService = class {
|
|
|
3097
3135
|
|
|
3098
3136
|
// src/telegram-remote.ts
|
|
3099
3137
|
var pluginDir = dirname6(fileURLToPath(import.meta.url));
|
|
3138
|
+
function withDirectoryQuery(path, directory) {
|
|
3139
|
+
if (directory === void 0) return path;
|
|
3140
|
+
const url = new URL(path, "http://opencode.local");
|
|
3141
|
+
url.searchParams.set("directory", directory);
|
|
3142
|
+
return `${url.pathname}${url.search}`;
|
|
3143
|
+
}
|
|
3100
3144
|
async function postToServer(serverUrl, path, body) {
|
|
3101
3145
|
const safeServerUrl = normalizeOpenCodeServerUrl(serverUrl);
|
|
3102
3146
|
if (!safeServerUrl) throw new Error("Invalid OpenCode server URL");
|
|
@@ -3169,9 +3213,12 @@ var TelegramRemote = async (input) => {
|
|
|
3169
3213
|
throwOnError: true
|
|
3170
3214
|
});
|
|
3171
3215
|
};
|
|
3172
|
-
const replyToPermission = async (requestID, sessionID, reply, endpoint2, serverUrl = input.serverUrl.href) => {
|
|
3216
|
+
const replyToPermission = async (requestID, sessionID, reply, endpoint2, serverUrl = input.serverUrl.href, directory = input.directory) => {
|
|
3173
3217
|
if (endpoint2 === "request") {
|
|
3174
|
-
const path2 =
|
|
3218
|
+
const path2 = withDirectoryQuery(
|
|
3219
|
+
`/permission/${encodeURIComponent(requestID)}/reply`,
|
|
3220
|
+
directory
|
|
3221
|
+
);
|
|
3175
3222
|
if (serverUrl !== input.serverUrl.href) {
|
|
3176
3223
|
await postToServer(serverUrl, path2, { reply });
|
|
3177
3224
|
return;
|
|
@@ -3184,7 +3231,10 @@ var TelegramRemote = async (input) => {
|
|
|
3184
3231
|
});
|
|
3185
3232
|
return;
|
|
3186
3233
|
}
|
|
3187
|
-
const path =
|
|
3234
|
+
const path = withDirectoryQuery(
|
|
3235
|
+
`/session/${encodeURIComponent(sessionID)}/permissions/${encodeURIComponent(requestID)}`,
|
|
3236
|
+
directory
|
|
3237
|
+
);
|
|
3188
3238
|
if (serverUrl !== input.serverUrl.href) {
|
|
3189
3239
|
await postToServer(serverUrl, path, { response: reply });
|
|
3190
3240
|
return;
|
|
@@ -3286,6 +3336,7 @@ var TelegramRemote = async (input) => {
|
|
|
3286
3336
|
claimsDir,
|
|
3287
3337
|
pluginDir,
|
|
3288
3338
|
serverUrl: input.serverUrl,
|
|
3339
|
+
directory: input.directory,
|
|
3289
3340
|
tokenHash,
|
|
3290
3341
|
pendingQuestions,
|
|
3291
3342
|
pendingPermissions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinseeker/opencode-telegram-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Control and monitor OpenCode from Telegram with notifications, question replies, and subagent-aware completion.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/telegram-remote.js",
|