@coinseeker/opencode-telegram-plugin 1.1.1 → 1.1.3
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 -2
- package/dist/telegram-remote.js +312 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,14 +15,16 @@ 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.3"]
|
|
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.3`.
|
|
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.3`, keep the rest of the `plugin` array unchanged, and restart OpenCode.
|
|
27
|
+
|
|
26
28
|
## Configure Telegram
|
|
27
29
|
|
|
28
30
|
Create `~/.config/opencode/telegram-remote/.env`:
|
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);
|
|
@@ -1560,7 +1591,7 @@ var ROOT_IDLE_RECHECK_DELAY_MS = 2500;
|
|
|
1560
1591
|
var DEFERRED_PARENT_CONFIRM_DELAY_MS = 2500;
|
|
1561
1592
|
var deferredConfirmTimers = /* @__PURE__ */ new Map();
|
|
1562
1593
|
function sleep(ms) {
|
|
1563
|
-
return new Promise((
|
|
1594
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1564
1595
|
}
|
|
1565
1596
|
function agentFinishedMessage(title, agent) {
|
|
1566
1597
|
const base = title ? `Agent has finished: ${title}` : "Agent has finished.";
|
|
@@ -1913,48 +1944,170 @@ ${body}
|
|
|
1913
1944
|
|
|
1914
1945
|
// src/lib/plan-readiness.ts
|
|
1915
1946
|
import { access, readFile as readFile5, readdir as readdir6, stat as stat2 } from "fs/promises";
|
|
1916
|
-
import { join as join6 } from "path";
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1947
|
+
import { basename, isAbsolute, join as join6, relative, resolve } from "path";
|
|
1948
|
+
function asRecord2(value) {
|
|
1949
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1950
|
+
return value;
|
|
1951
|
+
}
|
|
1952
|
+
function stringArray(value) {
|
|
1953
|
+
if (!Array.isArray(value)) return [];
|
|
1954
|
+
return value.filter((item) => typeof item === "string");
|
|
1955
|
+
}
|
|
1956
|
+
function optionalString(value) {
|
|
1957
|
+
return typeof value === "string" ? value : void 0;
|
|
1958
|
+
}
|
|
1959
|
+
function normalizeBoulderWork(value) {
|
|
1960
|
+
const record = asRecord2(value);
|
|
1961
|
+
if (!record || typeof record.active_plan !== "string") return void 0;
|
|
1962
|
+
const work = {
|
|
1963
|
+
activePlan: record.active_plan,
|
|
1964
|
+
sessionIds: stringArray(record.session_ids)
|
|
1965
|
+
};
|
|
1966
|
+
const planName = optionalString(record.plan_name);
|
|
1967
|
+
if (planName !== void 0) work.planName = planName;
|
|
1968
|
+
const status = optionalString(record.status);
|
|
1969
|
+
if (status !== void 0) work.status = status;
|
|
1970
|
+
const startedAt = optionalString(record.started_at);
|
|
1971
|
+
if (startedAt !== void 0) work.startedAt = startedAt;
|
|
1972
|
+
const updatedAt = optionalString(record.updated_at);
|
|
1973
|
+
if (updatedAt !== void 0) work.updatedAt = updatedAt;
|
|
1974
|
+
const worktreePath = optionalString(record.worktree_path);
|
|
1975
|
+
if (worktreePath !== void 0) work.worktreePath = worktreePath;
|
|
1976
|
+
return work;
|
|
1977
|
+
}
|
|
1978
|
+
function normalizeBoulderState(value) {
|
|
1979
|
+
const record = asRecord2(value);
|
|
1980
|
+
if (!record) return void 0;
|
|
1981
|
+
const state = { sessionIds: stringArray(record.session_ids) };
|
|
1982
|
+
const activePlan = optionalString(record.active_plan);
|
|
1983
|
+
if (activePlan !== void 0) state.activePlan = activePlan;
|
|
1984
|
+
const planName = optionalString(record.plan_name);
|
|
1985
|
+
if (planName !== void 0) state.planName = planName;
|
|
1986
|
+
const status = optionalString(record.status);
|
|
1987
|
+
if (status !== void 0) state.status = status;
|
|
1988
|
+
const startedAt = optionalString(record.started_at);
|
|
1989
|
+
if (startedAt !== void 0) state.startedAt = startedAt;
|
|
1990
|
+
const updatedAt = optionalString(record.updated_at);
|
|
1991
|
+
if (updatedAt !== void 0) state.updatedAt = updatedAt;
|
|
1992
|
+
const worktreePath = optionalString(record.worktree_path);
|
|
1993
|
+
if (worktreePath !== void 0) state.worktreePath = worktreePath;
|
|
1994
|
+
const activeWorkId = optionalString(record.active_work_id);
|
|
1995
|
+
if (activeWorkId !== void 0) state.activeWorkId = activeWorkId;
|
|
1996
|
+
const worksRecord = asRecord2(record.works);
|
|
1997
|
+
if (worksRecord) {
|
|
1998
|
+
const works = {};
|
|
1999
|
+
for (const [workId, rawWork] of Object.entries(worksRecord)) {
|
|
2000
|
+
const work = normalizeBoulderWork(rawWork);
|
|
2001
|
+
if (work) works[workId] = work;
|
|
2002
|
+
}
|
|
2003
|
+
if (Object.keys(works).length > 0) state.works = works;
|
|
2004
|
+
}
|
|
2005
|
+
return state;
|
|
2006
|
+
}
|
|
2007
|
+
async function readBoulderState(boulderPath) {
|
|
2008
|
+
let text;
|
|
1922
2009
|
try {
|
|
1923
|
-
await
|
|
2010
|
+
text = await readFile5(boulderPath, "utf8");
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
2013
|
+
return { exists: false };
|
|
2014
|
+
}
|
|
2015
|
+
return { exists: true };
|
|
2016
|
+
}
|
|
2017
|
+
try {
|
|
2018
|
+
return { exists: true, state: normalizeBoulderState(JSON.parse(text)) };
|
|
1924
2019
|
} catch {
|
|
1925
|
-
return {
|
|
1926
|
-
ready: false,
|
|
1927
|
-
reason: "no-omo-dir",
|
|
1928
|
-
detail: `${omoDir} does not exist`
|
|
1929
|
-
};
|
|
2020
|
+
return { exists: true };
|
|
1930
2021
|
}
|
|
2022
|
+
}
|
|
2023
|
+
function mirrorWorkFromState(state) {
|
|
2024
|
+
if (!state.activePlan) return void 0;
|
|
2025
|
+
const work = {
|
|
2026
|
+
activePlan: state.activePlan,
|
|
2027
|
+
sessionIds: state.sessionIds
|
|
2028
|
+
};
|
|
2029
|
+
if (state.planName !== void 0) work.planName = state.planName;
|
|
2030
|
+
if (state.status !== void 0) work.status = state.status;
|
|
2031
|
+
if (state.startedAt !== void 0) work.startedAt = state.startedAt;
|
|
2032
|
+
if (state.updatedAt !== void 0) work.updatedAt = state.updatedAt;
|
|
2033
|
+
if (state.worktreePath !== void 0) work.worktreePath = state.worktreePath;
|
|
2034
|
+
return work;
|
|
2035
|
+
}
|
|
2036
|
+
function boulderWorks(state) {
|
|
2037
|
+
if (state.works) return Object.values(state.works);
|
|
2038
|
+
const mirrorWork = mirrorWorkFromState(state);
|
|
2039
|
+
return mirrorWork ? [mirrorWork] : [];
|
|
2040
|
+
}
|
|
2041
|
+
function parseIsoToMs(value) {
|
|
2042
|
+
if (!value) return 0;
|
|
2043
|
+
const ms = Date.parse(value);
|
|
2044
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
2045
|
+
}
|
|
2046
|
+
function findBoulderWorkForSession(state, sessionId) {
|
|
2047
|
+
const works = boulderWorks(state).filter((work) => work.sessionIds.includes(sessionId)).sort(
|
|
2048
|
+
(left, right) => parseIsoToMs(right.updatedAt ?? right.startedAt) - parseIsoToMs(left.updatedAt ?? left.startedAt)
|
|
2049
|
+
);
|
|
2050
|
+
if (works[0]) return works[0];
|
|
2051
|
+
const mirrorWork = mirrorWorkFromState(state);
|
|
2052
|
+
if (mirrorWork && state.sessionIds.includes(sessionId)) return mirrorWork;
|
|
2053
|
+
return void 0;
|
|
2054
|
+
}
|
|
2055
|
+
function isActiveBoulderWork(work) {
|
|
2056
|
+
return work.status !== "completed" && work.status !== "abandoned";
|
|
2057
|
+
}
|
|
2058
|
+
function resolveTrackedPath(baseDirectory, trackedPath) {
|
|
2059
|
+
return isAbsolute(trackedPath) ? resolve(trackedPath) : resolve(baseDirectory, trackedPath);
|
|
2060
|
+
}
|
|
2061
|
+
async function resolveBoulderPlanPath(projectRoot, work) {
|
|
2062
|
+
const absolutePlanPath = resolveTrackedPath(projectRoot, work.activePlan);
|
|
2063
|
+
const worktreePath = work.worktreePath?.trim();
|
|
2064
|
+
if (!worktreePath) return absolutePlanPath;
|
|
2065
|
+
const relativePlanPath = relative(resolve(projectRoot), absolutePlanPath);
|
|
2066
|
+
if (relativePlanPath.length === 0 || relativePlanPath.startsWith("..") || isAbsolute(relativePlanPath)) {
|
|
2067
|
+
return absolutePlanPath;
|
|
2068
|
+
}
|
|
2069
|
+
const worktreePlanPath = resolve(resolveTrackedPath(projectRoot, worktreePath), relativePlanPath);
|
|
1931
2070
|
try {
|
|
1932
|
-
await access(
|
|
1933
|
-
return
|
|
1934
|
-
ready: false,
|
|
1935
|
-
reason: "boulder-active",
|
|
1936
|
-
detail: `${boulderPath} exists`
|
|
1937
|
-
};
|
|
2071
|
+
await access(worktreePlanPath);
|
|
2072
|
+
return worktreePlanPath;
|
|
1938
2073
|
} catch {
|
|
2074
|
+
return absolutePlanPath;
|
|
1939
2075
|
}
|
|
2076
|
+
}
|
|
2077
|
+
function planNameFromPath(planPath) {
|
|
2078
|
+
return basename(planPath, ".md");
|
|
2079
|
+
}
|
|
2080
|
+
function normalizePlanToken(value) {
|
|
2081
|
+
return value.normalize("NFKD").toLowerCase().replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2082
|
+
}
|
|
2083
|
+
function selectPlanByHint(candidates, planHint) {
|
|
2084
|
+
if (!planHint) return void 0;
|
|
2085
|
+
const normalizedHint = normalizePlanToken(planHint);
|
|
2086
|
+
if (!normalizedHint) return void 0;
|
|
2087
|
+
return candidates.find((candidate) => {
|
|
2088
|
+
const planName = candidate.name.replace(/\.md$/, "");
|
|
2089
|
+
return normalizePlanToken(planName) === normalizedHint;
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
function resolvePlanPathHint(projectRoot, planPath) {
|
|
2093
|
+
if (!planPath) return void 0;
|
|
2094
|
+
const resolvedPath = isAbsolute(planPath) ? resolve(planPath) : resolve(projectRoot, planPath);
|
|
2095
|
+
const plansRoot = resolve(projectRoot, ".omo", "plans");
|
|
2096
|
+
const relativePlanPath = relative(plansRoot, resolvedPath);
|
|
2097
|
+
if (!resolvedPath.endsWith(".md") || relativePlanPath.length === 0 || relativePlanPath.startsWith("..") || isAbsolute(relativePlanPath)) {
|
|
2098
|
+
return void 0;
|
|
2099
|
+
}
|
|
2100
|
+
return resolvedPath;
|
|
2101
|
+
}
|
|
2102
|
+
async function getPlanFiles(plansDir) {
|
|
1940
2103
|
let planFiles = [];
|
|
1941
2104
|
try {
|
|
1942
2105
|
const entries = await readdir6(plansDir);
|
|
1943
2106
|
planFiles = entries.filter((e) => e.endsWith(".md"));
|
|
1944
2107
|
} catch {
|
|
1945
|
-
return
|
|
1946
|
-
ready: false,
|
|
1947
|
-
reason: "no-plans",
|
|
1948
|
-
detail: `${plansDir} not found or empty`
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
if (planFiles.length === 0) {
|
|
1952
|
-
return {
|
|
1953
|
-
ready: false,
|
|
1954
|
-
reason: "no-plans",
|
|
1955
|
-
detail: `No .md files in ${plansDir}`
|
|
1956
|
-
};
|
|
2108
|
+
return void 0;
|
|
1957
2109
|
}
|
|
2110
|
+
if (planFiles.length === 0) return [];
|
|
1958
2111
|
const stats = await Promise.all(
|
|
1959
2112
|
planFiles.map(async (f) => {
|
|
1960
2113
|
const full = join6(plansDir, f);
|
|
@@ -1962,9 +2115,20 @@ async function checkPlanReadiness(args) {
|
|
|
1962
2115
|
return { path: full, name: f, mtime: s.mtime.getTime() };
|
|
1963
2116
|
})
|
|
1964
2117
|
);
|
|
1965
|
-
stats.sort((a, b) => b.mtime - a.mtime);
|
|
1966
|
-
|
|
1967
|
-
|
|
2118
|
+
return stats.sort((a, b) => b.mtime - a.mtime);
|
|
2119
|
+
}
|
|
2120
|
+
async function readPlanProgress(planPath, planName, boulderActive = false) {
|
|
2121
|
+
let content;
|
|
2122
|
+
try {
|
|
2123
|
+
content = await readFile5(planPath, "utf8");
|
|
2124
|
+
} catch {
|
|
2125
|
+
return {
|
|
2126
|
+
ready: false,
|
|
2127
|
+
reason: "no-plans",
|
|
2128
|
+
detail: `${planPath} not found`,
|
|
2129
|
+
...boulderActive ? { boulderActive } : {}
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
1968
2132
|
const totalMatches = content.match(/^- \[[ xX]\]/gm) ?? [];
|
|
1969
2133
|
const completedMatches = content.match(/^- \[[xX]\]/gm) ?? [];
|
|
1970
2134
|
const total = totalMatches.length;
|
|
@@ -1973,24 +2137,99 @@ async function checkPlanReadiness(args) {
|
|
|
1973
2137
|
return {
|
|
1974
2138
|
ready: false,
|
|
1975
2139
|
reason: "plan-empty",
|
|
1976
|
-
detail: `${
|
|
2140
|
+
detail: `${planName}: no checkboxes found`,
|
|
2141
|
+
...boulderActive ? { boulderActive } : {}
|
|
1977
2142
|
};
|
|
1978
2143
|
}
|
|
1979
2144
|
if (completed >= total) {
|
|
1980
2145
|
return {
|
|
1981
2146
|
ready: false,
|
|
1982
2147
|
reason: "all-plans-complete",
|
|
1983
|
-
detail: `${
|
|
2148
|
+
detail: `${planName}: ${completed}/${total} complete`,
|
|
2149
|
+
...boulderActive ? { boulderActive } : {}
|
|
1984
2150
|
};
|
|
1985
2151
|
}
|
|
1986
2152
|
return {
|
|
1987
2153
|
ready: true,
|
|
1988
|
-
planPath
|
|
1989
|
-
planName
|
|
2154
|
+
planPath,
|
|
2155
|
+
planName,
|
|
1990
2156
|
total,
|
|
1991
|
-
completed
|
|
2157
|
+
completed,
|
|
2158
|
+
...boulderActive ? { boulderActive } : {}
|
|
1992
2159
|
};
|
|
1993
2160
|
}
|
|
2161
|
+
async function checkPlanReadiness(args) {
|
|
2162
|
+
const { projectRoot, sessionId } = args;
|
|
2163
|
+
const allowLatestFallback = args.allowLatestFallback ?? sessionId === void 0;
|
|
2164
|
+
const omoDir = join6(projectRoot, ".omo");
|
|
2165
|
+
const plansDir = join6(omoDir, "plans");
|
|
2166
|
+
const boulderPath = join6(omoDir, "boulder.json");
|
|
2167
|
+
try {
|
|
2168
|
+
await access(omoDir);
|
|
2169
|
+
} catch {
|
|
2170
|
+
return {
|
|
2171
|
+
ready: false,
|
|
2172
|
+
reason: "no-omo-dir",
|
|
2173
|
+
detail: `${omoDir} does not exist`
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
const boulder = await readBoulderState(boulderPath);
|
|
2177
|
+
const projectBoulderActive = boulder.exists;
|
|
2178
|
+
if (boulder.exists && sessionId === void 0) {
|
|
2179
|
+
return {
|
|
2180
|
+
ready: false,
|
|
2181
|
+
reason: "boulder-active",
|
|
2182
|
+
detail: `${boulderPath} exists`,
|
|
2183
|
+
boulderActive: true
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
if (boulder.state && sessionId !== void 0) {
|
|
2187
|
+
const work = findBoulderWorkForSession(boulder.state, sessionId);
|
|
2188
|
+
if (work) {
|
|
2189
|
+
const planPath = await resolveBoulderPlanPath(projectRoot, work);
|
|
2190
|
+
return readPlanProgress(
|
|
2191
|
+
planPath,
|
|
2192
|
+
work.planName ?? planNameFromPath(planPath),
|
|
2193
|
+
isActiveBoulderWork(work)
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
const explicitPlanPath = resolvePlanPathHint(projectRoot, args.planPath);
|
|
2198
|
+
if (explicitPlanPath) {
|
|
2199
|
+
return readPlanProgress(explicitPlanPath, planNameFromPath(explicitPlanPath), projectBoulderActive);
|
|
2200
|
+
}
|
|
2201
|
+
const stats = await getPlanFiles(plansDir);
|
|
2202
|
+
if (stats === void 0) {
|
|
2203
|
+
return {
|
|
2204
|
+
ready: false,
|
|
2205
|
+
reason: "no-plans",
|
|
2206
|
+
detail: `${plansDir} not found or empty`,
|
|
2207
|
+
...projectBoulderActive ? { boulderActive: true } : {}
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
if (stats.length === 0) {
|
|
2211
|
+
return {
|
|
2212
|
+
ready: false,
|
|
2213
|
+
reason: "no-plans",
|
|
2214
|
+
detail: `No .md files in ${plansDir}`,
|
|
2215
|
+
...projectBoulderActive ? { boulderActive: true } : {}
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
const hinted = selectPlanByHint(stats, args.planHint);
|
|
2219
|
+
if (hinted) {
|
|
2220
|
+
return readPlanProgress(hinted.path, hinted.name.replace(/\.md$/, ""), projectBoulderActive);
|
|
2221
|
+
}
|
|
2222
|
+
if (!allowLatestFallback) {
|
|
2223
|
+
return {
|
|
2224
|
+
ready: false,
|
|
2225
|
+
reason: "no-session-plan",
|
|
2226
|
+
detail: `No plan associated with session ${sessionId ?? "missing"}`,
|
|
2227
|
+
...projectBoulderActive ? { boulderActive: true } : {}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
const latest = stats[0];
|
|
2231
|
+
return readPlanProgress(latest.path, latest.name.replace(/\.md$/, ""), projectBoulderActive);
|
|
2232
|
+
}
|
|
1994
2233
|
async function recheckSessionIdle(client, sessionId) {
|
|
1995
2234
|
const result = await client.session.status();
|
|
1996
2235
|
const statuses = result.data ?? {};
|
|
@@ -2052,6 +2291,8 @@ function planReadinessKorean(result) {
|
|
|
2052
2291
|
}
|
|
2053
2292
|
case "boulder-active":
|
|
2054
2293
|
return "boulder \uD65C\uC131";
|
|
2294
|
+
case "no-session-plan":
|
|
2295
|
+
return "\uC138\uC158 \uC5F0\uACB0 plan \uC5C6\uC74C";
|
|
2055
2296
|
}
|
|
2056
2297
|
}
|
|
2057
2298
|
function planLine(result) {
|
|
@@ -2061,7 +2302,7 @@ function planLine(result) {
|
|
|
2061
2302
|
return `<b>\uD50C\uB79C \uC0C1\uD0DC</b>: ${planReadinessKorean(result)}`;
|
|
2062
2303
|
}
|
|
2063
2304
|
function boulderLine(result) {
|
|
2064
|
-
const active = !result.ready && result.reason === "boulder-active";
|
|
2305
|
+
const active = result.boulderActive === true || !result.ready && result.reason === "boulder-active";
|
|
2065
2306
|
return active ? "<b>Boulder</b>: \uD65C\uC131" : "<b>Boulder</b>: \uC5C6\uC74C";
|
|
2066
2307
|
}
|
|
2067
2308
|
function createStatusDispatcher(deps) {
|
|
@@ -2162,11 +2403,18 @@ function createStatusDispatcher(deps) {
|
|
|
2162
2403
|
return;
|
|
2163
2404
|
}
|
|
2164
2405
|
const projectRoot = resolveProjectRoot(session);
|
|
2165
|
-
const
|
|
2406
|
+
const rawTitle = session.title ?? entry.title;
|
|
2407
|
+
const rawAgent = entry.agent ?? session.agent;
|
|
2408
|
+
const planReady = await checkPlanReadiness({
|
|
2409
|
+
projectRoot,
|
|
2410
|
+
sessionId: entry.sessionId,
|
|
2411
|
+
planHint: rawTitle,
|
|
2412
|
+
allowLatestFallback: rawAgent === "plan"
|
|
2413
|
+
});
|
|
2166
2414
|
const userSnippet = buildSnippet(findLastByRole(messages, "user"));
|
|
2167
2415
|
const assistantSnippet = buildSnippet(findLastByRole(messages, "assistant"));
|
|
2168
|
-
const title = escapeHtml(
|
|
2169
|
-
const agent =
|
|
2416
|
+
const title = escapeHtml(rawTitle ?? "");
|
|
2417
|
+
const agent = rawAgent ? escapeHtml(rawAgent) : "?";
|
|
2170
2418
|
const text = [
|
|
2171
2419
|
`<b>\uC138\uC158 #${n}</b>: ${title}`,
|
|
2172
2420
|
`\uC5D0\uC774\uC804\uD2B8: ${agent}`,
|
|
@@ -2208,6 +2456,8 @@ function readinessMessage(reason) {
|
|
|
2208
2456
|
return "plan \uC758 \uBAA8\uB4E0 task \uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8 plan \uC791\uC131 \uD544\uC694";
|
|
2209
2457
|
case "boulder-active":
|
|
2210
2458
|
return ".omo/boulder.json \uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uAE30\uC874 \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC774\uAC70\uB098 archive \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4";
|
|
2459
|
+
case "no-session-plan":
|
|
2460
|
+
return "\uD574\uB2F9 \uC138\uC158\uACFC \uC5F0\uACB0\uB41C plan \uC774 \uC5C6\uC2B5\uB2C8\uB2E4";
|
|
2211
2461
|
}
|
|
2212
2462
|
}
|
|
2213
2463
|
function isSessionNotFoundError(err) {
|
|
@@ -2878,6 +3128,12 @@ var SessionTitleService = class {
|
|
|
2878
3128
|
|
|
2879
3129
|
// src/telegram-remote.ts
|
|
2880
3130
|
var pluginDir = dirname6(fileURLToPath(import.meta.url));
|
|
3131
|
+
function withDirectoryQuery(path, directory) {
|
|
3132
|
+
if (directory === void 0) return path;
|
|
3133
|
+
const url = new URL(path, "http://opencode.local");
|
|
3134
|
+
url.searchParams.set("directory", directory);
|
|
3135
|
+
return `${url.pathname}${url.search}`;
|
|
3136
|
+
}
|
|
2881
3137
|
async function postToServer(serverUrl, path, body) {
|
|
2882
3138
|
const safeServerUrl = normalizeOpenCodeServerUrl(serverUrl);
|
|
2883
3139
|
if (!safeServerUrl) throw new Error("Invalid OpenCode server URL");
|
|
@@ -2950,9 +3206,12 @@ var TelegramRemote = async (input) => {
|
|
|
2950
3206
|
throwOnError: true
|
|
2951
3207
|
});
|
|
2952
3208
|
};
|
|
2953
|
-
const replyToPermission = async (requestID, sessionID, reply, endpoint2, serverUrl = input.serverUrl.href) => {
|
|
3209
|
+
const replyToPermission = async (requestID, sessionID, reply, endpoint2, serverUrl = input.serverUrl.href, directory = input.directory) => {
|
|
2954
3210
|
if (endpoint2 === "request") {
|
|
2955
|
-
const path2 =
|
|
3211
|
+
const path2 = withDirectoryQuery(
|
|
3212
|
+
`/permission/${encodeURIComponent(requestID)}/reply`,
|
|
3213
|
+
directory
|
|
3214
|
+
);
|
|
2956
3215
|
if (serverUrl !== input.serverUrl.href) {
|
|
2957
3216
|
await postToServer(serverUrl, path2, { reply });
|
|
2958
3217
|
return;
|
|
@@ -2965,7 +3224,10 @@ var TelegramRemote = async (input) => {
|
|
|
2965
3224
|
});
|
|
2966
3225
|
return;
|
|
2967
3226
|
}
|
|
2968
|
-
const path =
|
|
3227
|
+
const path = withDirectoryQuery(
|
|
3228
|
+
`/session/${encodeURIComponent(sessionID)}/permissions/${encodeURIComponent(requestID)}`,
|
|
3229
|
+
directory
|
|
3230
|
+
);
|
|
2969
3231
|
if (serverUrl !== input.serverUrl.href) {
|
|
2970
3232
|
await postToServer(serverUrl, path, { response: reply });
|
|
2971
3233
|
return;
|
|
@@ -3067,6 +3329,7 @@ var TelegramRemote = async (input) => {
|
|
|
3067
3329
|
claimsDir,
|
|
3068
3330
|
pluginDir,
|
|
3069
3331
|
serverUrl: input.serverUrl,
|
|
3332
|
+
directory: input.directory,
|
|
3070
3333
|
tokenHash,
|
|
3071
3334
|
pendingQuestions,
|
|
3072
3335
|
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.3",
|
|
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",
|