@coinseeker/opencode-telegram-plugin 1.1.5 → 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 +3 -3
- package/dist/telegram-remote.js +83 -9
- 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.7"]
|
|
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.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.
|
|
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
|
|
package/dist/telegram-remote.js
CHANGED
|
@@ -1474,8 +1474,8 @@ async function handleSessionError(event, ctx) {
|
|
|
1474
1474
|
// src/lib/plan-agent.ts
|
|
1475
1475
|
function isPlanSessionAgent(agent) {
|
|
1476
1476
|
if (!agent) return false;
|
|
1477
|
-
const normalized = agent.
|
|
1478
|
-
return normalized === "plan" || normalized === "prometheus" || normalized === "prometheus - plan builder" || normalized === "prometheus (plan builder)";
|
|
1477
|
+
const normalized = agent.normalize("NFKC").replace(/[\u200B-\u200D\uFEFF]/g, "").replace(/[‐‑‒–—―]/g, "-").replace(/\s+/g, " ").replace(/\s*-\s*/g, " - ").trim().toLowerCase();
|
|
1478
|
+
return normalized === "plan" || normalized === "prometheus" || normalized === "prometheus - plan builder" || normalized === "prometheus (plan builder)" || normalized.includes("prometheus") && normalized.includes("plan builder");
|
|
1479
1479
|
}
|
|
1480
1480
|
|
|
1481
1481
|
// src/lib/pending-start-work.ts
|
|
@@ -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));
|
|
@@ -1663,6 +1665,70 @@ function agentFinishedMessage(title, agent) {
|
|
|
1663
1665
|
const base = title ? `Agent has finished: ${title}` : "Agent has finished.";
|
|
1664
1666
|
return agent ? `${base} (${agent})` : base;
|
|
1665
1667
|
}
|
|
1668
|
+
function selectPlanSessionAgent(candidates) {
|
|
1669
|
+
return candidates.find(isPlanSessionAgent) ?? candidates.find((agent) => agent !== void 0);
|
|
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
|
+
}
|
|
1707
|
+
async function resolveSessionAgent(sessionId, ctx) {
|
|
1708
|
+
const candidates = [
|
|
1709
|
+
ctx.sessionTitleService.getSessionAgent(sessionId)
|
|
1710
|
+
];
|
|
1711
|
+
try {
|
|
1712
|
+
const registryEntry = (await ctx.sessionRegistry.listSessions()).find(
|
|
1713
|
+
(entry) => entry.sessionId === sessionId
|
|
1714
|
+
);
|
|
1715
|
+
candidates.push(registryEntry?.agent);
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
ctx.logger.warn("session registry agent lookup failed", { sessionId, error: String(err) });
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const result = await ctx.client.session.get({ path: { id: sessionId } });
|
|
1721
|
+
if (result.data) {
|
|
1722
|
+
ctx.sessionTitleService.setSessionInfo(result.data);
|
|
1723
|
+
candidates.push(ctx.sessionTitleService.getSessionAgent(sessionId));
|
|
1724
|
+
}
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
ctx.logger.warn("session agent live lookup failed", { sessionId, error: String(err) });
|
|
1727
|
+
}
|
|
1728
|
+
const agent = selectPlanSessionAgent(candidates);
|
|
1729
|
+
if (agent !== void 0) ctx.sessionTitleService.setSessionAgent(sessionId, agent);
|
|
1730
|
+
return agent;
|
|
1731
|
+
}
|
|
1666
1732
|
function cancelDeferredParentConfirm(sessionId) {
|
|
1667
1733
|
const timer = deferredConfirmTimers.get(sessionId);
|
|
1668
1734
|
if (timer === void 0) return;
|
|
@@ -1717,15 +1783,16 @@ async function sendIdleNotification(sessionId, ctx) {
|
|
|
1717
1783
|
ctx.logger.info("idle suppressed - session was aborted", { sessionId });
|
|
1718
1784
|
return;
|
|
1719
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;
|
|
1720
1790
|
const claimed = await claimOnce({
|
|
1721
1791
|
claimsDir: ctx.claimsDir,
|
|
1722
1792
|
key: `session.idle:${sessionId}`,
|
|
1723
1793
|
ttlMs: 5e3
|
|
1724
1794
|
});
|
|
1725
1795
|
if (!claimed) return;
|
|
1726
|
-
const title = ctx.sessionTitleService.getSessionTitle(sessionId);
|
|
1727
|
-
const agent = ctx.sessionTitleService.getSessionAgent(sessionId);
|
|
1728
|
-
const isPlanSession = isPlanSessionAgent(agent);
|
|
1729
1796
|
const text = isPlanSession ? planCompleteMessage(title) : agentFinishedMessage(title, agent);
|
|
1730
1797
|
try {
|
|
1731
1798
|
if (isPlanSession) {
|
|
@@ -1747,7 +1814,7 @@ async function sendIdleNotification(sessionId, ctx) {
|
|
|
1747
1814
|
await ctx.bot.sendMessage(text);
|
|
1748
1815
|
}
|
|
1749
1816
|
ctx.sessionTitleService.clearDeferredIdleNotification(sessionId);
|
|
1750
|
-
ctx.logger.info("idle notification sent", { sessionId, title });
|
|
1817
|
+
ctx.logger.info("idle notification sent", { sessionId, title, agent, isPlanSession });
|
|
1751
1818
|
} catch (err) {
|
|
1752
1819
|
ctx.logger.error("failed to send idle notification", { error: String(err) });
|
|
1753
1820
|
}
|
|
@@ -2324,7 +2391,7 @@ function resolveProjectRoot(session) {
|
|
|
2324
2391
|
if (!session.directory) throw new Error("session directory missing");
|
|
2325
2392
|
return session.directory;
|
|
2326
2393
|
}
|
|
2327
|
-
function
|
|
2394
|
+
function extractTextFromParts2(parts) {
|
|
2328
2395
|
const pieces = [];
|
|
2329
2396
|
for (const part of parts) {
|
|
2330
2397
|
if (part.type === "text" && typeof part.text === "string") {
|
|
@@ -2336,7 +2403,7 @@ function extractTextFromParts(parts) {
|
|
|
2336
2403
|
function buildSnippet(envelope) {
|
|
2337
2404
|
if (!envelope) return EMPTY_MESSAGE;
|
|
2338
2405
|
try {
|
|
2339
|
-
const raw =
|
|
2406
|
+
const raw = extractTextFromParts2(envelope.parts);
|
|
2340
2407
|
const cleaned = stripCodeFences(raw);
|
|
2341
2408
|
const truncated = truncateForTelegram(cleaned, SNIPPET_MAX_CHARS);
|
|
2342
2409
|
if (!truncated) return EMPTY_MESSAGE;
|
|
@@ -2523,6 +2590,9 @@ function agentFromSession3(session) {
|
|
|
2523
2590
|
function resolveProjectRoot2(session) {
|
|
2524
2591
|
return session.directory;
|
|
2525
2592
|
}
|
|
2593
|
+
function selectPlanSessionAgent2(candidates) {
|
|
2594
|
+
return candidates.find(isPlanSessionAgent) ?? candidates.find((agent) => agent !== void 0);
|
|
2595
|
+
}
|
|
2526
2596
|
function readinessMessage(reason) {
|
|
2527
2597
|
switch (reason) {
|
|
2528
2598
|
case "no-omo-dir":
|
|
@@ -2638,7 +2708,11 @@ function createStartWorkCommandDispatcher(deps) {
|
|
|
2638
2708
|
deps.logger.error("start-work session lookup failed", { sessionId, error: String(err) });
|
|
2639
2709
|
return;
|
|
2640
2710
|
}
|
|
2641
|
-
const agent =
|
|
2711
|
+
const agent = selectPlanSessionAgent2([
|
|
2712
|
+
deps.sessionTitleService.getSessionAgent(sessionId),
|
|
2713
|
+
entry.agent,
|
|
2714
|
+
agentFromSession3(session)
|
|
2715
|
+
]);
|
|
2642
2716
|
if (!isPlanSessionAgent(agent)) {
|
|
2643
2717
|
await sendPlain(
|
|
2644
2718
|
bot,
|
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.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",
|