agendex-cli 0.13.0 → 0.15.0
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/dist/cli.js +1371 -178
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1059,13 +1059,13 @@ var init_cleanup = __esm(() => {
|
|
|
1059
1059
|
|
|
1060
1060
|
// src/cli.ts
|
|
1061
1061
|
import { spawn as spawn3 } from "node:child_process";
|
|
1062
|
-
import { existsSync as
|
|
1063
|
-
import { resolve as
|
|
1062
|
+
import { existsSync as existsSync11, statSync as statSync2, writeSync } from "node:fs";
|
|
1063
|
+
import { resolve as resolve7 } from "node:path";
|
|
1064
1064
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1065
1065
|
|
|
1066
1066
|
// ../shared/src/adapters/catalog.ts
|
|
1067
|
-
import { homedir as
|
|
1068
|
-
import { join as
|
|
1067
|
+
import { homedir as homedir7 } from "node:os";
|
|
1068
|
+
import { join as join7 } from "node:path";
|
|
1069
1069
|
|
|
1070
1070
|
// ../shared/src/adapters/claude-code.ts
|
|
1071
1071
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -1748,6 +1748,453 @@ var ohMyOpencodeAdapter = {
|
|
|
1748
1748
|
}
|
|
1749
1749
|
};
|
|
1750
1750
|
|
|
1751
|
+
// ../shared/src/adapters/plannotator.ts
|
|
1752
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1753
|
+
import { readFile as readFile6, stat as stat6 } from "node:fs/promises";
|
|
1754
|
+
import { homedir as homedir6 } from "node:os";
|
|
1755
|
+
import { basename as basename5, dirname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
1756
|
+
var REQUEST_TIMEOUT_MS = 5000;
|
|
1757
|
+
var PROJECT_PLANS_DIRNAME = "@plans";
|
|
1758
|
+
function getPlannotatorDir() {
|
|
1759
|
+
return process.env.AGENDEX_PLANNOTATOR_DIR || join6(homedir6(), ".plannotator");
|
|
1760
|
+
}
|
|
1761
|
+
function getPlansDir() {
|
|
1762
|
+
return join6(getPlannotatorDir(), "plans");
|
|
1763
|
+
}
|
|
1764
|
+
function getSessionsDir() {
|
|
1765
|
+
return join6(getPlannotatorDir(), "sessions");
|
|
1766
|
+
}
|
|
1767
|
+
function isRecord3(value) {
|
|
1768
|
+
return typeof value === "object" && value !== null;
|
|
1769
|
+
}
|
|
1770
|
+
function normalizeStatusFromFilename(filePath) {
|
|
1771
|
+
const name = basename5(filePath, ".md");
|
|
1772
|
+
if (name.endsWith("-approved"))
|
|
1773
|
+
return "approved";
|
|
1774
|
+
if (name.endsWith("-denied"))
|
|
1775
|
+
return "denied";
|
|
1776
|
+
return "unknown";
|
|
1777
|
+
}
|
|
1778
|
+
function stripStatusSuffix(title) {
|
|
1779
|
+
return title.replace(/-(approved|denied)$/i, "");
|
|
1780
|
+
}
|
|
1781
|
+
function cleanTitle2(title) {
|
|
1782
|
+
return title.replace(/\d{4}-\d{2}-\d{2}/g, "").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1783
|
+
}
|
|
1784
|
+
function extractTitle5(content, filePath) {
|
|
1785
|
+
const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
1786
|
+
if (heading)
|
|
1787
|
+
return heading.replace(/^Plan:\s*/i, "").trim();
|
|
1788
|
+
const name = stripStatusSuffix(basename5(filePath, ".md"));
|
|
1789
|
+
return cleanTitle2(name) || "Plannotator Plan";
|
|
1790
|
+
}
|
|
1791
|
+
function isAnnotationFile(filePath) {
|
|
1792
|
+
return filePath.endsWith(".annotations.md");
|
|
1793
|
+
}
|
|
1794
|
+
function isSnapshotFile(filePath) {
|
|
1795
|
+
if (!filePath.endsWith(".md"))
|
|
1796
|
+
return false;
|
|
1797
|
+
if (isAnnotationFile(filePath))
|
|
1798
|
+
return false;
|
|
1799
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
1800
|
+
return status === "approved" || status === "denied";
|
|
1801
|
+
}
|
|
1802
|
+
function isProjectPlansPath(filePath) {
|
|
1803
|
+
return resolve2(filePath).split(sep2).includes(PROJECT_PLANS_DIRNAME);
|
|
1804
|
+
}
|
|
1805
|
+
function projectPlansDirForPath(filePath) {
|
|
1806
|
+
const parts = resolve2(filePath).split(sep2);
|
|
1807
|
+
const index = parts.lastIndexOf(PROJECT_PLANS_DIRNAME);
|
|
1808
|
+
if (index === -1)
|
|
1809
|
+
return;
|
|
1810
|
+
return parts.slice(0, index + 1).join(sep2) || sep2;
|
|
1811
|
+
}
|
|
1812
|
+
function isProjectPlanFile(filePath) {
|
|
1813
|
+
return filePath.endsWith(".md") && !isAnnotationFile(filePath) && isProjectPlansPath(filePath);
|
|
1814
|
+
}
|
|
1815
|
+
function isSessionFile2(filePath) {
|
|
1816
|
+
return filePath.endsWith(".json");
|
|
1817
|
+
}
|
|
1818
|
+
function isAlive(pid) {
|
|
1819
|
+
try {
|
|
1820
|
+
process.kill(pid, 0);
|
|
1821
|
+
return true;
|
|
1822
|
+
} catch {
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function isSafePlannotatorUrl(rawUrl) {
|
|
1827
|
+
try {
|
|
1828
|
+
const url = new URL(rawUrl);
|
|
1829
|
+
if (url.protocol !== "http:")
|
|
1830
|
+
return false;
|
|
1831
|
+
if (url.username || url.password)
|
|
1832
|
+
return false;
|
|
1833
|
+
if (url.pathname !== "/" && url.pathname !== "")
|
|
1834
|
+
return false;
|
|
1835
|
+
if (url.search || url.hash)
|
|
1836
|
+
return false;
|
|
1837
|
+
const hostname = url.hostname.toLowerCase();
|
|
1838
|
+
if (hostname === "localhost")
|
|
1839
|
+
return true;
|
|
1840
|
+
if (hostname === "127.0.0.1")
|
|
1841
|
+
return true;
|
|
1842
|
+
if (hostname === "::1" || hostname === "[::1]")
|
|
1843
|
+
return true;
|
|
1844
|
+
return false;
|
|
1845
|
+
} catch {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function apiUrl(baseUrl, path) {
|
|
1850
|
+
const url = new URL(baseUrl);
|
|
1851
|
+
url.pathname = path;
|
|
1852
|
+
url.search = "";
|
|
1853
|
+
url.hash = "";
|
|
1854
|
+
return url.toString();
|
|
1855
|
+
}
|
|
1856
|
+
async function fetchJson(url, init) {
|
|
1857
|
+
const controller = new AbortController;
|
|
1858
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1859
|
+
try {
|
|
1860
|
+
const res = await fetch(url, {
|
|
1861
|
+
...init,
|
|
1862
|
+
redirect: "error",
|
|
1863
|
+
signal: controller.signal
|
|
1864
|
+
});
|
|
1865
|
+
if (!res.ok)
|
|
1866
|
+
return null;
|
|
1867
|
+
return await res.json();
|
|
1868
|
+
} catch {
|
|
1869
|
+
return null;
|
|
1870
|
+
} finally {
|
|
1871
|
+
clearTimeout(timer);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
async function postJson(url, body) {
|
|
1875
|
+
const controller = new AbortController;
|
|
1876
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1877
|
+
try {
|
|
1878
|
+
const res = await fetch(url, {
|
|
1879
|
+
method: "POST",
|
|
1880
|
+
headers: { "Content-Type": "application/json" },
|
|
1881
|
+
body: JSON.stringify(body),
|
|
1882
|
+
redirect: "error",
|
|
1883
|
+
signal: controller.signal
|
|
1884
|
+
});
|
|
1885
|
+
return res.ok;
|
|
1886
|
+
} catch {
|
|
1887
|
+
return false;
|
|
1888
|
+
} finally {
|
|
1889
|
+
clearTimeout(timer);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
function normalizeMode(value) {
|
|
1893
|
+
if (value === "plan" || value === "review" || value === "annotate" || value === "archive") {
|
|
1894
|
+
return value;
|
|
1895
|
+
}
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
function parseSessionInfo(raw) {
|
|
1899
|
+
if (!isRecord3(raw))
|
|
1900
|
+
return null;
|
|
1901
|
+
const pid = typeof raw.pid === "number" && Number.isFinite(raw.pid) ? raw.pid : undefined;
|
|
1902
|
+
const port = typeof raw.port === "number" && Number.isFinite(raw.port) ? raw.port : undefined;
|
|
1903
|
+
const url = typeof raw.url === "string" ? raw.url : undefined;
|
|
1904
|
+
if (!pid || !url)
|
|
1905
|
+
return null;
|
|
1906
|
+
return {
|
|
1907
|
+
pid,
|
|
1908
|
+
port,
|
|
1909
|
+
url,
|
|
1910
|
+
mode: normalizeMode(raw.mode),
|
|
1911
|
+
project: typeof raw.project === "string" ? raw.project : undefined,
|
|
1912
|
+
startedAt: typeof raw.startedAt === "string" ? raw.startedAt : undefined,
|
|
1913
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
1914
|
+
origin: typeof raw.origin === "string" ? raw.origin : undefined,
|
|
1915
|
+
reviewId: typeof raw.reviewId === "string" ? raw.reviewId : undefined,
|
|
1916
|
+
sourcePlanPath: typeof raw.sourcePlanPath === "string" ? raw.sourcePlanPath : undefined
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
function parseDate2(value) {
|
|
1920
|
+
if (!value)
|
|
1921
|
+
return;
|
|
1922
|
+
const date = new Date(value);
|
|
1923
|
+
if (Number.isNaN(date.getTime()))
|
|
1924
|
+
return;
|
|
1925
|
+
return date;
|
|
1926
|
+
}
|
|
1927
|
+
function metadataRecord(metadata) {
|
|
1928
|
+
return {
|
|
1929
|
+
source: "plannotator",
|
|
1930
|
+
sourceAdapter: "plannotator",
|
|
1931
|
+
plannotator: metadata
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function annotationsPathForSnapshot(filePath) {
|
|
1935
|
+
const snapshotBase = basename5(filePath, ".md").replace(/-(approved|denied)$/i, "");
|
|
1936
|
+
return join6(dirname(filePath), `${snapshotBase}.annotations.md`);
|
|
1937
|
+
}
|
|
1938
|
+
function snapshotPathsForAnnotation(filePath) {
|
|
1939
|
+
const base = basename5(filePath, ".annotations.md");
|
|
1940
|
+
return [
|
|
1941
|
+
join6(dirname(filePath), `${base}-approved.md`),
|
|
1942
|
+
join6(dirname(filePath), `${base}-denied.md`)
|
|
1943
|
+
];
|
|
1944
|
+
}
|
|
1945
|
+
function projectPlanPathsForAnnotation(filePath) {
|
|
1946
|
+
const base = basename5(filePath, ".annotations.md");
|
|
1947
|
+
return [join6(dirname(filePath), `${base}.md`), ...snapshotPathsForAnnotation(filePath)];
|
|
1948
|
+
}
|
|
1949
|
+
async function countAnnotationHeadings(filePath) {
|
|
1950
|
+
if (!existsSync3(filePath))
|
|
1951
|
+
return;
|
|
1952
|
+
try {
|
|
1953
|
+
const raw = await readFile6(filePath, "utf-8");
|
|
1954
|
+
const headings = raw.match(/^#{1,6}\s+/gm);
|
|
1955
|
+
return headings?.length ?? (raw.trim() ? 1 : 0);
|
|
1956
|
+
} catch {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
async function parseSnapshot(filePath) {
|
|
1961
|
+
try {
|
|
1962
|
+
const content = await readFile6(filePath, "utf-8");
|
|
1963
|
+
const stats = await stat6(filePath);
|
|
1964
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
1965
|
+
const annotationsPath = annotationsPathForSnapshot(filePath);
|
|
1966
|
+
const annotationCount = await countAnnotationHeadings(annotationsPath);
|
|
1967
|
+
const metadata = {
|
|
1968
|
+
kind: "snapshot",
|
|
1969
|
+
mode: "plan",
|
|
1970
|
+
status,
|
|
1971
|
+
annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
|
|
1972
|
+
annotationCount,
|
|
1973
|
+
writebackCapable: false
|
|
1974
|
+
};
|
|
1975
|
+
return [
|
|
1976
|
+
{
|
|
1977
|
+
id: hashPath(filePath),
|
|
1978
|
+
agent: "plannotator",
|
|
1979
|
+
title: extractTitle5(content, filePath),
|
|
1980
|
+
content,
|
|
1981
|
+
filePath,
|
|
1982
|
+
format: "md",
|
|
1983
|
+
createdAt: stats.birthtime,
|
|
1984
|
+
updatedAt: stats.mtime,
|
|
1985
|
+
metadata: metadataRecord(metadata)
|
|
1986
|
+
}
|
|
1987
|
+
];
|
|
1988
|
+
} catch {
|
|
1989
|
+
return [];
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
async function parseAnnotationCompanion(filePath) {
|
|
1993
|
+
const plans = [];
|
|
1994
|
+
for (const snapshotPath of snapshotPathsForAnnotation(filePath)) {
|
|
1995
|
+
if (!existsSync3(snapshotPath))
|
|
1996
|
+
continue;
|
|
1997
|
+
plans.push(...await parseSnapshot(snapshotPath));
|
|
1998
|
+
}
|
|
1999
|
+
return plans;
|
|
2000
|
+
}
|
|
2001
|
+
async function parseProjectPlan(filePath) {
|
|
2002
|
+
try {
|
|
2003
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2004
|
+
const stats = await stat6(filePath);
|
|
2005
|
+
const projectPlansDir = projectPlansDirForPath(filePath);
|
|
2006
|
+
const projectRoot = projectPlansDir ? dirname(projectPlansDir) : undefined;
|
|
2007
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
2008
|
+
const annotationsPath = annotationsPathForSnapshot(filePath);
|
|
2009
|
+
const annotationCount = await countAnnotationHeadings(annotationsPath);
|
|
2010
|
+
const metadata = {
|
|
2011
|
+
kind: "project-plan",
|
|
2012
|
+
mode: "plan",
|
|
2013
|
+
status,
|
|
2014
|
+
annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
|
|
2015
|
+
annotationCount,
|
|
2016
|
+
sourcePlanPath: filePath,
|
|
2017
|
+
project: projectRoot ? basename5(projectRoot) : undefined,
|
|
2018
|
+
writebackCapable: false
|
|
2019
|
+
};
|
|
2020
|
+
return [
|
|
2021
|
+
{
|
|
2022
|
+
id: hashPath(filePath),
|
|
2023
|
+
agent: "plannotator",
|
|
2024
|
+
title: extractTitle5(content, filePath),
|
|
2025
|
+
content,
|
|
2026
|
+
filePath,
|
|
2027
|
+
format: "md",
|
|
2028
|
+
createdAt: stats.birthtime,
|
|
2029
|
+
updatedAt: stats.mtime,
|
|
2030
|
+
workspace: projectRoot,
|
|
2031
|
+
metadata: metadataRecord(metadata)
|
|
2032
|
+
}
|
|
2033
|
+
];
|
|
2034
|
+
} catch {
|
|
2035
|
+
return [];
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
async function parseProjectAnnotationCompanion(filePath) {
|
|
2039
|
+
const plans = [];
|
|
2040
|
+
const seen = new Set;
|
|
2041
|
+
for (const planPath of projectPlanPathsForAnnotation(filePath)) {
|
|
2042
|
+
const resolved = resolve2(planPath);
|
|
2043
|
+
if (seen.has(resolved))
|
|
2044
|
+
continue;
|
|
2045
|
+
seen.add(resolved);
|
|
2046
|
+
if (!existsSync3(planPath))
|
|
2047
|
+
continue;
|
|
2048
|
+
plans.push(...await parseProjectPlan(planPath));
|
|
2049
|
+
}
|
|
2050
|
+
return plans;
|
|
2051
|
+
}
|
|
2052
|
+
async function parseLiveSession(filePath) {
|
|
2053
|
+
try {
|
|
2054
|
+
const raw = JSON.parse(await readFile6(filePath, "utf-8"));
|
|
2055
|
+
const session = parseSessionInfo(raw);
|
|
2056
|
+
if (!session?.pid || !session.url)
|
|
2057
|
+
return [];
|
|
2058
|
+
if (!isAlive(session.pid))
|
|
2059
|
+
return [];
|
|
2060
|
+
if (!isSafePlannotatorUrl(session.url))
|
|
2061
|
+
return [];
|
|
2062
|
+
const mode = session.mode ?? "plan";
|
|
2063
|
+
if (mode === "archive")
|
|
2064
|
+
return [];
|
|
2065
|
+
const planResponse = await fetchJson(apiUrl(session.url, "/api/plan"));
|
|
2066
|
+
if (!planResponse?.plan)
|
|
2067
|
+
return [];
|
|
2068
|
+
const stats = await stat6(filePath);
|
|
2069
|
+
const createdAt = parseDate2(session.startedAt) ?? stats.birthtime;
|
|
2070
|
+
const origin = planResponse.origin ?? session.origin;
|
|
2071
|
+
const responseMode = normalizeMode(planResponse.mode) ?? mode;
|
|
2072
|
+
const workspace = planResponse.projectRoot;
|
|
2073
|
+
const sourcePlanPath = session.sourcePlanPath ?? planResponse.filePath;
|
|
2074
|
+
const metadata = {
|
|
2075
|
+
kind: "live-session",
|
|
2076
|
+
mode: responseMode,
|
|
2077
|
+
status: "pending",
|
|
2078
|
+
origin,
|
|
2079
|
+
url: session.url,
|
|
2080
|
+
pid: session.pid,
|
|
2081
|
+
port: session.port,
|
|
2082
|
+
project: session.project ?? planResponse.versionInfo?.project,
|
|
2083
|
+
label: session.label,
|
|
2084
|
+
reviewId: session.reviewId,
|
|
2085
|
+
sessionPath: filePath,
|
|
2086
|
+
sourcePlanPath,
|
|
2087
|
+
startedAt: session.startedAt,
|
|
2088
|
+
writebackCapable: true
|
|
2089
|
+
};
|
|
2090
|
+
return [
|
|
2091
|
+
{
|
|
2092
|
+
id: hashPath(filePath),
|
|
2093
|
+
agent: origin ?? "plannotator",
|
|
2094
|
+
title: extractTitle5(planResponse.plan, session.label ?? filePath),
|
|
2095
|
+
content: planResponse.plan,
|
|
2096
|
+
filePath: sourcePlanPath ?? filePath,
|
|
2097
|
+
format: "md",
|
|
2098
|
+
createdAt,
|
|
2099
|
+
updatedAt: stats.mtime,
|
|
2100
|
+
workspace,
|
|
2101
|
+
metadata: metadataRecord(metadata)
|
|
2102
|
+
}
|
|
2103
|
+
];
|
|
2104
|
+
} catch {
|
|
2105
|
+
return [];
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function getPlannotatorMetadata(plan) {
|
|
2109
|
+
const metadata = plan.metadata.plannotator;
|
|
2110
|
+
if (!isRecord3(metadata))
|
|
2111
|
+
return;
|
|
2112
|
+
return metadata;
|
|
2113
|
+
}
|
|
2114
|
+
function formatWritebackFeedback(_plan, payload) {
|
|
2115
|
+
const sections = ["# Agendex Plan Feedback", "The user reviewed this plan in Agendex Cloud."];
|
|
2116
|
+
if (payload.feedback.trim()) {
|
|
2117
|
+
sections.push("## Feedback", payload.feedback.trim());
|
|
2118
|
+
}
|
|
2119
|
+
if (payload.revisedContent?.trim()) {
|
|
2120
|
+
sections.push("## Requested revision", "Use this revised plan content as the target shape when you update and resubmit the plan:", payload.revisedContent.trim());
|
|
2121
|
+
}
|
|
2122
|
+
if (payload.annotations?.length) {
|
|
2123
|
+
sections.push("## Typed annotations", JSON.stringify(payload.annotations, null, 2));
|
|
2124
|
+
}
|
|
2125
|
+
sections.push("Please revise the plan and resubmit it for Plannotator review.");
|
|
2126
|
+
return sections.join(`
|
|
2127
|
+
|
|
2128
|
+
`);
|
|
2129
|
+
}
|
|
2130
|
+
function annotationsForEndpoint(annotations) {
|
|
2131
|
+
return annotations ?? [];
|
|
2132
|
+
}
|
|
2133
|
+
var plannotatorAdapter = {
|
|
2134
|
+
agent: "plannotator",
|
|
2135
|
+
writable: false,
|
|
2136
|
+
getSearchPaths() {
|
|
2137
|
+
return [getPlansDir(), getSessionsDir()];
|
|
2138
|
+
},
|
|
2139
|
+
getWatchPaths() {
|
|
2140
|
+
return [getPlansDir(), getSessionsDir()];
|
|
2141
|
+
},
|
|
2142
|
+
matches(filePath) {
|
|
2143
|
+
const normalized = resolve2(filePath);
|
|
2144
|
+
const plansDir2 = resolve2(getPlansDir());
|
|
2145
|
+
const sessionsDir2 = resolve2(getSessionsDir());
|
|
2146
|
+
if (normalized.startsWith(plansDir2 + sep2))
|
|
2147
|
+
return isSnapshotFile(filePath) || isAnnotationFile(filePath);
|
|
2148
|
+
if (normalized.startsWith(sessionsDir2 + sep2))
|
|
2149
|
+
return isSessionFile2(filePath);
|
|
2150
|
+
if (isProjectPlansPath(filePath))
|
|
2151
|
+
return isProjectPlanFile(filePath) || isAnnotationFile(filePath);
|
|
2152
|
+
return false;
|
|
2153
|
+
},
|
|
2154
|
+
async parse(filePath) {
|
|
2155
|
+
const normalized = resolve2(filePath);
|
|
2156
|
+
const plansDir2 = resolve2(getPlansDir());
|
|
2157
|
+
const sessionsDir2 = resolve2(getSessionsDir());
|
|
2158
|
+
if (normalized.startsWith(plansDir2 + sep2) && isSnapshotFile(filePath)) {
|
|
2159
|
+
return await parseSnapshot(filePath);
|
|
2160
|
+
}
|
|
2161
|
+
if (normalized.startsWith(plansDir2 + sep2) && isAnnotationFile(filePath)) {
|
|
2162
|
+
return await parseAnnotationCompanion(filePath);
|
|
2163
|
+
}
|
|
2164
|
+
if (normalized.startsWith(sessionsDir2 + sep2) && isSessionFile2(filePath)) {
|
|
2165
|
+
return await parseLiveSession(filePath);
|
|
2166
|
+
}
|
|
2167
|
+
if (isProjectPlanFile(filePath)) {
|
|
2168
|
+
return await parseProjectPlan(filePath);
|
|
2169
|
+
}
|
|
2170
|
+
if (isProjectPlansPath(filePath) && isAnnotationFile(filePath)) {
|
|
2171
|
+
return await parseProjectAnnotationCompanion(filePath);
|
|
2172
|
+
}
|
|
2173
|
+
return [];
|
|
2174
|
+
},
|
|
2175
|
+
async write() {
|
|
2176
|
+
return false;
|
|
2177
|
+
},
|
|
2178
|
+
async requestChanges(plan, payload) {
|
|
2179
|
+
const metadata = getPlannotatorMetadata(plan);
|
|
2180
|
+
if (!metadata?.url || !metadata.writebackCapable)
|
|
2181
|
+
return false;
|
|
2182
|
+
if (!isSafePlannotatorUrl(metadata.url))
|
|
2183
|
+
return false;
|
|
2184
|
+
const feedback = formatWritebackFeedback(plan, payload);
|
|
2185
|
+
if (metadata.mode === "review" || metadata.mode === "annotate") {
|
|
2186
|
+
return await postJson(apiUrl(metadata.url, "/api/feedback"), {
|
|
2187
|
+
feedback,
|
|
2188
|
+
annotations: annotationsForEndpoint(payload.annotations)
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
return await postJson(apiUrl(metadata.url, "/api/deny"), {
|
|
2192
|
+
feedback,
|
|
2193
|
+
planSave: { enabled: true }
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
|
|
1751
2198
|
// ../shared/src/adapters/stub.ts
|
|
1752
2199
|
function createStubAdapter(agent, searchPaths, matchExt) {
|
|
1753
2200
|
return {
|
|
@@ -1762,7 +2209,7 @@ function createStubAdapter(agent, searchPaths, matchExt) {
|
|
|
1762
2209
|
}
|
|
1763
2210
|
|
|
1764
2211
|
// ../shared/src/adapters/catalog.ts
|
|
1765
|
-
var home =
|
|
2212
|
+
var home = homedir7();
|
|
1766
2213
|
var ADAPTER_AGENT_ALIASES = {
|
|
1767
2214
|
amp: "amp",
|
|
1768
2215
|
antigravity: "antigravity",
|
|
@@ -1792,6 +2239,7 @@ var ADAPTER_AGENT_ALIASES = {
|
|
|
1792
2239
|
opencode: "oh-my-opencode",
|
|
1793
2240
|
openhands: "openhands",
|
|
1794
2241
|
pi: "pi",
|
|
2242
|
+
plannotator: "plannotator",
|
|
1795
2243
|
qoder: "qoder",
|
|
1796
2244
|
"qwen-code": "qwen-code",
|
|
1797
2245
|
replit: "replit",
|
|
@@ -1812,7 +2260,7 @@ var CATALOG = [
|
|
|
1812
2260
|
group: "universal",
|
|
1813
2261
|
implemented: false,
|
|
1814
2262
|
defaultEnabled: true,
|
|
1815
|
-
createAdapter: () => createStubAdapter("amp", [
|
|
2263
|
+
createAdapter: () => createStubAdapter("amp", [join7(home, ".amp")], ".json")
|
|
1816
2264
|
},
|
|
1817
2265
|
{
|
|
1818
2266
|
id: "antigravity",
|
|
@@ -1820,7 +2268,7 @@ var CATALOG = [
|
|
|
1820
2268
|
group: "other",
|
|
1821
2269
|
implemented: false,
|
|
1822
2270
|
defaultEnabled: false,
|
|
1823
|
-
createAdapter: () => createStubAdapter("antigravity", [
|
|
2271
|
+
createAdapter: () => createStubAdapter("antigravity", [join7(home, ".gemini", "antigravity")], ".json")
|
|
1824
2272
|
},
|
|
1825
2273
|
{
|
|
1826
2274
|
id: "augment",
|
|
@@ -1828,7 +2276,7 @@ var CATALOG = [
|
|
|
1828
2276
|
group: "other",
|
|
1829
2277
|
implemented: false,
|
|
1830
2278
|
defaultEnabled: false,
|
|
1831
|
-
createAdapter: () => createStubAdapter("augment", [
|
|
2279
|
+
createAdapter: () => createStubAdapter("augment", [join7(home, ".augment")], ".json")
|
|
1832
2280
|
},
|
|
1833
2281
|
{
|
|
1834
2282
|
id: "claude-code",
|
|
@@ -1844,7 +2292,7 @@ var CATALOG = [
|
|
|
1844
2292
|
group: "other",
|
|
1845
2293
|
implemented: false,
|
|
1846
2294
|
defaultEnabled: false,
|
|
1847
|
-
createAdapter: () => createStubAdapter("openclaw", [
|
|
2295
|
+
createAdapter: () => createStubAdapter("openclaw", [join7(home, ".openclaw")], ".md")
|
|
1848
2296
|
},
|
|
1849
2297
|
{
|
|
1850
2298
|
id: "cline",
|
|
@@ -1853,8 +2301,8 @@ var CATALOG = [
|
|
|
1853
2301
|
implemented: false,
|
|
1854
2302
|
defaultEnabled: true,
|
|
1855
2303
|
createAdapter: () => createStubAdapter("cline", [
|
|
1856
|
-
|
|
1857
|
-
|
|
2304
|
+
join7(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev"),
|
|
2305
|
+
join7(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
|
|
1858
2306
|
], ".json")
|
|
1859
2307
|
},
|
|
1860
2308
|
{
|
|
@@ -1863,7 +2311,7 @@ var CATALOG = [
|
|
|
1863
2311
|
group: "other",
|
|
1864
2312
|
implemented: false,
|
|
1865
2313
|
defaultEnabled: false,
|
|
1866
|
-
createAdapter: () => createStubAdapter("codebuddy", [
|
|
2314
|
+
createAdapter: () => createStubAdapter("codebuddy", [join7(home, ".codebuddy")], ".md")
|
|
1867
2315
|
},
|
|
1868
2316
|
{
|
|
1869
2317
|
id: "codex",
|
|
@@ -1879,7 +2327,7 @@ var CATALOG = [
|
|
|
1879
2327
|
group: "other",
|
|
1880
2328
|
implemented: false,
|
|
1881
2329
|
defaultEnabled: false,
|
|
1882
|
-
createAdapter: () => createStubAdapter("command-code", [
|
|
2330
|
+
createAdapter: () => createStubAdapter("command-code", [join7(home, ".commandcode")], ".md")
|
|
1883
2331
|
},
|
|
1884
2332
|
{
|
|
1885
2333
|
id: "continue",
|
|
@@ -1895,7 +2343,7 @@ var CATALOG = [
|
|
|
1895
2343
|
group: "other",
|
|
1896
2344
|
implemented: false,
|
|
1897
2345
|
defaultEnabled: false,
|
|
1898
|
-
createAdapter: () => createStubAdapter("crush", [
|
|
2346
|
+
createAdapter: () => createStubAdapter("crush", [join7(home, ".config", "crush")], ".json")
|
|
1899
2347
|
},
|
|
1900
2348
|
{
|
|
1901
2349
|
id: "cursor",
|
|
@@ -1911,7 +2359,7 @@ var CATALOG = [
|
|
|
1911
2359
|
group: "other",
|
|
1912
2360
|
implemented: false,
|
|
1913
2361
|
defaultEnabled: true,
|
|
1914
|
-
createAdapter: () => createStubAdapter("droid", [
|
|
2362
|
+
createAdapter: () => createStubAdapter("droid", [join7(home, ".factory", "droids")], ".md")
|
|
1915
2363
|
},
|
|
1916
2364
|
{
|
|
1917
2365
|
id: "gemini-cli",
|
|
@@ -1919,7 +2367,7 @@ var CATALOG = [
|
|
|
1919
2367
|
group: "universal",
|
|
1920
2368
|
implemented: false,
|
|
1921
2369
|
defaultEnabled: false,
|
|
1922
|
-
createAdapter: () => createStubAdapter("gemini-cli", [
|
|
2370
|
+
createAdapter: () => createStubAdapter("gemini-cli", [join7(home, ".gemini")], ".md")
|
|
1923
2371
|
},
|
|
1924
2372
|
{
|
|
1925
2373
|
id: "github-copilot",
|
|
@@ -1927,7 +2375,7 @@ var CATALOG = [
|
|
|
1927
2375
|
group: "universal",
|
|
1928
2376
|
implemented: false,
|
|
1929
2377
|
defaultEnabled: true,
|
|
1930
|
-
createAdapter: () => createStubAdapter("copilot-chat", [
|
|
2378
|
+
createAdapter: () => createStubAdapter("copilot-chat", [join7(home, ".vscode", "User", "workspaceStorage")], ".json")
|
|
1931
2379
|
},
|
|
1932
2380
|
{
|
|
1933
2381
|
id: "goose",
|
|
@@ -1935,7 +2383,7 @@ var CATALOG = [
|
|
|
1935
2383
|
group: "other",
|
|
1936
2384
|
implemented: false,
|
|
1937
2385
|
defaultEnabled: false,
|
|
1938
|
-
createAdapter: () => createStubAdapter("goose", [
|
|
2386
|
+
createAdapter: () => createStubAdapter("goose", [join7(home, ".config", "goose")], ".json")
|
|
1939
2387
|
},
|
|
1940
2388
|
{
|
|
1941
2389
|
id: "junie",
|
|
@@ -1943,7 +2391,7 @@ var CATALOG = [
|
|
|
1943
2391
|
group: "other",
|
|
1944
2392
|
implemented: false,
|
|
1945
2393
|
defaultEnabled: false,
|
|
1946
|
-
createAdapter: () => createStubAdapter("junie", [
|
|
2394
|
+
createAdapter: () => createStubAdapter("junie", [join7(home, ".junie")], ".json")
|
|
1947
2395
|
},
|
|
1948
2396
|
{
|
|
1949
2397
|
id: "iflow-cli",
|
|
@@ -1951,7 +2399,7 @@ var CATALOG = [
|
|
|
1951
2399
|
group: "other",
|
|
1952
2400
|
implemented: false,
|
|
1953
2401
|
defaultEnabled: false,
|
|
1954
|
-
createAdapter: () => createStubAdapter("iflow-cli", [
|
|
2402
|
+
createAdapter: () => createStubAdapter("iflow-cli", [join7(home, ".iflow")], ".json")
|
|
1955
2403
|
},
|
|
1956
2404
|
{
|
|
1957
2405
|
id: "kilo",
|
|
@@ -1959,7 +2407,7 @@ var CATALOG = [
|
|
|
1959
2407
|
group: "other",
|
|
1960
2408
|
implemented: false,
|
|
1961
2409
|
defaultEnabled: true,
|
|
1962
|
-
createAdapter: () => createStubAdapter("kilo-cli", [
|
|
2410
|
+
createAdapter: () => createStubAdapter("kilo-cli", [join7(home, ".kilo")], ".md")
|
|
1963
2411
|
},
|
|
1964
2412
|
{
|
|
1965
2413
|
id: "kimi-cli",
|
|
@@ -1967,7 +2415,7 @@ var CATALOG = [
|
|
|
1967
2415
|
group: "universal",
|
|
1968
2416
|
implemented: false,
|
|
1969
2417
|
defaultEnabled: false,
|
|
1970
|
-
createAdapter: () => createStubAdapter("kimi-cli", [
|
|
2418
|
+
createAdapter: () => createStubAdapter("kimi-cli", [join7(home, ".kimi")], ".md")
|
|
1971
2419
|
},
|
|
1972
2420
|
{
|
|
1973
2421
|
id: "kiro-cli",
|
|
@@ -1975,7 +2423,7 @@ var CATALOG = [
|
|
|
1975
2423
|
group: "other",
|
|
1976
2424
|
implemented: false,
|
|
1977
2425
|
defaultEnabled: false,
|
|
1978
|
-
createAdapter: () => createStubAdapter("kiro-cli", [
|
|
2426
|
+
createAdapter: () => createStubAdapter("kiro-cli", [join7(home, ".kiro")], ".json")
|
|
1979
2427
|
},
|
|
1980
2428
|
{
|
|
1981
2429
|
id: "kode",
|
|
@@ -1983,7 +2431,7 @@ var CATALOG = [
|
|
|
1983
2431
|
group: "other",
|
|
1984
2432
|
implemented: false,
|
|
1985
2433
|
defaultEnabled: false,
|
|
1986
|
-
createAdapter: () => createStubAdapter("kode", [
|
|
2434
|
+
createAdapter: () => createStubAdapter("kode", [join7(home, ".kode")], ".json")
|
|
1987
2435
|
},
|
|
1988
2436
|
{
|
|
1989
2437
|
id: "mcpjam",
|
|
@@ -1991,7 +2439,7 @@ var CATALOG = [
|
|
|
1991
2439
|
group: "other",
|
|
1992
2440
|
implemented: false,
|
|
1993
2441
|
defaultEnabled: false,
|
|
1994
|
-
createAdapter: () => createStubAdapter("mcpjam", [
|
|
2442
|
+
createAdapter: () => createStubAdapter("mcpjam", [join7(home, ".mcpjam")], ".json")
|
|
1995
2443
|
},
|
|
1996
2444
|
{
|
|
1997
2445
|
id: "mistral-vibe",
|
|
@@ -1999,7 +2447,7 @@ var CATALOG = [
|
|
|
1999
2447
|
group: "other",
|
|
2000
2448
|
implemented: false,
|
|
2001
2449
|
defaultEnabled: false,
|
|
2002
|
-
createAdapter: () => createStubAdapter("mistral-vibe", [
|
|
2450
|
+
createAdapter: () => createStubAdapter("mistral-vibe", [join7(home, ".vibe")], ".json")
|
|
2003
2451
|
},
|
|
2004
2452
|
{
|
|
2005
2453
|
id: "mux",
|
|
@@ -2007,7 +2455,7 @@ var CATALOG = [
|
|
|
2007
2455
|
group: "other",
|
|
2008
2456
|
implemented: false,
|
|
2009
2457
|
defaultEnabled: false,
|
|
2010
|
-
createAdapter: () => createStubAdapter("mux", [
|
|
2458
|
+
createAdapter: () => createStubAdapter("mux", [join7(home, ".mux")], ".json")
|
|
2011
2459
|
},
|
|
2012
2460
|
{
|
|
2013
2461
|
id: "opencode",
|
|
@@ -2023,7 +2471,7 @@ var CATALOG = [
|
|
|
2023
2471
|
group: "other",
|
|
2024
2472
|
implemented: false,
|
|
2025
2473
|
defaultEnabled: false,
|
|
2026
|
-
createAdapter: () => createStubAdapter("openhands", [
|
|
2474
|
+
createAdapter: () => createStubAdapter("openhands", [join7(home, ".openhands")], ".json")
|
|
2027
2475
|
},
|
|
2028
2476
|
{
|
|
2029
2477
|
id: "pi",
|
|
@@ -2031,7 +2479,15 @@ var CATALOG = [
|
|
|
2031
2479
|
group: "other",
|
|
2032
2480
|
implemented: false,
|
|
2033
2481
|
defaultEnabled: false,
|
|
2034
|
-
createAdapter: () => createStubAdapter("pi", [
|
|
2482
|
+
createAdapter: () => createStubAdapter("pi", [join7(home, ".pi", "agent")], ".md")
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
id: "plannotator",
|
|
2486
|
+
displayName: "Plannotator",
|
|
2487
|
+
group: "universal",
|
|
2488
|
+
implemented: true,
|
|
2489
|
+
defaultEnabled: false,
|
|
2490
|
+
createAdapter: () => plannotatorAdapter
|
|
2035
2491
|
},
|
|
2036
2492
|
{
|
|
2037
2493
|
id: "qoder",
|
|
@@ -2039,7 +2495,7 @@ var CATALOG = [
|
|
|
2039
2495
|
group: "other",
|
|
2040
2496
|
implemented: false,
|
|
2041
2497
|
defaultEnabled: false,
|
|
2042
|
-
createAdapter: () => createStubAdapter("qoder", [
|
|
2498
|
+
createAdapter: () => createStubAdapter("qoder", [join7(home, ".qoder")], ".json")
|
|
2043
2499
|
},
|
|
2044
2500
|
{
|
|
2045
2501
|
id: "qwen-code",
|
|
@@ -2047,7 +2503,7 @@ var CATALOG = [
|
|
|
2047
2503
|
group: "other",
|
|
2048
2504
|
implemented: false,
|
|
2049
2505
|
defaultEnabled: false,
|
|
2050
|
-
createAdapter: () => createStubAdapter("qwen-code", [
|
|
2506
|
+
createAdapter: () => createStubAdapter("qwen-code", [join7(home, ".qwen")], ".json")
|
|
2051
2507
|
},
|
|
2052
2508
|
{
|
|
2053
2509
|
id: "replit",
|
|
@@ -2055,7 +2511,7 @@ var CATALOG = [
|
|
|
2055
2511
|
group: "other",
|
|
2056
2512
|
implemented: false,
|
|
2057
2513
|
defaultEnabled: false,
|
|
2058
|
-
createAdapter: () => createStubAdapter("replit", [
|
|
2514
|
+
createAdapter: () => createStubAdapter("replit", [join7(home, ".replit")], ".md")
|
|
2059
2515
|
},
|
|
2060
2516
|
{
|
|
2061
2517
|
id: "roo",
|
|
@@ -2063,7 +2519,7 @@ var CATALOG = [
|
|
|
2063
2519
|
group: "other",
|
|
2064
2520
|
implemented: false,
|
|
2065
2521
|
defaultEnabled: false,
|
|
2066
|
-
createAdapter: () => createStubAdapter("roo", [
|
|
2522
|
+
createAdapter: () => createStubAdapter("roo", [join7(home, ".roo")], ".md")
|
|
2067
2523
|
},
|
|
2068
2524
|
{
|
|
2069
2525
|
id: "trae",
|
|
@@ -2071,7 +2527,7 @@ var CATALOG = [
|
|
|
2071
2527
|
group: "other",
|
|
2072
2528
|
implemented: false,
|
|
2073
2529
|
defaultEnabled: false,
|
|
2074
|
-
createAdapter: () => createStubAdapter("trae", [
|
|
2530
|
+
createAdapter: () => createStubAdapter("trae", [join7(home, ".trae")], ".md")
|
|
2075
2531
|
},
|
|
2076
2532
|
{
|
|
2077
2533
|
id: "trae-cn",
|
|
@@ -2079,7 +2535,7 @@ var CATALOG = [
|
|
|
2079
2535
|
group: "other",
|
|
2080
2536
|
implemented: false,
|
|
2081
2537
|
defaultEnabled: false,
|
|
2082
|
-
createAdapter: () => createStubAdapter("trae-cn", [
|
|
2538
|
+
createAdapter: () => createStubAdapter("trae-cn", [join7(home, ".trae-cn")], ".md")
|
|
2083
2539
|
},
|
|
2084
2540
|
{
|
|
2085
2541
|
id: "windsurf",
|
|
@@ -2087,7 +2543,7 @@ var CATALOG = [
|
|
|
2087
2543
|
group: "other",
|
|
2088
2544
|
implemented: false,
|
|
2089
2545
|
defaultEnabled: true,
|
|
2090
|
-
createAdapter: () => createStubAdapter("windsurf", [
|
|
2546
|
+
createAdapter: () => createStubAdapter("windsurf", [join7(home, ".cascade_backups")], ".md")
|
|
2091
2547
|
},
|
|
2092
2548
|
{
|
|
2093
2549
|
id: "zencoder",
|
|
@@ -2095,7 +2551,7 @@ var CATALOG = [
|
|
|
2095
2551
|
group: "other",
|
|
2096
2552
|
implemented: false,
|
|
2097
2553
|
defaultEnabled: false,
|
|
2098
|
-
createAdapter: () => createStubAdapter("zencoder", [
|
|
2554
|
+
createAdapter: () => createStubAdapter("zencoder", [join7(home, ".zencoder")], ".json")
|
|
2099
2555
|
},
|
|
2100
2556
|
{
|
|
2101
2557
|
id: "neovate",
|
|
@@ -2103,7 +2559,7 @@ var CATALOG = [
|
|
|
2103
2559
|
group: "other",
|
|
2104
2560
|
implemented: false,
|
|
2105
2561
|
defaultEnabled: false,
|
|
2106
|
-
createAdapter: () => createStubAdapter("neovate", [
|
|
2562
|
+
createAdapter: () => createStubAdapter("neovate", [join7(home, ".neovate")], ".json")
|
|
2107
2563
|
},
|
|
2108
2564
|
{
|
|
2109
2565
|
id: "pochi",
|
|
@@ -2111,7 +2567,7 @@ var CATALOG = [
|
|
|
2111
2567
|
group: "other",
|
|
2112
2568
|
implemented: false,
|
|
2113
2569
|
defaultEnabled: false,
|
|
2114
|
-
createAdapter: () => createStubAdapter("pochi", [
|
|
2570
|
+
createAdapter: () => createStubAdapter("pochi", [join7(home, ".pochi")], ".json")
|
|
2115
2571
|
},
|
|
2116
2572
|
{
|
|
2117
2573
|
id: "adal",
|
|
@@ -2119,7 +2575,7 @@ var CATALOG = [
|
|
|
2119
2575
|
group: "other",
|
|
2120
2576
|
implemented: false,
|
|
2121
2577
|
defaultEnabled: false,
|
|
2122
|
-
createAdapter: () => createStubAdapter("adal", [
|
|
2578
|
+
createAdapter: () => createStubAdapter("adal", [join7(home, ".adal")], ".json")
|
|
2123
2579
|
},
|
|
2124
2580
|
{
|
|
2125
2581
|
id: "aider",
|
|
@@ -2127,7 +2583,7 @@ var CATALOG = [
|
|
|
2127
2583
|
group: "other",
|
|
2128
2584
|
implemented: false,
|
|
2129
2585
|
defaultEnabled: true,
|
|
2130
|
-
createAdapter: () => createStubAdapter("aider", [
|
|
2586
|
+
createAdapter: () => createStubAdapter("aider", [join7(home, ".aider")], ".aider.chat.history.md")
|
|
2131
2587
|
}
|
|
2132
2588
|
];
|
|
2133
2589
|
function getAdapterCatalog() {
|
|
@@ -2198,9 +2654,9 @@ function getActiveAdapters() {
|
|
|
2198
2654
|
var activeAdapters = resolveAdapters(getDefaultAdapterIds());
|
|
2199
2655
|
// ../shared/src/config.ts
|
|
2200
2656
|
import { randomBytes } from "node:crypto";
|
|
2201
|
-
import { existsSync as
|
|
2202
|
-
import { homedir as
|
|
2203
|
-
import { join as
|
|
2657
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
2658
|
+
import { homedir as homedir8 } from "node:os";
|
|
2659
|
+
import { join as join8, resolve as resolve3 } from "node:path";
|
|
2204
2660
|
|
|
2205
2661
|
// ../shared/src/setup/adapter-selection.ts
|
|
2206
2662
|
init_dist2();
|
|
@@ -2256,6 +2712,16 @@ async function promptForAdapterSelection(options = {}) {
|
|
|
2256
2712
|
|
|
2257
2713
|
// ../shared/src/config.ts
|
|
2258
2714
|
var devModeOverride;
|
|
2715
|
+
function getHomeDir() {
|
|
2716
|
+
if (process.env.HOME)
|
|
2717
|
+
return process.env.HOME;
|
|
2718
|
+
if (process.env.USERPROFILE)
|
|
2719
|
+
return process.env.USERPROFILE;
|
|
2720
|
+
if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
|
|
2721
|
+
return `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`;
|
|
2722
|
+
}
|
|
2723
|
+
return homedir8();
|
|
2724
|
+
}
|
|
2259
2725
|
function setDevMode(dev) {
|
|
2260
2726
|
devModeOverride = dev;
|
|
2261
2727
|
}
|
|
@@ -2265,16 +2731,19 @@ function isDevMode() {
|
|
|
2265
2731
|
return process.env.AGENDEX_DEV === "1";
|
|
2266
2732
|
}
|
|
2267
2733
|
function getConfigDir() {
|
|
2268
|
-
|
|
2734
|
+
const override = process.env.AGENDEX_CONFIG_DIR?.trim();
|
|
2735
|
+
if (override)
|
|
2736
|
+
return resolve3(expandHomePath(override));
|
|
2737
|
+
return join8(getHomeDir(), isDevMode() ? ".agendex-dev" : ".agendex");
|
|
2269
2738
|
}
|
|
2270
2739
|
function ensureConfigDir() {
|
|
2271
2740
|
const dir = getConfigDir();
|
|
2272
|
-
if (!
|
|
2741
|
+
if (!existsSync4(dir))
|
|
2273
2742
|
mkdirSync(dir, { recursive: true });
|
|
2274
2743
|
}
|
|
2275
2744
|
function readStoredConfig() {
|
|
2276
2745
|
const cfgPath = getConfigPath();
|
|
2277
|
-
if (!
|
|
2746
|
+
if (!existsSync4(cfgPath))
|
|
2278
2747
|
return null;
|
|
2279
2748
|
try {
|
|
2280
2749
|
const raw = JSON.parse(readFileSync3(cfgPath, "utf-8"));
|
|
@@ -2292,7 +2761,7 @@ function normalizeAdapterIds(input) {
|
|
|
2292
2761
|
}
|
|
2293
2762
|
function expandHomePath(p) {
|
|
2294
2763
|
if (p.startsWith("~/") || p === "~")
|
|
2295
|
-
return
|
|
2764
|
+
return join8(getHomeDir(), p.slice(1));
|
|
2296
2765
|
return p;
|
|
2297
2766
|
}
|
|
2298
2767
|
function resolveCustomPlanDirPath(userPath) {
|
|
@@ -2300,7 +2769,7 @@ function resolveCustomPlanDirPath(userPath) {
|
|
|
2300
2769
|
if (!trimmed) {
|
|
2301
2770
|
throw new Error("Custom plan directory path must not be empty");
|
|
2302
2771
|
}
|
|
2303
|
-
return
|
|
2772
|
+
return resolve3(expandHomePath(trimmed));
|
|
2304
2773
|
}
|
|
2305
2774
|
function normalizeCustomPlanDirs(input) {
|
|
2306
2775
|
if (!Array.isArray(input))
|
|
@@ -2365,6 +2834,7 @@ function loadOrCreateToken() {
|
|
|
2365
2834
|
return existing.token;
|
|
2366
2835
|
const token = generateToken();
|
|
2367
2836
|
saveConfig({
|
|
2837
|
+
...existing ?? {},
|
|
2368
2838
|
configVersion: 3,
|
|
2369
2839
|
token,
|
|
2370
2840
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
@@ -2391,7 +2861,7 @@ function loadOrCreateDeviceId() {
|
|
|
2391
2861
|
return deviceId;
|
|
2392
2862
|
}
|
|
2393
2863
|
function getConfigPath() {
|
|
2394
|
-
return
|
|
2864
|
+
return join8(getConfigDir(), "config.json");
|
|
2395
2865
|
}
|
|
2396
2866
|
async function loadOrInitConfig(options = {}) {
|
|
2397
2867
|
const configureAdapters = Boolean(options.configureAdapters);
|
|
@@ -2438,17 +2908,348 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2438
2908
|
var CLI_DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
|
|
2439
2909
|
var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
|
|
2440
2910
|
// ../shared/src/services/plan-service.ts
|
|
2441
|
-
import { existsSync as
|
|
2442
|
-
import { lstat, mkdir, readdir as readdir2, readFile as
|
|
2443
|
-
import { homedir as
|
|
2444
|
-
import { join as
|
|
2911
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, realpathSync, statSync } from "node:fs";
|
|
2912
|
+
import { lstat, mkdir, readdir as readdir2, readFile as readFile7, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
|
|
2913
|
+
import { homedir as homedir9 } from "node:os";
|
|
2914
|
+
import { dirname as dirname2, join as join9, resolve as resolve4, sep as sep3 } from "node:path";
|
|
2915
|
+
|
|
2916
|
+
// ../shared/src/services/plan-value.ts
|
|
2917
|
+
function isLowValuePlan(plan) {
|
|
2918
|
+
const metadata = plan.metadata;
|
|
2919
|
+
return typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) && metadata.lowValue === true;
|
|
2920
|
+
}
|
|
2921
|
+
function isIndexablePlan(plan) {
|
|
2922
|
+
return !isLowValuePlan(plan);
|
|
2923
|
+
}
|
|
2924
|
+
var PROPOSED_PLAN_TAG_REGEX2 = /<\s*\/?\s*proposed_plan\s*>/gi;
|
|
2925
|
+
var ESCAPED_PROPOSED_PLAN_TAG_REGEX2 = /<\s*\/?\s*proposed_plan\s*>/gi;
|
|
2926
|
+
var VISIBLE_TEXT_REGEX = /[\p{L}\p{N}]/u;
|
|
2927
|
+
var LOW_VALUE_METADATA_KEYS = ["lowValue", "lowValueReasons", "lowValueSignals"];
|
|
2928
|
+
function normalizeLineEndings2(text) {
|
|
2929
|
+
return text.replace(/\r\n?/g, `
|
|
2930
|
+
`);
|
|
2931
|
+
}
|
|
2932
|
+
function stripBoundaryHtmlComments(text) {
|
|
2933
|
+
let next = text;
|
|
2934
|
+
let previous = "";
|
|
2935
|
+
while (next !== previous) {
|
|
2936
|
+
previous = next;
|
|
2937
|
+
next = next.replace(/^\s*<!--[\s\S]*?-->\s*/, "").replace(/\s*<!--[\s\S]*?-->\s*$/, "");
|
|
2938
|
+
}
|
|
2939
|
+
return next;
|
|
2940
|
+
}
|
|
2941
|
+
function normalizePlanContent(content) {
|
|
2942
|
+
return stripBoundaryHtmlComments(normalizeLineEndings2(content).replace(/^\uFEFF/, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "")).trim();
|
|
2943
|
+
}
|
|
2944
|
+
function withoutLowValueMetadata(metadata) {
|
|
2945
|
+
const next = { ...metadata ?? {} };
|
|
2946
|
+
for (const key of LOW_VALUE_METADATA_KEYS)
|
|
2947
|
+
delete next[key];
|
|
2948
|
+
return next;
|
|
2949
|
+
}
|
|
2950
|
+
function unique(items) {
|
|
2951
|
+
return Array.from(new Set(items));
|
|
2952
|
+
}
|
|
2953
|
+
function visibleText(text) {
|
|
2954
|
+
return text.replace(/<!--[\s\S]*?-->/g, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "").replace(/[`*_#[\](){}<>:|~\-+=.]/g, " ").trim();
|
|
2955
|
+
}
|
|
2956
|
+
function isSeparatorLine(line) {
|
|
2957
|
+
return /^(?:-{3,}|_{3,}|\*{3,})$/.test(line.trim());
|
|
2958
|
+
}
|
|
2959
|
+
function isFenceLine(line) {
|
|
2960
|
+
return /^(?:`{3,}|~{3,})/.test(line.trim());
|
|
2961
|
+
}
|
|
2962
|
+
function meaningfulLines(text) {
|
|
2963
|
+
return text.split(`
|
|
2964
|
+
`).map((line) => line.trim()).filter((line) => line && !isSeparatorLine(line) && !isFenceLine(line));
|
|
2965
|
+
}
|
|
2966
|
+
function cleanMarkdownLine(line) {
|
|
2967
|
+
return line.trim().replace(/^>+\s*/, "").replace(/^#{1,6}\s+/, "").replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").replace(/^\[[ xX]\]\s+/, "").replace(/^\*\*|\*\*$/g, "").replace(/^`|`$/g, "").trim();
|
|
2968
|
+
}
|
|
2969
|
+
function isHeadingLine(line) {
|
|
2970
|
+
return /^#{1,6}\s+\S/.test(line.trim());
|
|
2971
|
+
}
|
|
2972
|
+
function isChecklistLine(line) {
|
|
2973
|
+
return /^[-*+]\s+\[[ xX]\]\s+\S/.test(line.trim());
|
|
2974
|
+
}
|
|
2975
|
+
function isOrderedListLine(line) {
|
|
2976
|
+
return /^\d+[.)]\s+\S/.test(line.trim());
|
|
2977
|
+
}
|
|
2978
|
+
function isHeadingOnly(lines) {
|
|
2979
|
+
return lines.length > 0 && lines.some(isHeadingLine) && lines.every(isHeadingLine);
|
|
2980
|
+
}
|
|
2981
|
+
function metadataHasPlanBlocks(metadata) {
|
|
2982
|
+
const planBlocks = metadata?.planBlocks;
|
|
2983
|
+
return typeof planBlocks === "number" && planBlocks > 0;
|
|
2984
|
+
}
|
|
2985
|
+
function sectionName(line) {
|
|
2986
|
+
return cleanMarkdownLine(line).replace(/:$/, "").toLowerCase().replace(/\s+/g, " ");
|
|
2987
|
+
}
|
|
2988
|
+
var SECTION_LABELS = new Set([
|
|
2989
|
+
"context",
|
|
2990
|
+
"background",
|
|
2991
|
+
"problem",
|
|
2992
|
+
"goal",
|
|
2993
|
+
"goals",
|
|
2994
|
+
"scope",
|
|
2995
|
+
"approach",
|
|
2996
|
+
"strategy",
|
|
2997
|
+
"design",
|
|
2998
|
+
"implementation plan",
|
|
2999
|
+
"implementation",
|
|
3000
|
+
"plan",
|
|
3001
|
+
"files to modify",
|
|
3002
|
+
"files changed",
|
|
3003
|
+
"affected files",
|
|
3004
|
+
"steps",
|
|
3005
|
+
"step",
|
|
3006
|
+
"tasks",
|
|
3007
|
+
"task",
|
|
3008
|
+
"checklist",
|
|
3009
|
+
"todo",
|
|
3010
|
+
"todos",
|
|
3011
|
+
"verification",
|
|
3012
|
+
"testing",
|
|
3013
|
+
"tests",
|
|
3014
|
+
"test",
|
|
3015
|
+
"validation",
|
|
3016
|
+
"reuse",
|
|
3017
|
+
"existing utilities",
|
|
3018
|
+
"existing code",
|
|
3019
|
+
"acceptance criteria",
|
|
3020
|
+
"success criteria"
|
|
3021
|
+
]);
|
|
3022
|
+
function hasSectionSyntax(line, section) {
|
|
3023
|
+
const cleaned = cleanMarkdownLine(line);
|
|
3024
|
+
return isHeadingLine(line) || /^[a-z][\w\s/&+-]{1,48}:/i.test(cleaned) || SECTION_LABELS.has(section);
|
|
3025
|
+
}
|
|
3026
|
+
function collectPositiveSignals(normalized, lines, metadata) {
|
|
3027
|
+
const signals = [];
|
|
3028
|
+
if (metadataHasPlanBlocks(metadata))
|
|
3029
|
+
signals.push("metadata:proposed-plan-block");
|
|
3030
|
+
for (const line of lines) {
|
|
3031
|
+
const section = sectionName(line);
|
|
3032
|
+
if (!hasSectionSyntax(line, section))
|
|
3033
|
+
continue;
|
|
3034
|
+
if (/^(context|background|problem|goal|goals|scope)\b/.test(section)) {
|
|
3035
|
+
signals.push("section:context");
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
if (/^(approach|strategy|design)\b/.test(section)) {
|
|
3039
|
+
signals.push("section:approach");
|
|
3040
|
+
continue;
|
|
3041
|
+
}
|
|
3042
|
+
if (/^(implementation plan|implementation|plan)\b/.test(section)) {
|
|
3043
|
+
signals.push("section:implementation-plan");
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
if (/^(files? to modify|files? changed|affected files?)\b/.test(section)) {
|
|
3047
|
+
signals.push("section:files-to-modify");
|
|
3048
|
+
continue;
|
|
3049
|
+
}
|
|
3050
|
+
if (/^(steps?|tasks?|checklist|todo|todos)\b/.test(section)) {
|
|
3051
|
+
signals.push("section:steps");
|
|
3052
|
+
continue;
|
|
3053
|
+
}
|
|
3054
|
+
if (/^(verification|testing|tests?|validation)\b/.test(section)) {
|
|
3055
|
+
signals.push("section:verification");
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
if (/^(reuse|existing utilities|existing code)\b/.test(section)) {
|
|
3059
|
+
signals.push("section:reuse");
|
|
3060
|
+
continue;
|
|
3061
|
+
}
|
|
3062
|
+
if (/^(acceptance criteria|success criteria)\b/.test(section)) {
|
|
3063
|
+
signals.push("section:acceptance-criteria");
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
if (lines.some(isChecklistLine))
|
|
3067
|
+
signals.push("checklist");
|
|
3068
|
+
const orderedStepCount = lines.filter(isOrderedListLine).length;
|
|
3069
|
+
if (orderedStepCount >= 2)
|
|
3070
|
+
signals.push("ordered-steps");
|
|
3071
|
+
const actionBulletCount = lines.filter((line) => {
|
|
3072
|
+
const trimmed = line.trim();
|
|
3073
|
+
if (!/^(?:[-*+]|\d+[.)])\s+/.test(trimmed))
|
|
3074
|
+
return false;
|
|
3075
|
+
const cleaned = cleanMarkdownLine(trimmed);
|
|
3076
|
+
return /^(?:add|implement|update|modify|create|remove|delete|refactor|test|verify|run|wire|persist|handle|ensure|document|rename|move|extract|reuse|validate)\b/i.test(cleaned);
|
|
3077
|
+
}).length;
|
|
3078
|
+
if (actionBulletCount >= 2)
|
|
3079
|
+
signals.push("action-bullets");
|
|
3080
|
+
if (lines.length >= 2 && /\b(?:will|should|need to|needs to|plan to|planned|approach is to|implementation will)\b/i.test(normalized)) {
|
|
3081
|
+
signals.push("future-plan-language");
|
|
3082
|
+
}
|
|
3083
|
+
return unique(signals);
|
|
3084
|
+
}
|
|
3085
|
+
function isStrongPositiveSignal(signal) {
|
|
3086
|
+
return signal === "metadata:proposed-plan-block" || signal === "checklist" || signal === "ordered-steps" || signal === "action-bullets" || signal === "section:approach" || signal === "section:implementation-plan" || signal === "section:files-to-modify" || signal === "section:steps" || signal === "section:acceptance-criteria";
|
|
3087
|
+
}
|
|
3088
|
+
function hasStrongPlanSignal(positiveSignals) {
|
|
3089
|
+
const sectionCount = positiveSignals.filter((signal) => signal.startsWith("section:")).length;
|
|
3090
|
+
return positiveSignals.some(isStrongPositiveSignal) || sectionCount >= 2;
|
|
3091
|
+
}
|
|
3092
|
+
function isPromptLikeOneLiner(line) {
|
|
3093
|
+
if (isHeadingLine(line) || isChecklistLine(line) || isOrderedListLine(line))
|
|
3094
|
+
return false;
|
|
3095
|
+
const cleaned = cleanMarkdownLine(line).toLowerCase();
|
|
3096
|
+
if (!cleaned)
|
|
3097
|
+
return false;
|
|
3098
|
+
return [
|
|
3099
|
+
/^(?:important:\s*)?work in\b/,
|
|
3100
|
+
/^(?:please|pls)\b/,
|
|
3101
|
+
/^(?:can|could|would|will)\s+you\b/,
|
|
3102
|
+
/^(?:i|we)\s+(?:need|want|would like|have to)\b/,
|
|
3103
|
+
/^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
|
|
3104
|
+
/\?$/,
|
|
3105
|
+
/\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
|
|
3106
|
+
].some((pattern) => pattern.test(cleaned));
|
|
3107
|
+
}
|
|
3108
|
+
function normalizedTitle(title) {
|
|
3109
|
+
return cleanMarkdownLine(title ?? "").toLowerCase();
|
|
3110
|
+
}
|
|
3111
|
+
function looksLikeWrapperTitle(title) {
|
|
3112
|
+
return /^<user_(?:action|instructions|prompt)>$/i.test((title ?? "").trim());
|
|
3113
|
+
}
|
|
3114
|
+
function looksLikePromptTitle(title) {
|
|
3115
|
+
const cleaned = normalizedTitle(title);
|
|
3116
|
+
if (!cleaned)
|
|
3117
|
+
return false;
|
|
3118
|
+
return [
|
|
3119
|
+
/^(?:important:\s*)?work in\b/,
|
|
3120
|
+
/^review the code changes against\b/,
|
|
3121
|
+
/^perform a .*review\b/,
|
|
3122
|
+
/^(?:please|pls)\b/,
|
|
3123
|
+
/^(?:can|could|would|will)\s+you\b/,
|
|
3124
|
+
/^(?:i|we)\s+(?:need|want|would like|have to)\b/,
|
|
3125
|
+
/^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
|
|
3126
|
+
/\?$/,
|
|
3127
|
+
/\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
|
|
3128
|
+
].some((pattern) => pattern.test(cleaned));
|
|
3129
|
+
}
|
|
3130
|
+
function looksLikeReviewOutput(normalized, title) {
|
|
3131
|
+
const cleanedTitle = normalizedTitle(title);
|
|
3132
|
+
const lower = normalized.toLowerCase();
|
|
3133
|
+
return /^review the code changes against\b/.test(cleanedTitle) || /^perform a .*review\b/.test(cleanedTitle) || /"findings"\s*:\s*\[/.test(normalized) || /"overall_correctness"\s*:/.test(normalized) || /\bfull review comments\s*:/i.test(normalized) || /\bthe patch (?:currently )?(?:breaks|introduces|regresses)\b/i.test(normalized) || /\bshould not be considered correct\b/i.test(lower);
|
|
3134
|
+
}
|
|
3135
|
+
function looksLikeSystemContext(normalized, lines) {
|
|
3136
|
+
const lower = normalized.toLowerCase();
|
|
3137
|
+
if (lower.startsWith("# agents.md instructions") || lower.startsWith("<environment_context>") || lower.startsWith("<system-reminder>") || lower.startsWith("<thinking>")) {
|
|
3138
|
+
return true;
|
|
3139
|
+
}
|
|
3140
|
+
const wrapperMatches = normalized.match(/<\/?(?:environment_context|system-reminder|thinking|analysis|reasoning|tool_call|tool_result)\b[^>]*>/gi);
|
|
3141
|
+
if (wrapperMatches && wrapperMatches.length >= 2)
|
|
3142
|
+
return true;
|
|
3143
|
+
const wrapperLineCount = lines.filter((line) => /^(?:analysis|reasoning|thought|assistant thought|system|developer):\b/i.test(line)).length;
|
|
3144
|
+
return wrapperLineCount > 0 && wrapperLineCount / Math.max(lines.length, 1) >= 0.4;
|
|
3145
|
+
}
|
|
3146
|
+
function looksLikeToolLog(normalized) {
|
|
3147
|
+
return /::[a-z0-9_-]+(?:\{|\[|\s*$)/i.test(normalized) || /<\/?(?:tool_call|tool_result)\b[^>]*>/i.test(normalized) || /\b(?:function_call|tool_calls|tool_result)\b/i.test(normalized);
|
|
3148
|
+
}
|
|
3149
|
+
function looksLikeExecutionReport(normalized) {
|
|
3150
|
+
const hasPastCompletion = /\b(?:fixed|pushed|committed|completed|done|implemented|updated|changed|patched|merged|deployed|passed|failed|resolved|reverted)\b/i.test(normalized);
|
|
3151
|
+
const hasReportSection = /^\s*(?:summary|result|results|changes|verification|status)\s*:/im.test(normalized);
|
|
3152
|
+
const hasReviewReportMarker = /\b(?:review findings?|review issues?|review comments?)\b/i.test(normalized);
|
|
3153
|
+
const hasCommandMarker = /::[a-z0-9_-]+(?:\{|\[|\s*$)/im.test(normalized) || /`[^`]*(?:bun|npm|pnpm|yarn|git|tsc|biome)[^`]*`/i.test(normalized) || /\b(?:git\s+(?:stage|commit|push|status)|bunx?\s+|npm\s+|pnpm\s+|yarn\s+)\b/i.test(normalized);
|
|
3154
|
+
return hasPastCompletion && (hasReportSection || hasCommandMarker || hasReviewReportMarker);
|
|
3155
|
+
}
|
|
3156
|
+
function lowValueAssessment(reasons, signals) {
|
|
3157
|
+
return {
|
|
3158
|
+
lowValue: reasons.length > 0,
|
|
3159
|
+
reasons: unique(reasons),
|
|
3160
|
+
signals: unique(signals).slice(0, 20)
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
function assessPlanValue(input) {
|
|
3164
|
+
const metadata = withoutLowValueMetadata(input.metadata);
|
|
3165
|
+
const normalized = normalizePlanContent(input.content);
|
|
3166
|
+
const lines = meaningfulLines(normalized);
|
|
3167
|
+
const positiveSignals = collectPositiveSignals(normalized, lines, metadata);
|
|
3168
|
+
const signals = [...positiveSignals];
|
|
3169
|
+
const reasons = [];
|
|
3170
|
+
if (!VISIBLE_TEXT_REGEX.test(visibleText(normalized))) {
|
|
3171
|
+
return lowValueAssessment(["empty-content"], ["negative:empty-content"]);
|
|
3172
|
+
}
|
|
3173
|
+
if (isHeadingOnly(lines)) {
|
|
3174
|
+
return lowValueAssessment(["heading-only"], [...signals, "negative:heading-only"]);
|
|
3175
|
+
}
|
|
3176
|
+
const explicitPlanBlock = metadataHasPlanBlocks(metadata);
|
|
3177
|
+
const strongPositive = hasStrongPlanSignal(positiveSignals);
|
|
3178
|
+
const systemContext = looksLikeSystemContext(normalized, lines);
|
|
3179
|
+
const toolLog = looksLikeToolLog(normalized);
|
|
3180
|
+
const executionReport = looksLikeExecutionReport(normalized);
|
|
3181
|
+
const wrapperTitle = looksLikeWrapperTitle(input.title);
|
|
3182
|
+
const promptTitle = looksLikePromptTitle(input.title);
|
|
3183
|
+
const reviewOutput = looksLikeReviewOutput(normalized, input.title);
|
|
3184
|
+
if (systemContext)
|
|
3185
|
+
signals.push("negative:system-context");
|
|
3186
|
+
if (toolLog)
|
|
3187
|
+
signals.push("negative:tool-log");
|
|
3188
|
+
if (executionReport)
|
|
3189
|
+
signals.push("negative:execution-report");
|
|
3190
|
+
if (wrapperTitle)
|
|
3191
|
+
signals.push("negative:wrapper-title");
|
|
3192
|
+
if (promptTitle)
|
|
3193
|
+
signals.push("negative:prompt-title");
|
|
3194
|
+
if (reviewOutput)
|
|
3195
|
+
signals.push("negative:review-output");
|
|
3196
|
+
if (lines.length === 1)
|
|
3197
|
+
signals.push("shape:single-line");
|
|
3198
|
+
if (lines.length === 1 && positiveSignals.length === 0) {
|
|
3199
|
+
reasons.push(isPromptLikeOneLiner(lines[0] ?? "") ? "prompt-like" : "no-plan-signals");
|
|
3200
|
+
}
|
|
3201
|
+
if (systemContext && !strongPositive)
|
|
3202
|
+
reasons.push("system-context");
|
|
3203
|
+
if (executionReport && !strongPositive)
|
|
3204
|
+
reasons.push("execution-report");
|
|
3205
|
+
if (wrapperTitle && !explicitPlanBlock)
|
|
3206
|
+
reasons.push("wrapper-title");
|
|
3207
|
+
if (reviewOutput && !explicitPlanBlock)
|
|
3208
|
+
reasons.push("review-output");
|
|
3209
|
+
if (promptTitle && !strongPositive && positiveSignals.length === 0)
|
|
3210
|
+
reasons.push("prompt-like");
|
|
3211
|
+
if (toolLog && !strongPositive && positiveSignals.length === 0)
|
|
3212
|
+
reasons.push("no-plan-signals");
|
|
3213
|
+
if (reasons.length === 0 && positiveSignals.length === 0 && lines.length <= 3) {
|
|
3214
|
+
reasons.push("no-plan-signals");
|
|
3215
|
+
}
|
|
3216
|
+
return lowValueAssessment(reasons, signals);
|
|
3217
|
+
}
|
|
3218
|
+
function annotatePlanValueMetadata(plan) {
|
|
3219
|
+
const baseMetadata = withoutLowValueMetadata(plan.metadata);
|
|
3220
|
+
const assessment = assessPlanValue({
|
|
3221
|
+
content: plan.content,
|
|
3222
|
+
title: plan.title,
|
|
3223
|
+
metadata: baseMetadata
|
|
3224
|
+
});
|
|
3225
|
+
if (!assessment.lowValue) {
|
|
3226
|
+
return {
|
|
3227
|
+
...plan,
|
|
3228
|
+
metadata: baseMetadata
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
return {
|
|
3232
|
+
...plan,
|
|
3233
|
+
metadata: {
|
|
3234
|
+
...baseMetadata,
|
|
3235
|
+
lowValue: true,
|
|
3236
|
+
lowValueReasons: assessment.reasons,
|
|
3237
|
+
lowValueSignals: assessment.signals
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// ../shared/src/services/plan-service.ts
|
|
2445
3243
|
function getUserPlansDir() {
|
|
2446
|
-
return
|
|
3244
|
+
return join9(getConfigDir(), "plans");
|
|
2447
3245
|
}
|
|
2448
3246
|
var store = new Map;
|
|
2449
3247
|
var MAX_DEPTH = 6;
|
|
2450
3248
|
var DISCOVERY_MAX_DEPTH = 4;
|
|
2451
|
-
var PROJECT_PLAN_MARKERS = [
|
|
3249
|
+
var PROJECT_PLAN_MARKERS = [
|
|
3250
|
+
{ marker: ".sisyphus/plans", agent: "oh-my-opencode" },
|
|
3251
|
+
{ marker: "@plans", agent: "plannotator" }
|
|
3252
|
+
];
|
|
2452
3253
|
var SKIP_DIRS = new Set([
|
|
2453
3254
|
"node_modules",
|
|
2454
3255
|
".git",
|
|
@@ -2481,19 +3282,58 @@ var SKIP_DIRS = new Set([
|
|
|
2481
3282
|
"Google Drive",
|
|
2482
3283
|
"iCloud Drive"
|
|
2483
3284
|
]);
|
|
3285
|
+
function getRuntimeHomeDir() {
|
|
3286
|
+
const homeFromEnv = process.env.HOME || process.env.USERPROFILE || (process.env.HOMEDRIVE && process.env.HOMEPATH ? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}` : undefined);
|
|
3287
|
+
return resolve4(homeFromEnv || homedir9());
|
|
3288
|
+
}
|
|
2484
3289
|
function discoverProjectPlanDirs() {
|
|
2485
|
-
const home2 =
|
|
3290
|
+
const home2 = canonicalPath(getRuntimeHomeDir());
|
|
2486
3291
|
const results = [];
|
|
2487
|
-
|
|
2488
|
-
|
|
3292
|
+
const seen = new Set;
|
|
3293
|
+
let nearestAncestorMarkerRoot;
|
|
3294
|
+
function addResult(dir, agent) {
|
|
3295
|
+
const resolved = canonicalPath(dir);
|
|
3296
|
+
const key = `${agent}:${resolved}`;
|
|
3297
|
+
if (seen.has(key))
|
|
2489
3298
|
return;
|
|
3299
|
+
seen.add(key);
|
|
3300
|
+
results.push({ dir: resolved, agent });
|
|
3301
|
+
}
|
|
3302
|
+
function inspectMarkers(dir) {
|
|
3303
|
+
let found = false;
|
|
2490
3304
|
for (const { marker, agent } of PROJECT_PLAN_MARKERS) {
|
|
2491
|
-
const candidate =
|
|
2492
|
-
if (
|
|
2493
|
-
|
|
3305
|
+
const candidate = join9(dir, marker);
|
|
3306
|
+
if (existsSync5(candidate)) {
|
|
3307
|
+
addResult(candidate, agent);
|
|
3308
|
+
found = true;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
return found;
|
|
3312
|
+
}
|
|
3313
|
+
function walkAncestorsForCurrentProject() {
|
|
3314
|
+
let dir = canonicalPath(process.cwd());
|
|
3315
|
+
while (true) {
|
|
3316
|
+
if (inspectMarkers(dir)) {
|
|
3317
|
+
nearestAncestorMarkerRoot = dir;
|
|
2494
3318
|
return;
|
|
2495
3319
|
}
|
|
3320
|
+
if (dir === home2)
|
|
3321
|
+
return;
|
|
3322
|
+
const parent = dirname2(dir);
|
|
3323
|
+
if (parent === dir)
|
|
3324
|
+
return;
|
|
3325
|
+
dir = parent;
|
|
2496
3326
|
}
|
|
3327
|
+
}
|
|
3328
|
+
function isAncestorOfNearestMarker(dir) {
|
|
3329
|
+
return Boolean(nearestAncestorMarkerRoot?.startsWith(dir + sep3));
|
|
3330
|
+
}
|
|
3331
|
+
function walk(dir, depth) {
|
|
3332
|
+
if (depth > DISCOVERY_MAX_DEPTH)
|
|
3333
|
+
return;
|
|
3334
|
+
const resolved = canonicalPath(dir);
|
|
3335
|
+
if (!isAncestorOfNearestMarker(resolved) && inspectMarkers(dir))
|
|
3336
|
+
return;
|
|
2497
3337
|
let names;
|
|
2498
3338
|
try {
|
|
2499
3339
|
names = readdirSync3(dir);
|
|
@@ -2505,13 +3345,14 @@ function discoverProjectPlanDirs() {
|
|
|
2505
3345
|
continue;
|
|
2506
3346
|
if (SKIP_DIRS.has(name))
|
|
2507
3347
|
continue;
|
|
2508
|
-
const full =
|
|
3348
|
+
const full = join9(dir, name);
|
|
2509
3349
|
try {
|
|
2510
3350
|
if (statSync(full).isDirectory())
|
|
2511
3351
|
walk(full, depth + 1);
|
|
2512
3352
|
} catch {}
|
|
2513
3353
|
}
|
|
2514
3354
|
}
|
|
3355
|
+
walkAncestorsForCurrentProject();
|
|
2515
3356
|
walk(home2, 0);
|
|
2516
3357
|
return results;
|
|
2517
3358
|
}
|
|
@@ -2525,9 +3366,9 @@ function notifyPlansChanged() {
|
|
|
2525
3366
|
async function walkDir(dir, depth = 0, seen = new Set) {
|
|
2526
3367
|
if (depth > MAX_DEPTH)
|
|
2527
3368
|
return [];
|
|
2528
|
-
if (!
|
|
3369
|
+
if (!existsSync5(dir))
|
|
2529
3370
|
return [];
|
|
2530
|
-
const real =
|
|
3371
|
+
const real = resolve4(dir);
|
|
2531
3372
|
if (seen.has(real))
|
|
2532
3373
|
return [];
|
|
2533
3374
|
seen.add(real);
|
|
@@ -2535,7 +3376,7 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2535
3376
|
try {
|
|
2536
3377
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
2537
3378
|
for (const entry of entries) {
|
|
2538
|
-
const full =
|
|
3379
|
+
const full = join9(dir, entry.name);
|
|
2539
3380
|
try {
|
|
2540
3381
|
const stats = await lstat(full);
|
|
2541
3382
|
if (stats.isSymbolicLink())
|
|
@@ -2552,8 +3393,8 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2552
3393
|
}
|
|
2553
3394
|
async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
2554
3395
|
try {
|
|
2555
|
-
const content = await
|
|
2556
|
-
const stats = await
|
|
3396
|
+
const content = await readFile7(filePath, "utf-8");
|
|
3397
|
+
const stats = await stat7(filePath);
|
|
2557
3398
|
let agent = (typeof extraMetadata.agentHint === "string" ? extraMetadata.agentHint : "") || "unknown";
|
|
2558
3399
|
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
2559
3400
|
if (fmMatch) {
|
|
@@ -2581,7 +3422,7 @@ async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
|
2581
3422
|
}
|
|
2582
3423
|
async function scanUserPlans(into) {
|
|
2583
3424
|
const userPlansDir = getUserPlansDir();
|
|
2584
|
-
if (!
|
|
3425
|
+
if (!existsSync5(userPlansDir))
|
|
2585
3426
|
return;
|
|
2586
3427
|
const files = await walkDir(userPlansDir);
|
|
2587
3428
|
for (const file of files) {
|
|
@@ -2595,19 +3436,27 @@ async function scanUserPlans(into) {
|
|
|
2595
3436
|
function getCustomPlanDirs() {
|
|
2596
3437
|
return loadConfig()?.customPlanDirs ?? [];
|
|
2597
3438
|
}
|
|
3439
|
+
function canonicalPath(path) {
|
|
3440
|
+
const resolved = resolve4(path);
|
|
3441
|
+
try {
|
|
3442
|
+
return realpathSync(resolved);
|
|
3443
|
+
} catch {
|
|
3444
|
+
return resolved;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
2598
3447
|
function pathsOverlapFilesystemTree(a, b) {
|
|
2599
|
-
const ra =
|
|
2600
|
-
const rb =
|
|
3448
|
+
const ra = canonicalPath(a);
|
|
3449
|
+
const rb = canonicalPath(b);
|
|
2601
3450
|
if (ra === rb)
|
|
2602
3451
|
return true;
|
|
2603
|
-
if (ra.startsWith(rb +
|
|
3452
|
+
if (ra.startsWith(rb + sep3))
|
|
2604
3453
|
return true;
|
|
2605
|
-
if (rb.startsWith(ra +
|
|
3454
|
+
if (rb.startsWith(ra + sep3))
|
|
2606
3455
|
return true;
|
|
2607
3456
|
return false;
|
|
2608
3457
|
}
|
|
2609
3458
|
function overlapsAnyRoot(candidate, roots) {
|
|
2610
|
-
const resolvedCandidate =
|
|
3459
|
+
const resolvedCandidate = canonicalPath(candidate);
|
|
2611
3460
|
for (const root of roots) {
|
|
2612
3461
|
if (pathsOverlapFilesystemTree(resolvedCandidate, root))
|
|
2613
3462
|
return true;
|
|
@@ -2616,9 +3465,9 @@ function overlapsAnyRoot(candidate, roots) {
|
|
|
2616
3465
|
}
|
|
2617
3466
|
async function scanCustomPlanDirs(coveredPaths, into) {
|
|
2618
3467
|
const dirs = getCustomPlanDirs();
|
|
2619
|
-
const userPlansDir =
|
|
3468
|
+
const userPlansDir = canonicalPath(getUserPlansDir());
|
|
2620
3469
|
for (const dir of dirs) {
|
|
2621
|
-
const resolved =
|
|
3470
|
+
const resolved = canonicalPath(dir);
|
|
2622
3471
|
if (overlapsAnyRoot(resolved, coveredPaths)) {
|
|
2623
3472
|
console.log(`[agendex] skipping custom dir (overlaps adapter / discovered coverage): ${dir}`);
|
|
2624
3473
|
continue;
|
|
@@ -2627,7 +3476,7 @@ async function scanCustomPlanDirs(coveredPaths, into) {
|
|
|
2627
3476
|
console.log(`[agendex] skipping custom dir (overlaps user plans): ${dir}`);
|
|
2628
3477
|
continue;
|
|
2629
3478
|
}
|
|
2630
|
-
if (!
|
|
3479
|
+
if (!existsSync5(dir)) {
|
|
2631
3480
|
console.log(`[agendex] skipping custom dir (not found): ${dir}`);
|
|
2632
3481
|
continue;
|
|
2633
3482
|
}
|
|
@@ -2656,21 +3505,22 @@ async function scan() {
|
|
|
2656
3505
|
const coveredPaths = new Set;
|
|
2657
3506
|
for (const adapter of adapters) {
|
|
2658
3507
|
for (const searchPath of adapter.getSearchPaths()) {
|
|
2659
|
-
coveredPaths.add(
|
|
3508
|
+
coveredPaths.add(canonicalPath(searchPath));
|
|
2660
3509
|
const files = await walkDir(searchPath);
|
|
2661
3510
|
for (const file of files) {
|
|
2662
3511
|
if (!adapter.matches(file))
|
|
2663
3512
|
continue;
|
|
2664
3513
|
const plans = await adapter.parse(file);
|
|
2665
3514
|
for (const plan of plans) {
|
|
2666
|
-
|
|
3515
|
+
const annotated = annotatePlanValueMetadata(plan);
|
|
3516
|
+
next.set(annotated.id, annotated);
|
|
2667
3517
|
}
|
|
2668
3518
|
}
|
|
2669
3519
|
}
|
|
2670
3520
|
}
|
|
2671
3521
|
const discovered = discoverProjectPlanDirs();
|
|
2672
3522
|
for (const { dir, agent } of discovered) {
|
|
2673
|
-
const resolvedDir =
|
|
3523
|
+
const resolvedDir = canonicalPath(dir);
|
|
2674
3524
|
if (coveredPaths.has(resolvedDir))
|
|
2675
3525
|
continue;
|
|
2676
3526
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
@@ -2682,7 +3532,8 @@ async function scan() {
|
|
|
2682
3532
|
continue;
|
|
2683
3533
|
const plans = await adapter.parse(file);
|
|
2684
3534
|
for (const plan of plans) {
|
|
2685
|
-
|
|
3535
|
+
const annotated = annotatePlanValueMetadata(plan);
|
|
3536
|
+
next.set(annotated.id, annotated);
|
|
2686
3537
|
}
|
|
2687
3538
|
}
|
|
2688
3539
|
coveredPaths.add(resolvedDir);
|
|
@@ -2692,34 +3543,97 @@ async function scan() {
|
|
|
2692
3543
|
await scanCustomPlanDirs(coveredPaths, next);
|
|
2693
3544
|
store = next;
|
|
2694
3545
|
notifyPlansChanged();
|
|
2695
|
-
|
|
3546
|
+
const indexableCount = getIndexablePlans().length;
|
|
3547
|
+
const hiddenCount = store.size - indexableCount;
|
|
3548
|
+
const hiddenSuffix = hiddenCount > 0 ? ` (${hiddenCount} hidden as low-value)` : "";
|
|
3549
|
+
console.log(`[agendex] indexed ${indexableCount} plans${hiddenSuffix} from ${adapters.length} adapters`);
|
|
2696
3550
|
}
|
|
2697
3551
|
function getAll() {
|
|
2698
3552
|
return Array.from(store.values());
|
|
2699
3553
|
}
|
|
3554
|
+
function getIndexablePlans() {
|
|
3555
|
+
return getAll().filter(isIndexablePlan);
|
|
3556
|
+
}
|
|
3557
|
+
function getById(id) {
|
|
3558
|
+
return store.get(id);
|
|
3559
|
+
}
|
|
3560
|
+
function getPlanSourceAdapter(plan) {
|
|
3561
|
+
const sourceAdapter = plan.metadata.sourceAdapter;
|
|
3562
|
+
return typeof sourceAdapter === "string" && sourceAdapter.trim() ? sourceAdapter : undefined;
|
|
3563
|
+
}
|
|
3564
|
+
function findAdapterForPlan(plan) {
|
|
3565
|
+
const adapters = getActiveAdapters();
|
|
3566
|
+
const sourceAdapter = getPlanSourceAdapter(plan);
|
|
3567
|
+
return adapters.find((adapter) => adapter.agent === sourceAdapter) ?? adapters.find((adapter) => adapter.agent === plan.agent);
|
|
3568
|
+
}
|
|
3569
|
+
async function requestChanges(id, payload) {
|
|
3570
|
+
const plan = store.get(id);
|
|
3571
|
+
if (!plan)
|
|
3572
|
+
return false;
|
|
3573
|
+
const adapter = findAdapterForPlan(plan);
|
|
3574
|
+
if (!adapter?.requestChanges)
|
|
3575
|
+
return false;
|
|
3576
|
+
const ok = await adapter.requestChanges(plan, payload);
|
|
3577
|
+
if (ok) {
|
|
3578
|
+
const plannotator = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? { ...plan.metadata.plannotator, lastWritebackStatus: "sent", lastWritebackAt: Date.now() } : undefined;
|
|
3579
|
+
if (plannotator)
|
|
3580
|
+
plan.metadata = { ...plan.metadata, plannotator };
|
|
3581
|
+
plan.updatedAt = new Date;
|
|
3582
|
+
notifyPlansChanged();
|
|
3583
|
+
}
|
|
3584
|
+
return ok;
|
|
3585
|
+
}
|
|
3586
|
+
function planBelongsToAdapter(plan, adapter) {
|
|
3587
|
+
if (plan.agent === adapter.agent)
|
|
3588
|
+
return true;
|
|
3589
|
+
return adapter.agent === "plannotator" && typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null;
|
|
3590
|
+
}
|
|
3591
|
+
function removePlansForPath(filePath, adapter) {
|
|
3592
|
+
const normalized = resolve4(filePath);
|
|
3593
|
+
const removed = [];
|
|
3594
|
+
for (const [id, plan] of store.entries()) {
|
|
3595
|
+
const planPath = resolve4(plan.filePath);
|
|
3596
|
+
const sessionPath = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? plan.metadata.plannotator.sessionPath : undefined;
|
|
3597
|
+
if ((planPath === normalized || sessionPath === normalized) && (!adapter || planBelongsToAdapter(plan, adapter))) {
|
|
3598
|
+
removed.push(plan);
|
|
3599
|
+
store.delete(id);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (removed.length > 0)
|
|
3603
|
+
notifyPlansChanged();
|
|
3604
|
+
return removed;
|
|
3605
|
+
}
|
|
2700
3606
|
async function rescanFile(filePath) {
|
|
2701
3607
|
const adapters = getActiveAdapters();
|
|
2702
|
-
const normalized =
|
|
3608
|
+
const normalized = resolve4(filePath);
|
|
3609
|
+
const removedPlans = [];
|
|
2703
3610
|
for (const adapter of adapters) {
|
|
2704
3611
|
if (!adapter.matches(filePath))
|
|
2705
3612
|
continue;
|
|
2706
|
-
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) =>
|
|
3613
|
+
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve4(d2.dir));
|
|
2707
3614
|
const allSearchPaths = [
|
|
2708
|
-
...adapter.getSearchPaths().map((sp) =>
|
|
3615
|
+
...adapter.getSearchPaths().map((sp) => resolve4(sp)),
|
|
2709
3616
|
...discoveredDirs
|
|
2710
3617
|
];
|
|
2711
|
-
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp +
|
|
3618
|
+
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep3) || normalized === sp);
|
|
2712
3619
|
if (!isInSearchPath)
|
|
2713
3620
|
continue;
|
|
2714
|
-
const
|
|
3621
|
+
const rawPlans = await adapter.parse(filePath);
|
|
3622
|
+
if (rawPlans.length === 0) {
|
|
3623
|
+
removedPlans.push(...removePlansForPath(filePath, adapter));
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
const plans = rawPlans.map(annotatePlanValueMetadata);
|
|
2715
3627
|
for (const plan of plans) {
|
|
2716
3628
|
store.set(plan.id, plan);
|
|
2717
3629
|
}
|
|
2718
3630
|
notifyPlansChanged();
|
|
2719
3631
|
return plans;
|
|
2720
3632
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
3633
|
+
if (removedPlans.length > 0)
|
|
3634
|
+
return removedPlans;
|
|
3635
|
+
const userPlansDir = resolve4(getUserPlansDir());
|
|
3636
|
+
if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep3) || normalized === userPlansDir)) {
|
|
2723
3637
|
const plan = await parseGenericMarkdownPlan(filePath, { userCreated: true });
|
|
2724
3638
|
if (plan) {
|
|
2725
3639
|
store.set(plan.id, plan);
|
|
@@ -2730,8 +3644,8 @@ async function rescanFile(filePath) {
|
|
|
2730
3644
|
if (normalized.endsWith(".md")) {
|
|
2731
3645
|
const customDirs = getCustomPlanDirs();
|
|
2732
3646
|
for (const dir of customDirs) {
|
|
2733
|
-
const resolvedDir =
|
|
2734
|
-
if (normalized.startsWith(resolvedDir +
|
|
3647
|
+
const resolvedDir = resolve4(dir);
|
|
3648
|
+
if (normalized.startsWith(resolvedDir + sep3) || normalized === resolvedDir) {
|
|
2735
3649
|
const plan = await parseGenericMarkdownPlan(filePath, {
|
|
2736
3650
|
source: "custom-dir",
|
|
2737
3651
|
customDir: dir
|
|
@@ -2747,8 +3661,8 @@ async function rescanFile(filePath) {
|
|
|
2747
3661
|
return [];
|
|
2748
3662
|
}
|
|
2749
3663
|
// ../shared/src/services/watcher.ts
|
|
2750
|
-
import { existsSync as
|
|
2751
|
-
import { join as
|
|
3664
|
+
import { existsSync as existsSync6, watch } from "node:fs";
|
|
3665
|
+
import { join as join10, resolve as resolve5 } from "node:path";
|
|
2752
3666
|
var debounceTimer = null;
|
|
2753
3667
|
var pendingFiles = new Set;
|
|
2754
3668
|
var activeWatchers = [];
|
|
@@ -2766,13 +3680,13 @@ function closeAllWatchers() {
|
|
|
2766
3680
|
activeWatchers.length = 0;
|
|
2767
3681
|
}
|
|
2768
3682
|
function watchDir(dir, matchFn, onChange) {
|
|
2769
|
-
if (!
|
|
3683
|
+
if (!existsSync6(dir))
|
|
2770
3684
|
return;
|
|
2771
3685
|
try {
|
|
2772
3686
|
const watcher = watch(dir, { recursive: true }, async (_event, filename) => {
|
|
2773
3687
|
if (!filename)
|
|
2774
3688
|
return;
|
|
2775
|
-
const fullPath =
|
|
3689
|
+
const fullPath = join10(dir, filename);
|
|
2776
3690
|
if (!matchFn(fullPath))
|
|
2777
3691
|
return;
|
|
2778
3692
|
pendingFiles.add(fullPath);
|
|
@@ -2805,23 +3719,23 @@ function setupWatchers(onChange) {
|
|
|
2805
3719
|
const watchedPaths = new Set;
|
|
2806
3720
|
for (const adapter of adapters) {
|
|
2807
3721
|
for (const watchPath of adapter.getWatchPaths()) {
|
|
2808
|
-
watchedPaths.add(
|
|
3722
|
+
watchedPaths.add(resolve5(watchPath));
|
|
2809
3723
|
watchDir(watchPath, (f) => adapter.matches(f), onChange);
|
|
2810
3724
|
}
|
|
2811
3725
|
}
|
|
2812
3726
|
const discovered = discoverProjectPlanDirs();
|
|
2813
3727
|
for (const { dir, agent } of discovered) {
|
|
2814
|
-
if (watchedPaths.has(
|
|
3728
|
+
if (watchedPaths.has(resolve5(dir)))
|
|
2815
3729
|
continue;
|
|
2816
3730
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
2817
3731
|
if (!adapter)
|
|
2818
3732
|
continue;
|
|
2819
|
-
watchedPaths.add(
|
|
3733
|
+
watchedPaths.add(resolve5(dir));
|
|
2820
3734
|
watchDir(dir, (f) => adapter.matches(f), onChange);
|
|
2821
3735
|
}
|
|
2822
3736
|
const customDirs = getCustomPlanDirs();
|
|
2823
3737
|
for (const dir of customDirs) {
|
|
2824
|
-
const resolvedCustom =
|
|
3738
|
+
const resolvedCustom = resolve5(dir);
|
|
2825
3739
|
let overlaps = false;
|
|
2826
3740
|
for (const watched of watchedPaths) {
|
|
2827
3741
|
if (pathsOverlapFilesystemTree(resolvedCustom, watched)) {
|
|
@@ -2841,15 +3755,15 @@ import { request as httpsRequest } from "node:https";
|
|
|
2841
3755
|
import { hostname as osHostname } from "node:os";
|
|
2842
3756
|
|
|
2843
3757
|
// src/pid.ts
|
|
2844
|
-
import { existsSync as
|
|
3758
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2845
3759
|
import { hostname } from "node:os";
|
|
2846
|
-
import { dirname, join as
|
|
3760
|
+
import { dirname as dirname3, join as join11 } from "node:path";
|
|
2847
3761
|
function getPidPath() {
|
|
2848
|
-
return
|
|
3762
|
+
return join11(getConfigDir(), "daemon.pid");
|
|
2849
3763
|
}
|
|
2850
3764
|
function writePid() {
|
|
2851
3765
|
const path = getPidPath();
|
|
2852
|
-
mkdirSync2(
|
|
3766
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
2853
3767
|
const info = {
|
|
2854
3768
|
pid: process.pid,
|
|
2855
3769
|
startedAtMs: Date.now(),
|
|
@@ -2859,7 +3773,7 @@ function writePid() {
|
|
|
2859
3773
|
}
|
|
2860
3774
|
function readPidInfo() {
|
|
2861
3775
|
const path = getPidPath();
|
|
2862
|
-
if (!
|
|
3776
|
+
if (!existsSync7(path))
|
|
2863
3777
|
return null;
|
|
2864
3778
|
const raw = readFileSync4(path, "utf-8").trim();
|
|
2865
3779
|
const asNumber = Number(raw);
|
|
@@ -2909,6 +3823,22 @@ function getCloudConfig() {
|
|
|
2909
3823
|
convexUrl: config.convexUrl
|
|
2910
3824
|
};
|
|
2911
3825
|
}
|
|
3826
|
+
function parseSyncSuccess(body) {
|
|
3827
|
+
try {
|
|
3828
|
+
const parsed = JSON.parse(body);
|
|
3829
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3830
|
+
return { ok: true };
|
|
3831
|
+
}
|
|
3832
|
+
const result = parsed;
|
|
3833
|
+
return {
|
|
3834
|
+
ok: true,
|
|
3835
|
+
skippedLowValue: result.skippedLowValue === true,
|
|
3836
|
+
deleted: result.deleted === true
|
|
3837
|
+
};
|
|
3838
|
+
} catch {
|
|
3839
|
+
return { ok: true };
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
2912
3842
|
async function syncPlan(plan) {
|
|
2913
3843
|
const { token, convexUrl } = getCloudConfig();
|
|
2914
3844
|
const url = `${convexUrl}/api/cli/sync`;
|
|
@@ -2940,7 +3870,7 @@ async function syncPlan(plan) {
|
|
|
2940
3870
|
if (res.status < 200 || res.status >= 300) {
|
|
2941
3871
|
return { ok: false, error: `${res.status}: ${res.body}` };
|
|
2942
3872
|
}
|
|
2943
|
-
return
|
|
3873
|
+
return parseSyncSuccess(res.body);
|
|
2944
3874
|
}
|
|
2945
3875
|
async function refreshStoredToken(currentToken, convexUrl) {
|
|
2946
3876
|
const refreshed = await refreshToken(currentToken, convexUrl);
|
|
@@ -2995,7 +3925,7 @@ async function sendHeartbeat() {
|
|
|
2995
3925
|
}
|
|
2996
3926
|
async function sendShutdown() {
|
|
2997
3927
|
try {
|
|
2998
|
-
|
|
3928
|
+
getCloudConfig();
|
|
2999
3929
|
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
3000
3930
|
await deleteDaemons([cachedDeviceId]);
|
|
3001
3931
|
} catch {}
|
|
@@ -3023,7 +3953,7 @@ function requestText(urlString, options) {
|
|
|
3023
3953
|
if (options.body) {
|
|
3024
3954
|
headers["Content-Length"] = String(Buffer.byteLength(options.body));
|
|
3025
3955
|
}
|
|
3026
|
-
return new Promise((
|
|
3956
|
+
return new Promise((resolve6, reject) => {
|
|
3027
3957
|
const req = request(url, {
|
|
3028
3958
|
agent: false,
|
|
3029
3959
|
headers,
|
|
@@ -3035,7 +3965,7 @@ function requestText(urlString, options) {
|
|
|
3035
3965
|
body += chunk;
|
|
3036
3966
|
});
|
|
3037
3967
|
res.on("end", () => {
|
|
3038
|
-
|
|
3968
|
+
resolve6({
|
|
3039
3969
|
status: res.statusCode ?? 0,
|
|
3040
3970
|
body
|
|
3041
3971
|
});
|
|
@@ -3049,6 +3979,62 @@ function requestText(urlString, options) {
|
|
|
3049
3979
|
req.end();
|
|
3050
3980
|
});
|
|
3051
3981
|
}
|
|
3982
|
+
function authHeaders(token, contentType = false) {
|
|
3983
|
+
return {
|
|
3984
|
+
Authorization: `Bearer ${token}`,
|
|
3985
|
+
Connection: "close",
|
|
3986
|
+
...contentType && { "Content-Type": "application/json" }
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
async function fetchPlannotatorWritebacks(limit = 10) {
|
|
3990
|
+
const { token, convexUrl } = getCloudConfig();
|
|
3991
|
+
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
3992
|
+
const url = `${convexUrl}/api/cli/plannotator/writebacks?deviceId=${encodeURIComponent(cachedDeviceId)}&limit=${limit}`;
|
|
3993
|
+
let activeToken = token;
|
|
3994
|
+
let res = await requestText(url, {
|
|
3995
|
+
method: "GET",
|
|
3996
|
+
headers: authHeaders(activeToken)
|
|
3997
|
+
});
|
|
3998
|
+
if (res.status === 401) {
|
|
3999
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
4000
|
+
if (refreshed) {
|
|
4001
|
+
activeToken = refreshed;
|
|
4002
|
+
res = await requestText(url, {
|
|
4003
|
+
method: "GET",
|
|
4004
|
+
headers: authHeaders(activeToken)
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
if (res.status === 401)
|
|
4009
|
+
throw new AuthExpiredError;
|
|
4010
|
+
if (res.status < 200 || res.status >= 300)
|
|
4011
|
+
return [];
|
|
4012
|
+
const body = JSON.parse(res.body);
|
|
4013
|
+
return body.writebacks ?? [];
|
|
4014
|
+
}
|
|
4015
|
+
async function reportPlannotatorWriteback(writebackId, status, error) {
|
|
4016
|
+
const { token, convexUrl } = getCloudConfig();
|
|
4017
|
+
const url = `${convexUrl}/api/cli/plannotator/writebacks/report`;
|
|
4018
|
+
let activeToken = token;
|
|
4019
|
+
const body = JSON.stringify({ writebackId, status, error });
|
|
4020
|
+
let res = await requestText(url, {
|
|
4021
|
+
method: "POST",
|
|
4022
|
+
headers: authHeaders(activeToken, true),
|
|
4023
|
+
body
|
|
4024
|
+
});
|
|
4025
|
+
if (res.status === 401) {
|
|
4026
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
4027
|
+
if (refreshed) {
|
|
4028
|
+
activeToken = refreshed;
|
|
4029
|
+
res = await requestText(url, {
|
|
4030
|
+
method: "POST",
|
|
4031
|
+
headers: authHeaders(activeToken, true),
|
|
4032
|
+
body
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
return res.status >= 200 && res.status < 300;
|
|
4037
|
+
}
|
|
3052
4038
|
async function fetchDevices() {
|
|
3053
4039
|
const { token, convexUrl } = getCloudConfig();
|
|
3054
4040
|
const url = `${convexUrl}/api/cli/devices`;
|
|
@@ -3149,6 +4135,7 @@ async function login(siteUrlOverride) {
|
|
|
3149
4135
|
token: existing?.token,
|
|
3150
4136
|
cloudToken: callback.token,
|
|
3151
4137
|
convexUrl: callback.convexUrl,
|
|
4138
|
+
deviceId: existing?.deviceId,
|
|
3152
4139
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
3153
4140
|
customPlanDirs: existing?.customPlanDirs ?? []
|
|
3154
4141
|
};
|
|
@@ -3167,6 +4154,7 @@ function logout() {
|
|
|
3167
4154
|
token: existing.token,
|
|
3168
4155
|
cloudToken: undefined,
|
|
3169
4156
|
convexUrl: undefined,
|
|
4157
|
+
deviceId: existing.deviceId,
|
|
3170
4158
|
enabledAdapters: existing.enabledAdapters,
|
|
3171
4159
|
customPlanDirs: existing.customPlanDirs
|
|
3172
4160
|
};
|
|
@@ -3179,14 +4167,14 @@ async function startCallbackServer() {
|
|
|
3179
4167
|
let timeout;
|
|
3180
4168
|
let settle;
|
|
3181
4169
|
let fail;
|
|
3182
|
-
const result = new Promise((
|
|
3183
|
-
settle =
|
|
4170
|
+
const result = new Promise((resolve6, reject) => {
|
|
4171
|
+
settle = resolve6;
|
|
3184
4172
|
fail = reject;
|
|
3185
4173
|
});
|
|
3186
4174
|
const finish = (value) => {
|
|
3187
4175
|
if (!settle || !fail)
|
|
3188
4176
|
return;
|
|
3189
|
-
const
|
|
4177
|
+
const resolve6 = settle;
|
|
3190
4178
|
const reject = fail;
|
|
3191
4179
|
settle = undefined;
|
|
3192
4180
|
fail = undefined;
|
|
@@ -3203,7 +4191,7 @@ async function startCallbackServer() {
|
|
|
3203
4191
|
reject(value);
|
|
3204
4192
|
return;
|
|
3205
4193
|
}
|
|
3206
|
-
|
|
4194
|
+
resolve6(value);
|
|
3207
4195
|
};
|
|
3208
4196
|
server.on("request", (req, res) => {
|
|
3209
4197
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -3237,8 +4225,8 @@ async function startCallbackServer() {
|
|
|
3237
4225
|
sockets.delete(socket);
|
|
3238
4226
|
});
|
|
3239
4227
|
});
|
|
3240
|
-
await new Promise((
|
|
3241
|
-
server.listen(0, "127.0.0.1", () =>
|
|
4228
|
+
await new Promise((resolve6, reject) => {
|
|
4229
|
+
server.listen(0, "127.0.0.1", () => resolve6());
|
|
3242
4230
|
server.once("error", reject);
|
|
3243
4231
|
});
|
|
3244
4232
|
server.once("error", (error) => {
|
|
@@ -3318,19 +4306,77 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
3318
4306
|
|
|
3319
4307
|
// src/daemon.ts
|
|
3320
4308
|
import { spawn as spawn2 } from "node:child_process";
|
|
3321
|
-
import { resolve as
|
|
4309
|
+
import { resolve as resolve6 } from "node:path";
|
|
3322
4310
|
import { fileURLToPath } from "node:url";
|
|
3323
4311
|
|
|
4312
|
+
// src/adapters.ts
|
|
4313
|
+
var PLANNOTATOR_ADAPTER_ID = "plannotator";
|
|
4314
|
+
function envFlag(name) {
|
|
4315
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
4316
|
+
if (!value)
|
|
4317
|
+
return;
|
|
4318
|
+
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
|
4319
|
+
return true;
|
|
4320
|
+
if (value === "0" || value === "false" || value === "no" || value === "off")
|
|
4321
|
+
return false;
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
function shouldEnablePlannotatorSync(config) {
|
|
4325
|
+
const explicit = envFlag("AGENDEX_PLANNOTATOR_SYNC");
|
|
4326
|
+
if (explicit !== undefined)
|
|
4327
|
+
return explicit;
|
|
4328
|
+
return Boolean(config.cloudToken && config.convexUrl);
|
|
4329
|
+
}
|
|
4330
|
+
function resolveCliAdapterIds(config) {
|
|
4331
|
+
const ids = config.enabledAdapters.length > 0 ? [...config.enabledAdapters] : getDefaultAdapterIds();
|
|
4332
|
+
if (shouldEnablePlannotatorSync(config) && !ids.includes(PLANNOTATOR_ADAPTER_ID)) {
|
|
4333
|
+
ids.push(PLANNOTATOR_ADAPTER_ID);
|
|
4334
|
+
}
|
|
4335
|
+
return ids;
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
// src/payload.ts
|
|
4339
|
+
var SYNC_METADATA_KEY = "agendexSync";
|
|
4340
|
+
function isRecord4(value) {
|
|
4341
|
+
return typeof value === "object" && value !== null;
|
|
4342
|
+
}
|
|
4343
|
+
function withSyncDeviceMetadata(metadata, deviceId) {
|
|
4344
|
+
if (!deviceId)
|
|
4345
|
+
return metadata;
|
|
4346
|
+
const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
|
|
4347
|
+
return {
|
|
4348
|
+
...metadata,
|
|
4349
|
+
[SYNC_METADATA_KEY]: {
|
|
4350
|
+
...existing,
|
|
4351
|
+
deviceId
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4355
|
+
function planToSyncPayload(plan, deviceId) {
|
|
4356
|
+
return {
|
|
4357
|
+
localPlanId: plan.id,
|
|
4358
|
+
agent: plan.agent,
|
|
4359
|
+
title: plan.title,
|
|
4360
|
+
content: plan.content,
|
|
4361
|
+
format: plan.format,
|
|
4362
|
+
filePath: plan.filePath,
|
|
4363
|
+
workspace: plan.workspace,
|
|
4364
|
+
metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
|
|
4365
|
+
createdAt: plan.createdAt.getTime(),
|
|
4366
|
+
updatedAt: plan.updatedAt.getTime()
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
|
|
3324
4370
|
// src/sync-cache.ts
|
|
3325
4371
|
import { createHash as createHash2 } from "node:crypto";
|
|
3326
|
-
import { existsSync as
|
|
3327
|
-
import { join as
|
|
4372
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4373
|
+
import { join as join12 } from "node:path";
|
|
3328
4374
|
function getCachePath() {
|
|
3329
|
-
return
|
|
4375
|
+
return join12(getConfigDir(), "sync-cache.json");
|
|
3330
4376
|
}
|
|
3331
4377
|
function loadSyncCache() {
|
|
3332
4378
|
const cachePath = getCachePath();
|
|
3333
|
-
if (!
|
|
4379
|
+
if (!existsSync8(cachePath))
|
|
3334
4380
|
return {};
|
|
3335
4381
|
try {
|
|
3336
4382
|
const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
@@ -3343,7 +4389,7 @@ function loadSyncCache() {
|
|
|
3343
4389
|
}
|
|
3344
4390
|
function saveSyncCache(cache, options) {
|
|
3345
4391
|
const dir = getConfigDir();
|
|
3346
|
-
if (!
|
|
4392
|
+
if (!existsSync8(dir))
|
|
3347
4393
|
mkdirSync3(dir, { recursive: true });
|
|
3348
4394
|
const cachePath = getCachePath();
|
|
3349
4395
|
if (options?.replace) {
|
|
@@ -3369,32 +4415,71 @@ function computePayloadHash(payload) {
|
|
|
3369
4415
|
return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
3370
4416
|
}
|
|
3371
4417
|
|
|
4418
|
+
// src/writeback-delivery-cache.ts
|
|
4419
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4420
|
+
import { join as join13 } from "node:path";
|
|
4421
|
+
var MAX_PENDING_WRITEBACK_REPORTS = 1000;
|
|
4422
|
+
function getCachePath2() {
|
|
4423
|
+
return join13(getConfigDir(), "plannotator-writebacks-delivered.json");
|
|
4424
|
+
}
|
|
4425
|
+
function isPendingWritebackReportStatus(value) {
|
|
4426
|
+
return value === "sent" || value === "failed" || value === "expired";
|
|
4427
|
+
}
|
|
4428
|
+
function normalizeReports(input) {
|
|
4429
|
+
if (!Array.isArray(input))
|
|
4430
|
+
return [];
|
|
4431
|
+
const reports = new Map;
|
|
4432
|
+
for (const item of input) {
|
|
4433
|
+
if (typeof item === "string" && item.length > 0) {
|
|
4434
|
+
reports.set(item, "sent");
|
|
4435
|
+
continue;
|
|
4436
|
+
}
|
|
4437
|
+
if (item && typeof item === "object" && "id" in item && "status" in item && typeof item.id === "string" && item.id.length > 0 && isPendingWritebackReportStatus(item.status)) {
|
|
4438
|
+
reports.set(item.id, item.status);
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
return [...reports.entries()].slice(-MAX_PENDING_WRITEBACK_REPORTS);
|
|
4442
|
+
}
|
|
4443
|
+
function loadPendingWritebackReports() {
|
|
4444
|
+
const cachePath = getCachePath2();
|
|
4445
|
+
if (!existsSync9(cachePath))
|
|
4446
|
+
return new Map;
|
|
4447
|
+
try {
|
|
4448
|
+
return new Map(normalizeReports(JSON.parse(readFileSync6(cachePath, "utf-8"))));
|
|
4449
|
+
} catch {
|
|
4450
|
+
return new Map;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
function savePendingWritebackReports(reports) {
|
|
4454
|
+
try {
|
|
4455
|
+
const dir = getConfigDir();
|
|
4456
|
+
if (!existsSync9(dir))
|
|
4457
|
+
mkdirSync4(dir, { recursive: true });
|
|
4458
|
+
const normalizedReports = normalizeReports([...reports].map(([id, status]) => ({ id, status })));
|
|
4459
|
+
writeFileSync4(getCachePath2(), JSON.stringify(normalizedReports.map(([id, status]) => ({ id, status }))));
|
|
4460
|
+
return true;
|
|
4461
|
+
} catch {
|
|
4462
|
+
return false;
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
|
|
3372
4466
|
// src/daemon.ts
|
|
3373
4467
|
var MAX_RESTARTS = 5;
|
|
3374
4468
|
var RESTART_WINDOW_MS = 60000;
|
|
3375
4469
|
var RESTART_DELAY_MS = 5000;
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
agent: plan.agent,
|
|
3380
|
-
title: plan.title,
|
|
3381
|
-
content: plan.content,
|
|
3382
|
-
format: plan.format,
|
|
3383
|
-
filePath: plan.filePath,
|
|
3384
|
-
workspace: plan.workspace,
|
|
3385
|
-
metadata: plan.metadata,
|
|
3386
|
-
createdAt: plan.createdAt.getTime(),
|
|
3387
|
-
updatedAt: plan.updatedAt.getTime()
|
|
3388
|
-
};
|
|
3389
|
-
}
|
|
4470
|
+
var PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS = 15000;
|
|
4471
|
+
var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
4472
|
+
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
|
|
3390
4473
|
async function runWorker() {
|
|
3391
4474
|
const config = await loadOrInitConfig();
|
|
3392
|
-
const
|
|
4475
|
+
const adapterIds = resolveCliAdapterIds(config);
|
|
4476
|
+
const adapters = resolveAdapters(adapterIds);
|
|
3393
4477
|
setActiveAdapters(adapters);
|
|
3394
|
-
console.log(`[agendex] daemon starting with ${
|
|
4478
|
+
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
3395
4479
|
await sendHeartbeat();
|
|
3396
4480
|
const syncCache = loadSyncCache();
|
|
3397
4481
|
const syncQueue = [];
|
|
4482
|
+
const pendingWritebackReports = loadPendingWritebackReports();
|
|
3398
4483
|
let syncing = false;
|
|
3399
4484
|
async function tryRefreshToken() {
|
|
3400
4485
|
const cfg = loadConfig();
|
|
@@ -3413,6 +4498,8 @@ async function runWorker() {
|
|
|
3413
4498
|
syncing = true;
|
|
3414
4499
|
const batch = syncQueue.splice(0);
|
|
3415
4500
|
let syncedCount = 0;
|
|
4501
|
+
let lowValueSkippedCount = 0;
|
|
4502
|
+
let lowValueDeletedCount = 0;
|
|
3416
4503
|
let failedCount = 0;
|
|
3417
4504
|
try {
|
|
3418
4505
|
for (const payload of batch) {
|
|
@@ -3433,35 +4520,134 @@ async function runWorker() {
|
|
|
3433
4520
|
failedCount++;
|
|
3434
4521
|
console.error(`[agendex] sync failed for "${payload.title}": ${result.error}`);
|
|
3435
4522
|
} else {
|
|
3436
|
-
|
|
4523
|
+
if (result.skippedLowValue) {
|
|
4524
|
+
lowValueSkippedCount++;
|
|
4525
|
+
if (result.deleted)
|
|
4526
|
+
lowValueDeletedCount++;
|
|
4527
|
+
} else {
|
|
4528
|
+
syncedCount++;
|
|
4529
|
+
}
|
|
3437
4530
|
syncCache[payload.localPlanId] = computePayloadHash(payload);
|
|
3438
4531
|
}
|
|
3439
4532
|
}
|
|
3440
4533
|
} catch (err) {
|
|
3441
4534
|
console.error("[agendex] sync error:", err);
|
|
3442
|
-
syncQueue.unshift(...batch.slice(syncedCount + failedCount));
|
|
4535
|
+
syncQueue.unshift(...batch.slice(syncedCount + lowValueSkippedCount + failedCount));
|
|
3443
4536
|
} finally {
|
|
3444
4537
|
syncing = false;
|
|
3445
4538
|
}
|
|
3446
|
-
if (syncedCount > 0 || failedCount > 0) {
|
|
4539
|
+
if (syncedCount > 0 || lowValueSkippedCount > 0 || failedCount > 0) {
|
|
3447
4540
|
saveSyncCache(syncCache);
|
|
3448
|
-
|
|
4541
|
+
const lowValueSuffix2 = lowValueSkippedCount > 0 ? `, ${lowValueSkippedCount} low-value skipped/pruned${lowValueDeletedCount > 0 ? ` (${lowValueDeletedCount} deleted)` : ""}` : "";
|
|
4542
|
+
console.log(`[agendex] sync complete: ${syncedCount} synced${lowValueSuffix2}, ${failedCount} failed`);
|
|
3449
4543
|
}
|
|
3450
4544
|
if (syncQueue.length > 0)
|
|
3451
4545
|
processSyncQueue();
|
|
3452
4546
|
}
|
|
4547
|
+
function persistPendingWritebackReports() {
|
|
4548
|
+
if (!savePendingWritebackReports(pendingWritebackReports)) {
|
|
4549
|
+
console.error("[agendex] failed to persist Plannotator write-back delivery cache");
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
async function reportPendingWriteback(writebackId) {
|
|
4553
|
+
const status = pendingWritebackReports.get(writebackId);
|
|
4554
|
+
if (!status)
|
|
4555
|
+
return;
|
|
4556
|
+
const reported = await reportPlannotatorWriteback(writebackId, status, status === "expired" ? PLANNOTATOR_WRITEBACK_EXPIRED_ERROR : status === "failed" ? PLANNOTATOR_WRITEBACK_FAILED_ERROR : undefined);
|
|
4557
|
+
if (reported) {
|
|
4558
|
+
pendingWritebackReports.delete(writebackId);
|
|
4559
|
+
persistPendingWritebackReports();
|
|
4560
|
+
} else {
|
|
4561
|
+
console.error("[agendex] failed to report write-back status for", writebackId, "- will retry");
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
async function handlePlannotatorWriteback(job) {
|
|
4565
|
+
if (pendingWritebackReports.has(job._id)) {
|
|
4566
|
+
await reportPendingWriteback(job._id);
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
if (job.expiresAt <= Date.now()) {
|
|
4570
|
+
pendingWritebackReports.set(job._id, "expired");
|
|
4571
|
+
persistPendingWritebackReports();
|
|
4572
|
+
await reportPendingWriteback(job._id);
|
|
4573
|
+
return;
|
|
4574
|
+
}
|
|
4575
|
+
let localPlan = getById(job.localPlanId);
|
|
4576
|
+
if (!localPlan) {
|
|
4577
|
+
await scan();
|
|
4578
|
+
localPlan = getById(job.localPlanId);
|
|
4579
|
+
}
|
|
4580
|
+
if (!localPlan) {
|
|
4581
|
+
if (job.deviceId) {
|
|
4582
|
+
pendingWritebackReports.set(job._id, "failed");
|
|
4583
|
+
persistPendingWritebackReports();
|
|
4584
|
+
await reportPendingWriteback(job._id);
|
|
4585
|
+
}
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
const ok = await requestChanges(job.localPlanId, {
|
|
4589
|
+
feedback: job.feedback,
|
|
4590
|
+
revisedContent: job.revisedContent,
|
|
4591
|
+
annotations: job.annotations,
|
|
4592
|
+
source: job.source,
|
|
4593
|
+
writebackId: job._id,
|
|
4594
|
+
requestedAt: Date.now()
|
|
4595
|
+
});
|
|
4596
|
+
if (ok) {
|
|
4597
|
+
const updatedPlan = getById(job.localPlanId);
|
|
4598
|
+
if (updatedPlan)
|
|
4599
|
+
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
|
|
4600
|
+
pendingWritebackReports.set(job._id, "sent");
|
|
4601
|
+
persistPendingWritebackReports();
|
|
4602
|
+
await reportPendingWriteback(job._id);
|
|
4603
|
+
processSyncQueue();
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
pendingWritebackReports.set(job._id, "failed");
|
|
4607
|
+
persistPendingWritebackReports();
|
|
4608
|
+
await reportPendingWriteback(job._id);
|
|
4609
|
+
}
|
|
4610
|
+
let pollingWritebacks = false;
|
|
4611
|
+
async function pollPlannotatorWritebacks() {
|
|
4612
|
+
if (pollingWritebacks)
|
|
4613
|
+
return;
|
|
4614
|
+
pollingWritebacks = true;
|
|
4615
|
+
try {
|
|
4616
|
+
const jobs = await fetchPlannotatorWritebacks();
|
|
4617
|
+
for (const job of jobs) {
|
|
4618
|
+
await handlePlannotatorWriteback(job);
|
|
4619
|
+
}
|
|
4620
|
+
} catch (err) {
|
|
4621
|
+
if (err instanceof Error && err.name === "AuthExpiredError") {
|
|
4622
|
+
console.error("[agendex] session expired. Run `agendex login` to re-authenticate.");
|
|
4623
|
+
} else {
|
|
4624
|
+
console.error("[agendex] Plannotator write-back polling failed:", err);
|
|
4625
|
+
}
|
|
4626
|
+
} finally {
|
|
4627
|
+
pollingWritebacks = false;
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
3453
4630
|
setOnPlansChanged(() => {});
|
|
3454
4631
|
console.log(`[agendex] initial scan...`);
|
|
3455
4632
|
await scan();
|
|
3456
4633
|
const plans = getAll();
|
|
4634
|
+
const syncablePlanCount = plans.filter(isIndexablePlan).length;
|
|
4635
|
+
const lowValuePlanCount = plans.length - syncablePlanCount;
|
|
3457
4636
|
let initialSkipped = 0;
|
|
4637
|
+
let initialQueuedSyncable = 0;
|
|
4638
|
+
let initialQueuedLowValue = 0;
|
|
3458
4639
|
for (const plan of plans) {
|
|
3459
|
-
const payload =
|
|
4640
|
+
const payload = planToSyncPayload(plan, config.deviceId);
|
|
3460
4641
|
const hash = computePayloadHash(payload);
|
|
3461
4642
|
if (syncCache[plan.id] === hash) {
|
|
3462
4643
|
initialSkipped++;
|
|
3463
4644
|
continue;
|
|
3464
4645
|
}
|
|
4646
|
+
if (isLowValuePlan(plan)) {
|
|
4647
|
+
initialQueuedLowValue++;
|
|
4648
|
+
} else {
|
|
4649
|
+
initialQueuedSyncable++;
|
|
4650
|
+
}
|
|
3465
4651
|
syncQueue.push(payload);
|
|
3466
4652
|
}
|
|
3467
4653
|
const activePlanIds = new Set(plans.map((plan) => plan.id));
|
|
@@ -3470,12 +4656,17 @@ async function runWorker() {
|
|
|
3470
4656
|
delete syncCache[id];
|
|
3471
4657
|
}
|
|
3472
4658
|
saveSyncCache(syncCache, { replace: true });
|
|
3473
|
-
|
|
4659
|
+
const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
|
|
4660
|
+
console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
|
|
3474
4661
|
await processSyncQueue();
|
|
3475
4662
|
setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
4663
|
+
if (shouldEnablePlannotatorSync(config)) {
|
|
4664
|
+
setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
|
|
4665
|
+
pollPlannotatorWritebacks();
|
|
4666
|
+
}
|
|
3476
4667
|
startWatching((changedPlans) => {
|
|
3477
4668
|
for (const plan of changedPlans) {
|
|
3478
|
-
syncQueue.push(
|
|
4669
|
+
syncQueue.push(planToSyncPayload(plan, config.deviceId));
|
|
3479
4670
|
}
|
|
3480
4671
|
processSyncQueue();
|
|
3481
4672
|
});
|
|
@@ -3505,17 +4696,17 @@ async function startSupervisor() {
|
|
|
3505
4696
|
};
|
|
3506
4697
|
process.on("SIGTERM", shutdown);
|
|
3507
4698
|
process.on("SIGINT", shutdown);
|
|
3508
|
-
const scriptPath =
|
|
4699
|
+
const scriptPath = resolve6(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
3509
4700
|
const restartTimes = [];
|
|
3510
4701
|
while (!stopping) {
|
|
3511
4702
|
workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
|
|
3512
4703
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3513
4704
|
});
|
|
3514
|
-
const exitCode = await new Promise((
|
|
3515
|
-
workerProc?.once("exit", (code) =>
|
|
4705
|
+
const exitCode = await new Promise((resolve7) => {
|
|
4706
|
+
workerProc?.once("exit", (code) => resolve7(code));
|
|
3516
4707
|
workerProc?.once("error", (error) => {
|
|
3517
4708
|
console.error("[agendex] failed to spawn worker:", error);
|
|
3518
|
-
|
|
4709
|
+
resolve7(1);
|
|
3519
4710
|
});
|
|
3520
4711
|
});
|
|
3521
4712
|
workerProc = null;
|
|
@@ -3540,31 +4731,26 @@ async function startSupervisor() {
|
|
|
3540
4731
|
// src/sync.ts
|
|
3541
4732
|
async function syncAll(force = false) {
|
|
3542
4733
|
const config = await loadOrInitConfig();
|
|
3543
|
-
const
|
|
4734
|
+
const adapterIds = resolveCliAdapterIds(config);
|
|
4735
|
+
const adapters = resolveAdapters(adapterIds);
|
|
3544
4736
|
setActiveAdapters(adapters);
|
|
3545
4737
|
console.log(`[agendex] Scanning local plans...`);
|
|
3546
4738
|
await scan();
|
|
3547
|
-
const
|
|
3548
|
-
|
|
4739
|
+
const allPlans = getAll();
|
|
4740
|
+
const syncablePlans = allPlans.filter(isIndexablePlan);
|
|
4741
|
+
const lowValuePlans = allPlans.filter(isLowValuePlan);
|
|
4742
|
+
const hiddenSuffix = lowValuePlans.length > 0 ? ` (${lowValuePlans.length} low-value hidden/pruned)` : "";
|
|
4743
|
+
console.log(`[agendex] Found ${syncablePlans.length} syncable plans${hiddenSuffix}. Syncing to cloud...`);
|
|
3549
4744
|
const cache = force ? {} : loadSyncCache();
|
|
3550
4745
|
const activePlanIds = new Set;
|
|
3551
4746
|
let synced = 0;
|
|
4747
|
+
let lowValueSkipped = 0;
|
|
4748
|
+
let lowValueDeleted = 0;
|
|
3552
4749
|
let skipped = 0;
|
|
3553
4750
|
let failed = 0;
|
|
3554
|
-
for (const plan of
|
|
4751
|
+
for (const plan of [...syncablePlans, ...lowValuePlans]) {
|
|
3555
4752
|
activePlanIds.add(plan.id);
|
|
3556
|
-
const payload =
|
|
3557
|
-
localPlanId: plan.id,
|
|
3558
|
-
agent: plan.agent,
|
|
3559
|
-
title: plan.title,
|
|
3560
|
-
content: plan.content,
|
|
3561
|
-
format: plan.format,
|
|
3562
|
-
filePath: plan.filePath,
|
|
3563
|
-
workspace: plan.workspace,
|
|
3564
|
-
metadata: plan.metadata,
|
|
3565
|
-
createdAt: plan.createdAt.getTime(),
|
|
3566
|
-
updatedAt: plan.updatedAt.getTime()
|
|
3567
|
-
};
|
|
4753
|
+
const payload = planToSyncPayload(plan, config.deviceId);
|
|
3568
4754
|
const hash = computePayloadHash(payload);
|
|
3569
4755
|
if (!force && cache[plan.id] === hash) {
|
|
3570
4756
|
skipped++;
|
|
@@ -3572,7 +4758,13 @@ async function syncAll(force = false) {
|
|
|
3572
4758
|
}
|
|
3573
4759
|
const result = await syncPlan(payload);
|
|
3574
4760
|
if (result.ok) {
|
|
3575
|
-
|
|
4761
|
+
if (result.skippedLowValue) {
|
|
4762
|
+
lowValueSkipped++;
|
|
4763
|
+
if (result.deleted)
|
|
4764
|
+
lowValueDeleted++;
|
|
4765
|
+
} else {
|
|
4766
|
+
synced++;
|
|
4767
|
+
}
|
|
3576
4768
|
cache[plan.id] = hash;
|
|
3577
4769
|
} else {
|
|
3578
4770
|
failed++;
|
|
@@ -3584,17 +4776,18 @@ async function syncAll(force = false) {
|
|
|
3584
4776
|
delete cache[id];
|
|
3585
4777
|
}
|
|
3586
4778
|
saveSyncCache(cache, { replace: true });
|
|
3587
|
-
|
|
4779
|
+
const lowValueSuffix = lowValueSkipped > 0 ? `, ${lowValueSkipped} low-value skipped/pruned${lowValueDeleted > 0 ? ` (${lowValueDeleted} deleted)` : ""}` : "";
|
|
4780
|
+
console.log(`[agendex] Sync complete: ${synced} synced${lowValueSuffix}, ${skipped} unchanged, ${failed} failed`);
|
|
3588
4781
|
}
|
|
3589
4782
|
|
|
3590
4783
|
// src/version.ts
|
|
3591
|
-
import { existsSync as
|
|
4784
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3592
4785
|
import { tmpdir } from "node:os";
|
|
3593
|
-
import { join as
|
|
4786
|
+
import { join as join14 } from "node:path";
|
|
3594
4787
|
// package.json
|
|
3595
4788
|
var package_default = {
|
|
3596
4789
|
name: "agendex-cli",
|
|
3597
|
-
version: "0.
|
|
4790
|
+
version: "0.15.0",
|
|
3598
4791
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3599
4792
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3600
4793
|
repository: {
|
|
@@ -3638,14 +4831,14 @@ var package_default = {
|
|
|
3638
4831
|
|
|
3639
4832
|
// src/version.ts
|
|
3640
4833
|
var CLI_VERSION = package_default.version;
|
|
3641
|
-
var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ??
|
|
4834
|
+
var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join14(tmpdir(), ".agendex-update-cache.json");
|
|
3642
4835
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
3643
4836
|
var UPDATE_URL = process.env.AGENDEX_UPDATE_URL ?? "https://registry.npmjs.org/agendex-cli/latest";
|
|
3644
4837
|
function readCache(current) {
|
|
3645
4838
|
try {
|
|
3646
|
-
if (!
|
|
4839
|
+
if (!existsSync10(CACHE_FILE))
|
|
3647
4840
|
return null;
|
|
3648
|
-
const { result, ts } = JSON.parse(
|
|
4841
|
+
const { result, ts } = JSON.parse(readFileSync7(CACHE_FILE, "utf8"));
|
|
3649
4842
|
if (Date.now() - ts > CACHE_TTL_MS)
|
|
3650
4843
|
return null;
|
|
3651
4844
|
return normalizeResult(result, current);
|
|
@@ -3655,7 +4848,7 @@ function readCache(current) {
|
|
|
3655
4848
|
}
|
|
3656
4849
|
function writeCache(result) {
|
|
3657
4850
|
try {
|
|
3658
|
-
|
|
4851
|
+
writeFileSync5(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
|
|
3659
4852
|
} catch {}
|
|
3660
4853
|
}
|
|
3661
4854
|
async function checkForUpdate() {
|
|
@@ -3741,7 +4934,7 @@ function firstCommandToken(argv) {
|
|
|
3741
4934
|
return;
|
|
3742
4935
|
}
|
|
3743
4936
|
var command = firstCommandToken(args) ?? "start";
|
|
3744
|
-
var cliEntry =
|
|
4937
|
+
var cliEntry = resolve7(process.argv[1] ?? fileURLToPath2(import.meta.url));
|
|
3745
4938
|
async function main() {
|
|
3746
4939
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
3747
4940
|
if (command === "--version" || command === "-v") {
|
|
@@ -3941,7 +5134,7 @@ async function main() {
|
|
|
3941
5134
|
return 1;
|
|
3942
5135
|
}
|
|
3943
5136
|
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
3944
|
-
if (!
|
|
5137
|
+
if (!existsSync11(resolved)) {
|
|
3945
5138
|
writeStderr(`[agendex] path does not exist: ${resolved}`);
|
|
3946
5139
|
return 1;
|
|
3947
5140
|
}
|
|
@@ -3960,7 +5153,7 @@ async function main() {
|
|
|
3960
5153
|
const { request } = await import("node:http");
|
|
3961
5154
|
const body = JSON.stringify({ path: resolved });
|
|
3962
5155
|
try {
|
|
3963
|
-
const res = await new Promise((
|
|
5156
|
+
const res = await new Promise((resolve8, reject) => {
|
|
3964
5157
|
const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
|
|
3965
5158
|
method: "POST",
|
|
3966
5159
|
headers: {
|
|
@@ -3974,7 +5167,7 @@ async function main() {
|
|
|
3974
5167
|
res2.on("data", (chunk) => {
|
|
3975
5168
|
data += chunk;
|
|
3976
5169
|
});
|
|
3977
|
-
res2.on("end", () =>
|
|
5170
|
+
res2.on("end", () => resolve8({ status: res2.statusCode ?? 0, body: data }));
|
|
3978
5171
|
res2.on("error", reject);
|
|
3979
5172
|
});
|
|
3980
5173
|
req.on("error", reject);
|
|
@@ -4156,13 +5349,13 @@ function flushStream(stream) {
|
|
|
4156
5349
|
if (stream.destroyed || !stream.writable) {
|
|
4157
5350
|
return Promise.resolve();
|
|
4158
5351
|
}
|
|
4159
|
-
return new Promise((
|
|
5352
|
+
return new Promise((resolve8, reject) => {
|
|
4160
5353
|
stream.write("", (error) => {
|
|
4161
5354
|
if (error) {
|
|
4162
5355
|
reject(error);
|
|
4163
5356
|
return;
|
|
4164
5357
|
}
|
|
4165
|
-
|
|
5358
|
+
resolve8();
|
|
4166
5359
|
});
|
|
4167
5360
|
});
|
|
4168
5361
|
}
|