@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 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.2"]
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.2`.
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.2`, keep the rest of the `plugin` array unchanged, and restart OpenCode.
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=plan, idle status, incomplete plan, and no active boulder before dispatching.
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
 
@@ -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) return;
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 === "plan";
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 === "plan"
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 !== "plan") {
2538
+ if (!isPlanSessionAgent(agent)) {
2501
2539
  await sendPlain(
2502
2540
  bot,
2503
- `${index}\uBC88 \uC138\uC158\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 'plan' \uC774 \uC544\uB2D9\uB2C8\uB2E4 (\uD604\uC7AC: ${agent ?? "unknown"}). /start_work \uB294 plan \uC138\uC158\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4`
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 &lt;\uBC88\uD638&gt;</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: agent='plan' AND status=idle AND .omo/plans \uC5D0 \uBBF8\uC644\uB8CC plan \uC874\uC7AC AND .omo/boulder.json \uBD80\uC7AC.
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 = `/permission/${encodeURIComponent(requestID)}/reply`;
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 = `/session/${encodeURIComponent(sessionID)}/permissions/${encodeURIComponent(requestID)}`;
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.2",
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",