@coinseeker/opencode-telegram-plugin 1.1.6 → 1.1.7

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.6"]
18
+ "plugin": ["@coinseeker/opencode-telegram-plugin@1.1.7"]
19
19
  }
20
20
  ```
21
21
 
22
- Current stable version: `@coinseeker/opencode-telegram-plugin@1.1.6`.
22
+ Current stable version: `@coinseeker/opencode-telegram-plugin@1.1.7`.
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.6`, 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.7`, keep the rest of the `plugin` array unchanged, and restart OpenCode.
27
27
 
28
28
  ## Configure Telegram
29
29
 
@@ -1655,6 +1655,8 @@ function createStartWorkDispatcher(ctx) {
1655
1655
  // src/events/session-idle.ts
1656
1656
  var ROOT_IDLE_RECHECK_DELAY_MS = 2500;
1657
1657
  var DEFERRED_PARENT_CONFIRM_DELAY_MS = 2500;
1658
+ var PLAN_COMPLETION_MESSAGE_LIMIT = 5;
1659
+ var START_WORK_COMMAND_RE = /(?:^|[\s`"'(])\/?start[_-]work(?:$|[\s`"').,!?])/i;
1658
1660
  var deferredConfirmTimers = /* @__PURE__ */ new Map();
1659
1661
  function sleep(ms) {
1660
1662
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -1666,6 +1668,42 @@ function agentFinishedMessage(title, agent) {
1666
1668
  function selectPlanSessionAgent(candidates) {
1667
1669
  return candidates.find(isPlanSessionAgent) ?? candidates.find((agent) => agent !== void 0);
1668
1670
  }
1671
+ function extractTextFromParts(parts) {
1672
+ const pieces = [];
1673
+ for (const part of parts) {
1674
+ if (part.type === "text" && typeof part.text === "string") pieces.push(part.text);
1675
+ }
1676
+ return pieces.join(" ");
1677
+ }
1678
+ function findLatestAssistantMessage(messages) {
1679
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
1680
+ const message = messages[i];
1681
+ if (message?.info.role === "assistant") return message;
1682
+ }
1683
+ return void 0;
1684
+ }
1685
+ function hasStartWorkCommandInstruction(text) {
1686
+ return START_WORK_COMMAND_RE.test(text);
1687
+ }
1688
+ async function latestAssistantText(sessionId, ctx) {
1689
+ try {
1690
+ const result = await ctx.client.session.messages({
1691
+ path: { id: sessionId },
1692
+ query: { limit: PLAN_COMPLETION_MESSAGE_LIMIT }
1693
+ });
1694
+ const message = findLatestAssistantMessage(normalizeMessages(result.data));
1695
+ return message ? extractTextFromParts(message.parts) : void 0;
1696
+ } catch (err) {
1697
+ ctx.logger.warn("plan completion message lookup failed", { sessionId, error: String(err) });
1698
+ return void 0;
1699
+ }
1700
+ }
1701
+ async function shouldSendPlanCompletion(sessionId, ctx) {
1702
+ const text = await latestAssistantText(sessionId, ctx);
1703
+ if (text !== void 0 && hasStartWorkCommandInstruction(text)) return true;
1704
+ ctx.logger.info("skipping plan completion notice - no start-work instruction", { sessionId });
1705
+ return false;
1706
+ }
1669
1707
  async function resolveSessionAgent(sessionId, ctx) {
1670
1708
  const candidates = [
1671
1709
  ctx.sessionTitleService.getSessionAgent(sessionId)
@@ -1745,15 +1783,16 @@ async function sendIdleNotification(sessionId, ctx) {
1745
1783
  ctx.logger.info("idle suppressed - session was aborted", { sessionId });
1746
1784
  return;
1747
1785
  }
1786
+ const title = ctx.sessionTitleService.getSessionTitle(sessionId);
1787
+ const agent = await resolveSessionAgent(sessionId, ctx);
1788
+ const isPlanSession = isPlanSessionAgent(agent);
1789
+ if (isPlanSession && !await shouldSendPlanCompletion(sessionId, ctx)) return;
1748
1790
  const claimed = await claimOnce({
1749
1791
  claimsDir: ctx.claimsDir,
1750
1792
  key: `session.idle:${sessionId}`,
1751
1793
  ttlMs: 5e3
1752
1794
  });
1753
1795
  if (!claimed) return;
1754
- const title = ctx.sessionTitleService.getSessionTitle(sessionId);
1755
- const agent = await resolveSessionAgent(sessionId, ctx);
1756
- const isPlanSession = isPlanSessionAgent(agent);
1757
1796
  const text = isPlanSession ? planCompleteMessage(title) : agentFinishedMessage(title, agent);
1758
1797
  try {
1759
1798
  if (isPlanSession) {
@@ -2352,7 +2391,7 @@ function resolveProjectRoot(session) {
2352
2391
  if (!session.directory) throw new Error("session directory missing");
2353
2392
  return session.directory;
2354
2393
  }
2355
- function extractTextFromParts(parts) {
2394
+ function extractTextFromParts2(parts) {
2356
2395
  const pieces = [];
2357
2396
  for (const part of parts) {
2358
2397
  if (part.type === "text" && typeof part.text === "string") {
@@ -2364,7 +2403,7 @@ function extractTextFromParts(parts) {
2364
2403
  function buildSnippet(envelope) {
2365
2404
  if (!envelope) return EMPTY_MESSAGE;
2366
2405
  try {
2367
- const raw = extractTextFromParts(envelope.parts);
2406
+ const raw = extractTextFromParts2(envelope.parts);
2368
2407
  const cleaned = stripCodeFences(raw);
2369
2408
  const truncated = truncateForTelegram(cleaned, SNIPPET_MAX_CHARS);
2370
2409
  if (!truncated) return EMPTY_MESSAGE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinseeker/opencode-telegram-plugin",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
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",