@agimon-ai/po-file-edit 0.0.0-alpha.44 → 0.0.0-alpha.46
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.
|
@@ -34,9 +34,8 @@ let node_path = require("node:path");
|
|
|
34
34
|
node_path = __toESM(node_path, 1);
|
|
35
35
|
let _agimon_ai_po_hashline = require("@agimon-ai/po-hashline");
|
|
36
36
|
let _agimon_ai_po_hashline_files = require("@agimon-ai/po-hashline/files");
|
|
37
|
-
let node_fs = require("node:fs");
|
|
38
|
-
node_fs = __toESM(node_fs, 1);
|
|
39
37
|
let _agimon_ai_po_core_childProcess = require("@agimon-ai/po-core/childProcess");
|
|
38
|
+
let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
|
|
40
39
|
let _agimon_ai_po_core_telemetry = require("@agimon-ai/po-core/telemetry");
|
|
41
40
|
let node_crypto = require("node:crypto");
|
|
42
41
|
let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
|
|
@@ -47,7 +46,6 @@ ignore = __toESM(ignore, 1);
|
|
|
47
46
|
let node_os = require("node:os");
|
|
48
47
|
node_os = __toESM(node_os, 1);
|
|
49
48
|
let _agimon_ai_po_config = require("@agimon-ai/po-config");
|
|
50
|
-
let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
|
|
51
49
|
let hono = require("hono");
|
|
52
50
|
//#region src/constants/editPresence.ts
|
|
53
51
|
const PRESENCE_NOTE_TITLE = "[Po: other active agents edited files in this result]";
|
|
@@ -717,17 +715,17 @@ var FileEditPaths = class {
|
|
|
717
715
|
* the hub and the session API can find it from another process knowing only
|
|
718
716
|
* those two things.
|
|
719
717
|
*/
|
|
720
|
-
timelinePath(cwd, sessionKey) {
|
|
718
|
+
async timelinePath(cwd, sessionKey) {
|
|
721
719
|
const directory = this.stateDirectory();
|
|
722
|
-
|
|
723
|
-
return node_path.default.join(directory, `${hash(
|
|
720
|
+
await node_fs_promises.default.mkdir(directory, { recursive: true });
|
|
721
|
+
return node_path.default.join(directory, `${hash(await node_fs_promises.default.realpath(cwd))}-${hash(sessionKey)}.jsonl`);
|
|
724
722
|
}
|
|
725
723
|
/**
|
|
726
724
|
* Where this session's content snapshots live: beside its timeline and named
|
|
727
725
|
* after it, so the two are found together and cleared together.
|
|
728
726
|
*/
|
|
729
|
-
snapshotsPath(cwd, sessionKey) {
|
|
730
|
-
return `${this.timelinePath(cwd, sessionKey).replace(/\.jsonl$/u, "")}.blobs`;
|
|
727
|
+
async snapshotsPath(cwd, sessionKey) {
|
|
728
|
+
return `${(await this.timelinePath(cwd, sessionKey)).replace(/\.jsonl$/u, "")}.blobs`;
|
|
731
729
|
}
|
|
732
730
|
};
|
|
733
731
|
//#endregion
|
|
@@ -1576,8 +1574,15 @@ function createFileEditsApi(options = {}) {
|
|
|
1576
1574
|
const app = new hono.Hono();
|
|
1577
1575
|
if (options.sessionId !== void 0 && cwd !== "") {
|
|
1578
1576
|
const sessionKey = paths.sessionKey(options.sessionId);
|
|
1579
|
-
|
|
1580
|
-
|
|
1577
|
+
let initializing;
|
|
1578
|
+
app.use(async (_context, next) => {
|
|
1579
|
+
initializing ??= (async () => {
|
|
1580
|
+
if (options.timeline === void 0) timeline.initialize(await paths.timelinePath(cwd, sessionKey));
|
|
1581
|
+
if (options.snapshots === void 0) snapshots.initialize(await paths.snapshotsPath(cwd, sessionKey));
|
|
1582
|
+
})();
|
|
1583
|
+
await initializing;
|
|
1584
|
+
await next();
|
|
1585
|
+
});
|
|
1581
1586
|
}
|
|
1582
1587
|
/**
|
|
1583
1588
|
* The file a request names, rejected unless this session actually recorded a
|
|
@@ -1834,9 +1839,9 @@ async function sweepSessionState(options) {
|
|
|
1834
1839
|
* startup.
|
|
1835
1840
|
*/
|
|
1836
1841
|
/** Whether the file is still there to be opened. */
|
|
1837
|
-
function stillExists(filePath) {
|
|
1842
|
+
async function stillExists(filePath) {
|
|
1838
1843
|
try {
|
|
1839
|
-
return
|
|
1844
|
+
return (await node_fs_promises.default.stat(filePath)).isFile();
|
|
1840
1845
|
} catch {
|
|
1841
1846
|
return false;
|
|
1842
1847
|
}
|
|
@@ -1851,15 +1856,17 @@ function stillExists(filePath) {
|
|
|
1851
1856
|
* is gone" is a dead end; the timeline still holds the change, so a tab already
|
|
1852
1857
|
* open on the file keeps working and says so.
|
|
1853
1858
|
*/
|
|
1854
|
-
function readSessionFiles(timelinePath, cwd) {
|
|
1859
|
+
async function readSessionFiles(timelinePath, cwd) {
|
|
1855
1860
|
let content;
|
|
1856
1861
|
try {
|
|
1857
|
-
content =
|
|
1862
|
+
content = await node_fs_promises.default.readFile(timelinePath, "utf8");
|
|
1858
1863
|
} catch {
|
|
1859
1864
|
return [];
|
|
1860
1865
|
}
|
|
1861
1866
|
const events = confirmedChanges(parseTimeline(content));
|
|
1862
|
-
const
|
|
1867
|
+
const entries = foldEntries(events);
|
|
1868
|
+
const existing = await Promise.all(entries.map((entry) => stillExists(entry.path)));
|
|
1869
|
+
const items = entries.filter((_entry, index) => existing[index]).map((entry) => ({
|
|
1863
1870
|
path: entry.path,
|
|
1864
1871
|
relPath: node_path.default.relative(cwd, entry.path) || entry.path,
|
|
1865
1872
|
tool: entry.tool,
|
|
@@ -1868,7 +1875,7 @@ function readSessionFiles(timelinePath, cwd) {
|
|
|
1868
1875
|
diffable: isDiffable(foldVersions(events, entry.path))
|
|
1869
1876
|
}));
|
|
1870
1877
|
try {
|
|
1871
|
-
return filterPoIgnoredFiles(items,
|
|
1878
|
+
return filterPoIgnoredFiles(items, await node_fs_promises.default.readFile(node_path.default.join(cwd, ".poignore"), "utf8"));
|
|
1872
1879
|
} catch {
|
|
1873
1880
|
return items;
|
|
1874
1881
|
}
|
|
@@ -1894,19 +1901,27 @@ function createFilesChannel() {
|
|
|
1894
1901
|
sessionAdded(scope) {
|
|
1895
1902
|
subscriptions.get(scope.sessionId)?.();
|
|
1896
1903
|
const paths = new FileEditPaths(scope.environment);
|
|
1897
|
-
let
|
|
1898
|
-
|
|
1899
|
-
timelinePath = paths.timelinePath(scope.cwd, paths.sessionKey(scope.sessionId));
|
|
1900
|
-
} catch {}
|
|
1901
|
-
const items = timelinePath === void 0 ? [] : readSessionFiles(timelinePath, scope.cwd);
|
|
1902
|
-
latest.set(scope.sessionId, items);
|
|
1903
|
-
host.publish(scope.sessionId, { items });
|
|
1904
|
+
let current = true;
|
|
1905
|
+
let receivedDirect = false;
|
|
1904
1906
|
const unsubscribe = host.directEvents.subscribe(filesChannelType, scope.sessionId, (payload) => {
|
|
1905
1907
|
if (!filesPayload(payload)) return;
|
|
1908
|
+
receivedDirect = true;
|
|
1906
1909
|
latest.set(scope.sessionId, payload.items);
|
|
1907
1910
|
host.publish(scope.sessionId, payload);
|
|
1908
1911
|
}, { replayLatest: true });
|
|
1909
|
-
subscriptions.set(scope.sessionId,
|
|
1912
|
+
subscriptions.set(scope.sessionId, () => {
|
|
1913
|
+
current = false;
|
|
1914
|
+
unsubscribe();
|
|
1915
|
+
});
|
|
1916
|
+
(async () => {
|
|
1917
|
+
let items = [];
|
|
1918
|
+
try {
|
|
1919
|
+
items = await readSessionFiles(await paths.timelinePath(scope.cwd, paths.sessionKey(scope.sessionId)), scope.cwd);
|
|
1920
|
+
} catch {}
|
|
1921
|
+
if (!current || receivedDirect) return;
|
|
1922
|
+
latest.set(scope.sessionId, items);
|
|
1923
|
+
host.publish(scope.sessionId, { items });
|
|
1924
|
+
})().catch((error) => host.onNotice(`File-edit channel seed failed: ${String(error)}`));
|
|
1910
1925
|
},
|
|
1911
1926
|
sessionRemoved(sessionId) {
|
|
1912
1927
|
subscriptions.get(sessionId)?.();
|
|
@@ -1925,9 +1940,11 @@ function createFilesChannel() {
|
|
|
1925
1940
|
//#endregion
|
|
1926
1941
|
//#region src/services/fileEditSession/index.ts
|
|
1927
1942
|
const PO_IGNORE_FILE = ".poignore";
|
|
1928
|
-
function readPoIgnore(cwd) {
|
|
1943
|
+
async function readPoIgnore(cwd, context) {
|
|
1929
1944
|
try {
|
|
1930
|
-
const
|
|
1945
|
+
const content = await (0, _agimon_ai_po_core_platform.readTextIfExists)((0, _agimon_ai_po_core_platform.requirePoFs)(context), node_path.default.join(cwd, PO_IGNORE_FILE), _agimon_ai_po_core_platform.BACKGROUND_CONTEXT);
|
|
1946
|
+
if (content === void 0) return void 0;
|
|
1947
|
+
const matcher = createPoIgnoreMatcher(content);
|
|
1931
1948
|
return matcher === void 0 ? void 0 : (filePath) => matcher(node_path.default.relative(cwd, filePath));
|
|
1932
1949
|
} catch {
|
|
1933
1950
|
return;
|
|
@@ -1937,7 +1954,7 @@ function stringValue(value, key) {
|
|
|
1937
1954
|
const candidate = value[key];
|
|
1938
1955
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1939
1956
|
}
|
|
1940
|
-
function createFileEditSession({ agent, host }) {
|
|
1957
|
+
function createFileEditSession({ agent, host, context }) {
|
|
1941
1958
|
if (!agent) return { api: [api] };
|
|
1942
1959
|
const environment = Object.freeze({ ...host.context.environment });
|
|
1943
1960
|
const paths = new FileEditPaths(environment);
|
|
@@ -1958,6 +1975,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
1958
1975
|
let closed = false;
|
|
1959
1976
|
let disposal;
|
|
1960
1977
|
let stopping;
|
|
1978
|
+
let starting;
|
|
1961
1979
|
let initialized = false;
|
|
1962
1980
|
const track = (work) => {
|
|
1963
1981
|
const task = work();
|
|
@@ -1975,8 +1993,9 @@ function createFileEditSession({ agent, host }) {
|
|
|
1975
1993
|
* string is deliberate: the group declares `hideWhenEmpty`, so a session that
|
|
1976
1994
|
* has edited nothing publishes a key with no content and stays hidden.
|
|
1977
1995
|
*/
|
|
1978
|
-
const publish = (runtime) => {
|
|
1979
|
-
const items = readSessionFiles(runtime.timelinePath, runtime.context.cwd);
|
|
1996
|
+
const publish = async (runtime) => {
|
|
1997
|
+
const items = await readSessionFiles(runtime.timelinePath, runtime.context.cwd);
|
|
1998
|
+
if (active !== runtime || closed || stopping) return;
|
|
1980
1999
|
directEvents.publish(filesChannelType, runtime.context.sessionId, { items });
|
|
1981
2000
|
runtime.context.client.setStatus(filesStatusKey, items.length === 0 ? "" : `${items.length} ${items.length === 1 ? "file" : "files"}`);
|
|
1982
2001
|
};
|
|
@@ -1985,11 +2004,13 @@ function createFileEditSession({ agent, host }) {
|
|
|
1985
2004
|
const runtime = active;
|
|
1986
2005
|
active = void 0;
|
|
1987
2006
|
stopping = (async () => {
|
|
1988
|
-
await Promise.allSettled(pending);
|
|
2007
|
+
while (pending.size > 0) await Promise.allSettled(pending);
|
|
2008
|
+
const startedRuntime = active;
|
|
2009
|
+
active = void 0;
|
|
1989
2010
|
await editTracker.flush();
|
|
1990
2011
|
editTracker.reset();
|
|
1991
2012
|
toolStartedAt.clear();
|
|
1992
|
-
runtime?.context.client.setStatus(filesStatusKey, void 0);
|
|
2013
|
+
(runtime ?? startedRuntime)?.context.client.setStatus(filesStatusKey, void 0);
|
|
1993
2014
|
})();
|
|
1994
2015
|
return stopping;
|
|
1995
2016
|
};
|
|
@@ -2017,50 +2038,60 @@ function createFileEditSession({ agent, host }) {
|
|
|
2017
2038
|
name: "file-edits",
|
|
2018
2039
|
start: async (executionContext) => {
|
|
2019
2040
|
if (closed) throw new Error("File-edit session is closed.");
|
|
2041
|
+
if (starting) return starting;
|
|
2020
2042
|
if (active) return stopActivity;
|
|
2021
2043
|
if (stopping) await stopping;
|
|
2022
2044
|
if (closed) throw new Error("File-edit session is closed.");
|
|
2045
|
+
if (starting) return starting;
|
|
2023
2046
|
stopping = void 0;
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2047
|
+
starting = track(async () => {
|
|
2048
|
+
const sessionKey = paths.sessionKey(executionContext.sessionId);
|
|
2049
|
+
const timelinePath = await paths.timelinePath(executionContext.cwd, sessionKey);
|
|
2050
|
+
const snapshotsPath = await paths.snapshotsPath(executionContext.cwd, sessionKey);
|
|
2051
|
+
timeline.initialize(timelinePath);
|
|
2052
|
+
snapshots.initialize(snapshotsPath);
|
|
2053
|
+
const isIgnored = await readPoIgnore(executionContext.cwd, context);
|
|
2054
|
+
editTracker.reset({
|
|
2055
|
+
exclude: [
|
|
2056
|
+
timelinePath,
|
|
2057
|
+
`${timelinePath}.lock`,
|
|
2058
|
+
snapshotsPath
|
|
2059
|
+
],
|
|
2060
|
+
...isIgnored === void 0 ? {} : { isIgnored }
|
|
2061
|
+
});
|
|
2062
|
+
const runtime = {
|
|
2063
|
+
context: executionContext,
|
|
2032
2064
|
timelinePath,
|
|
2033
|
-
`${timelinePath}.lock`,
|
|
2034
2065
|
snapshotsPath
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
} catch {}
|
|
2066
|
+
};
|
|
2067
|
+
active = runtime;
|
|
2068
|
+
lastRuntime = runtime;
|
|
2069
|
+
await publish(runtime);
|
|
2070
|
+
track(async () => {
|
|
2071
|
+
try {
|
|
2072
|
+
await sweepSessionState({
|
|
2073
|
+
directory: paths.stateDirectory(),
|
|
2074
|
+
keep: timelinePath
|
|
2075
|
+
});
|
|
2076
|
+
} catch {}
|
|
2077
|
+
});
|
|
2078
|
+
if (!initialized) {
|
|
2079
|
+
initialized = true;
|
|
2080
|
+
await track(() => telemetry.recordEvent("po_file_edit.timeline_started", { outcome: "initialized" }));
|
|
2081
|
+
}
|
|
2082
|
+
return () => active === runtime ? stopActivity() : Promise.resolve();
|
|
2053
2083
|
});
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2084
|
+
try {
|
|
2085
|
+
return await starting;
|
|
2086
|
+
} finally {
|
|
2087
|
+
starting = void 0;
|
|
2057
2088
|
}
|
|
2058
|
-
return () => active === runtime ? stopActivity() : Promise.resolve();
|
|
2059
2089
|
}
|
|
2060
2090
|
}],
|
|
2061
2091
|
hooks: [{
|
|
2062
2092
|
event: "tool_execution_start",
|
|
2063
2093
|
handle: (event, executionContext) => {
|
|
2094
|
+
if (closed || stopping) return;
|
|
2064
2095
|
const runtime = active;
|
|
2065
2096
|
const toolCallId = stringValue(event, "toolCallId");
|
|
2066
2097
|
const toolName = stringValue(event, "toolName");
|
|
@@ -2079,6 +2110,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
2079
2110
|
}, {
|
|
2080
2111
|
event: "tool_execution_end",
|
|
2081
2112
|
handle: (event, executionContext) => {
|
|
2113
|
+
if (closed || stopping) return;
|
|
2082
2114
|
const runtime = active;
|
|
2083
2115
|
const toolCallId = stringValue(event, "toolCallId");
|
|
2084
2116
|
const toolName = stringValue(event, "toolName");
|
|
@@ -2090,7 +2122,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
2090
2122
|
const isError = event.isError === true;
|
|
2091
2123
|
await editTracker.end(toolCallId, isError, executionContext.cwd);
|
|
2092
2124
|
if (active !== runtime) return;
|
|
2093
|
-
publish(runtime);
|
|
2125
|
+
await publish(runtime);
|
|
2094
2126
|
await telemetry.recordEvent("po_file_edit.edit_finished", {
|
|
2095
2127
|
"tool.name": toolName,
|
|
2096
2128
|
"tool.result.error": isError,
|