@agimon-ai/po-file-edit 0.0.0-alpha.3 → 0.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +60 -88
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +1 -1
- package/dist/extensions/server.mjs +62 -90
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +36 -36
- package/dist/extensions/web.mjs.map +1 -1
- package/package.json +18 -18
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ tree comparison. See [What this cannot see](#what-this-cannot-see) for gaps in t
|
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
|
-
`po init`
|
|
21
|
+
`po init` include File Edit in `default.packages` in `.po/modes.yaml`.
|
|
22
22
|
Remove it or move it to a named layer to change which major modes load it.
|
|
23
23
|
|
|
24
24
|
| Entry | Purpose |
|
|
@@ -62,7 +62,7 @@ and keeps it, content-addressed, beside the session timeline; both go when the s
|
|
|
62
62
|
|
|
63
63
|
The timeline contains file paths, and the snapshot store holds copies of the file content the
|
|
64
64
|
session changed. That content can be sensitive. Both live under the Pi agent directory,
|
|
65
|
-
`$PI_CODING_AGENT_DIR` or `~/.po/agent/
|
|
65
|
+
`$PI_CODING_AGENT_DIR` or `~/.po/agent/po-file-edit`, never inside the working tree, and should be
|
|
66
66
|
protected with the same access controls as Pi session data.
|
|
67
67
|
|
|
68
68
|
A parent session removes its own timeline and snapshots on final disposal. Child sessions share the parent timeline and do not clear it. Activity selection stops retain review data for reactivation. A session that is killed cannot, so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.mjs","names":["channelFileEdits"],"sources":["../../src/types/webFiles.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesModel.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesStore.native.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.native.ts","../../generated/native.ts"],"sourcesContent":["import type { FileEditTool } from './domain';\n\n/**\n * View types shared by this package's hub channel and its web plugin. Wire\n * JSON only, no node types, so the cockpit bundle can carry them; the\n * cockpit receives them as 'file_edits' channel payloads.\n *\n * The channel carries the list alone. One file's diffs and content are large\n * and only wanted once a reader opens the file, so those come from the session\n * API instead (see fileEditsApi.ts).\n */\n\n/** One file this session changed, as the activity dock lists it. */\nexport interface FilesItemView {\n /** Absolute path, and the identity every API route takes back. */\n path: string;\n /** Path relative to the session's working directory, which is what a row shows. */\n relPath: string;\n /** The tool behind the most recent change. */\n tool: FileEditTool;\n /** When that change happened, epoch milliseconds. */\n at: number;\n /** How many changes this session recorded for the file. */\n count: number;\n /**\n * Whether any change captured a baseline, so a diff can be shown at all. A\n * file found by comparing the tree around a bash call has no \"before\", so it\n * is listed and readable but never diffed.\n */\n diffable: boolean;\n}\n\n/** The frame type the hub publishes and the web plugin claims; declared in the poWeb block too. */\nexport const filesChannelType = 'file_edits';\n\n/** Footer status key whose presence shows the activity group; the extension publishes it. */\nexport const filesStatusKey = '
|
|
1
|
+
{"version":3,"file":"native.mjs","names":["channelFileEdits"],"sources":["../../src/types/webFiles.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesModel.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesStore.native.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.native.ts","../../generated/native.ts"],"sourcesContent":["import type { FileEditTool } from './domain';\n\n/**\n * View types shared by this package's hub channel and its web plugin. Wire\n * JSON only, no node types, so the cockpit bundle can carry them; the\n * cockpit receives them as 'file_edits' channel payloads.\n *\n * The channel carries the list alone. One file's diffs and content are large\n * and only wanted once a reader opens the file, so those come from the session\n * API instead (see fileEditsApi.ts).\n */\n\n/** One file this session changed, as the activity dock lists it. */\nexport interface FilesItemView {\n /** Absolute path, and the identity every API route takes back. */\n path: string;\n /** Path relative to the session's working directory, which is what a row shows. */\n relPath: string;\n /** The tool behind the most recent change. */\n tool: FileEditTool;\n /** When that change happened, epoch milliseconds. */\n at: number;\n /** How many changes this session recorded for the file. */\n count: number;\n /**\n * Whether any change captured a baseline, so a diff can be shown at all. A\n * file found by comparing the tree around a bash call has no \"before\", so it\n * is listed and readable but never diffed.\n */\n diffable: boolean;\n}\n\n/** The frame type the hub publishes and the web plugin claims; declared in the poWeb block too. */\nexport const filesChannelType = 'file_edits';\n\n/** Footer status key whose presence shows the activity group; the extension publishes it. */\nexport const filesStatusKey = 'po-file-edit-files';\n","import type { FilesItemView } from '../../../../../types/webFiles';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isItem(value: unknown): value is FilesItemView {\n return (\n isRecord(value) &&\n typeof value.path === 'string' &&\n typeof value.relPath === 'string' &&\n typeof value.tool === 'string' &&\n typeof value.at === 'number' &&\n typeof value.count === 'number'\n );\n}\n\nexport interface FilesPayload {\n items: FilesItemView[];\n}\n\n/** The shared wire gate for browser and native stores. */\nexport function parseFilesPayload(input: unknown): FilesPayload | null {\n if (!isRecord(input) || !Array.isArray(input.items)) return null;\n return { items: input.items.filter(isItem) };\n}\n","import { defineSessionStore } from '@agimon-ai/po-core/native';\nimport type { ReviewComment } from '@agimon-ai/po-web-components';\n\nimport type { FileEditsDetailView } from '../../../../../types/fileEditsApi';\nimport { filesChannelType, type FilesItemView } from '../../../../../types/webFiles';\nimport { parseFilesPayload, type FilesPayload } from './filesModel';\nexport type { FilesPayload } from './filesModel';\n\n/**\n * One session's record: the hub's last report plus whatever this page does with\n * it. Records are immutable values; the reducer and every action return a new one.\n *\n * Detail is cached per file rather than per open tab, because a reader closes a\n * file and comes back to it, and refetching the same history for the same click\n * is work the session already did.\n */\nexport interface FilesSession {\n items: FilesItemView[];\n /** One file's fetched history, keyed by absolute path. */\n detail: Readonly<Record<string, FileEditsDetailView>>;\n /** Paths with a detail request in flight, so a panel can say it is loading. */\n loading: readonly string[];\n /** What a failed fetch or a refused save reported, keyed by absolute path. */\n errors: Readonly<Record<string, string>>;\n /** Review notes waiting to be sent, in the order they were written. */\n comments: readonly ReviewComment[];\n}\n\nconst empty: FilesSession = { items: [], detail: {}, loading: [], errors: {}, comments: [] };\n\nexport const files = defineSessionStore<FilesSession>(empty);\n\n/** The plugin's session data channel: 'file_edits' payloads into the store. */\nexport const filesChannel = files.channel<FilesPayload>({\n channel: filesChannelType,\n parse: parseFilesPayload,\n // Only the list is replaced. The cached detail and the pending notes belong\n // to files a reader opened, not to the list: a file the session deletes stops\n // being listed, and pruning here would blank the tab still open on it and\n // then refetch it on the next frame.\n reduce: (current, { items }) => ({ ...current, items }),\n});\n\nexport function markLoading(sessionId: string, filePath: string): void {\n files.update(sessionId, (current) =>\n current.loading.includes(filePath)\n ? current\n : { ...current, loading: [...current.loading, filePath], errors: withoutKey(current.errors, filePath) },\n );\n}\n\nexport function storeDetail(sessionId: string, detail: FileEditsDetailView): void {\n files.update(sessionId, (current) => ({\n ...current,\n detail: { ...current.detail, [detail.path]: detail },\n loading: current.loading.filter((path) => path !== detail.path),\n errors: withoutKey(current.errors, detail.path),\n }));\n}\n\nexport function storeError(sessionId: string, filePath: string, message: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n loading: current.loading.filter((path) => path !== filePath),\n errors: { ...current.errors, [filePath]: message },\n }));\n}\n\nexport function addComment(sessionId: string, comment: ReviewComment): void {\n files.update(sessionId, (current) => ({ ...current, comments: [...current.comments, comment] }));\n}\n\nexport function removeComment(sessionId: string, id: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n comments: current.comments.filter((comment) => comment.id !== id),\n }));\n}\n\nexport function clearComments(sessionId: string, filePath: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n comments: current.comments.filter((comment) => comment.path !== filePath),\n }));\n}\n\nfunction withoutKey(source: Readonly<Record<string, string>>, key: string): Record<string, string> {\n return Object.fromEntries(Object.entries(source).filter(([entry]) => entry !== key));\n}\n","import { defineChannelFile } from '@agimon-ai/po-core/native';\n\nimport { filesChannel } from '../_lib/filesStore.native';\nexport default defineChannelFile(filesChannel);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineNativePlugin, type WebPluginContributions } from '@agimon-ai/po-core/native';\n\nimport channelFileEdits from '../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.native';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const nativePlugin = defineNativePlugin({\n id: 'files',\n session: {\n get channels(): WebPluginContributions['channels'] { return [via({ channel: 'file_edits' }, channelFileEdits)]; },\n },\n});\n"],"mappings":";;;AAiCA,MAAa,mBAAmB;;;AC/BhC,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,OAAO,OAAwC;CACtD,OACE,SAAS,KAAK,KACd,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,YAAY,YACzB,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,UAAU;AAE3B;;AAOA,SAAgB,kBAAkB,OAAqC;CACrE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG,OAAO;CAC5D,OAAO,EAAE,OAAO,MAAM,MAAM,OAAO,MAAM,EAAE;AAC7C;;ACQA,MAAa,eAHQ,mBAAiC;CAFxB,OAAO,CAAC;CAAG,QAAQ,CAAC;CAAG,SAAS,CAAC;CAAG,QAAQ,CAAC;CAAG,UAAU,CAAC;AAEnC,CAG1B,CAAA,CAAM,QAAsB;CACtD,SAAS;CACT,OAAO;CAKP,SAAS,SAAS,EAAE,aAAa;EAAE,GAAG;EAAS;CAAM;AACvD,CAAC;;;ACtCD,IAAA,4BAAe,kBAAkB,YAAY;;;ACE7C,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAa,eAAe,mBAAmB;CAC7C,IAAI;CACJ,SAAS,EACP,IAAI,WAA+C;EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,aAAa,GAAGA,yBAAgB,CAAC;CAAG,EAClH;AACF,CAAC"}
|
|
@@ -38,12 +38,12 @@ let node_fs = require("node:fs");
|
|
|
38
38
|
node_fs = __toESM(node_fs, 1);
|
|
39
39
|
let _agimon_ai_po_core_childProcess = require("@agimon-ai/po-core/childProcess");
|
|
40
40
|
let _agimon_ai_po_telemetry = require("@agimon-ai/po-telemetry");
|
|
41
|
-
let ignore = require("ignore");
|
|
42
|
-
ignore = __toESM(ignore, 1);
|
|
43
|
-
let node_child_process = require("node:child_process");
|
|
44
41
|
let node_crypto = require("node:crypto");
|
|
45
42
|
let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
|
|
43
|
+
let node_child_process = require("node:child_process");
|
|
46
44
|
let node_util = require("node:util");
|
|
45
|
+
let ignore = require("ignore");
|
|
46
|
+
ignore = __toESM(ignore, 1);
|
|
47
47
|
let node_os = require("node:os");
|
|
48
48
|
node_os = __toESM(node_os, 1);
|
|
49
49
|
let _agimon_ai_po_config = require("@agimon-ai/po-config");
|
|
@@ -231,41 +231,6 @@ function createSessionPresence(options) {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
//#endregion
|
|
234
|
-
//#region src/services/doomIgnore/index.ts
|
|
235
|
-
function matchablePath(relPath) {
|
|
236
|
-
const normalized = relPath.replace(/\\/gu, "/");
|
|
237
|
-
if (normalized.length === 0 || normalized.includes("\0") || normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/") || /^[A-Za-z]:\//u.test(normalized)) return;
|
|
238
|
-
return normalized;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Builds the project's ignore test, or nothing when there are no rules.
|
|
242
|
-
*
|
|
243
|
-
* Shared because the same rules are needed twice: once when deciding what to
|
|
244
|
-
* record, and once when deciding what to list. A caller that built its own
|
|
245
|
-
* matcher would let the two drift apart.
|
|
246
|
-
*
|
|
247
|
-
* The test answers false for anything it cannot safely match, so a path that
|
|
248
|
-
* escapes the project stays visible rather than being hidden by accident.
|
|
249
|
-
*/
|
|
250
|
-
function createDoomIgnoreMatcher(content) {
|
|
251
|
-
try {
|
|
252
|
-
if (content.trim().length === 0) return void 0;
|
|
253
|
-
const matcher = (0, ignore.default)().add(content);
|
|
254
|
-
return (relPath) => {
|
|
255
|
-
const candidate = matchablePath(relPath);
|
|
256
|
-
return candidate !== void 0 && matcher.ignores(candidate);
|
|
257
|
-
};
|
|
258
|
-
} catch {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/** Applies project-local gitignore rules while keeping unsafe candidates visible. */
|
|
263
|
-
function filterDoomIgnoredFiles(items, content) {
|
|
264
|
-
const ignored = createDoomIgnoreMatcher(content);
|
|
265
|
-
if (ignored === void 0) return items;
|
|
266
|
-
return items.filter((item) => !ignored(item.relPath));
|
|
267
|
-
}
|
|
268
|
-
//#endregion
|
|
269
234
|
//#region src/services/lineDiff/index.ts
|
|
270
235
|
/**
|
|
271
236
|
* The line difference between two versions of a file, as bounded hunks.
|
|
@@ -713,7 +678,7 @@ var EditTracker = class {
|
|
|
713
678
|
//#endregion
|
|
714
679
|
//#region src/services/fileEditPaths/index.ts
|
|
715
680
|
/** The directory this package owns, under whichever root holds agent state. */
|
|
716
|
-
const STATE_DIRECTORY = "
|
|
681
|
+
const STATE_DIRECTORY = "po-file-edit";
|
|
717
682
|
function hash(value) {
|
|
718
683
|
return (0, node_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 16);
|
|
719
684
|
}
|
|
@@ -731,7 +696,7 @@ var FileEditPaths = class {
|
|
|
731
696
|
}
|
|
732
697
|
/**
|
|
733
698
|
* Where every session's state lives: the Pi agent directory, the same root
|
|
734
|
-
* every other
|
|
699
|
+
* every other Po package writes to, and never inside the working tree.
|
|
735
700
|
*
|
|
736
701
|
* An earlier layout put this in the repository's git common directory. That
|
|
737
702
|
* is git's own storage: nothing in git's lifecycle prunes a directory we add
|
|
@@ -761,31 +726,6 @@ var FileEditPaths = class {
|
|
|
761
726
|
snapshotsPath(cwd, sessionKey) {
|
|
762
727
|
return `${this.timelinePath(cwd, sessionKey).replace(/\.jsonl$/u, "")}.blobs`;
|
|
763
728
|
}
|
|
764
|
-
legacyStateDirectory(cwd) {
|
|
765
|
-
const common = this.gitCommonDirectory(cwd);
|
|
766
|
-
return common === void 0 ? void 0 : node_path.default.join(common, STATE_DIRECTORY);
|
|
767
|
-
}
|
|
768
|
-
/** Only still read so the state an older build left behind can be cleared away. */
|
|
769
|
-
gitCommonDirectory(cwd) {
|
|
770
|
-
try {
|
|
771
|
-
return (0, node_child_process.execFileSync)("git", [
|
|
772
|
-
"rev-parse",
|
|
773
|
-
"--path-format=absolute",
|
|
774
|
-
"--git-common-dir"
|
|
775
|
-
], {
|
|
776
|
-
cwd,
|
|
777
|
-
encoding: "utf8",
|
|
778
|
-
stdio: [
|
|
779
|
-
"ignore",
|
|
780
|
-
"pipe",
|
|
781
|
-
"ignore"
|
|
782
|
-
]
|
|
783
|
-
}).trim() || void 0;
|
|
784
|
-
} catch (error) {
|
|
785
|
-
if (error instanceof Error) return void 0;
|
|
786
|
-
throw error;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
729
|
};
|
|
790
730
|
//#endregion
|
|
791
731
|
//#region src/services/gitStatus/index.ts
|
|
@@ -873,6 +813,41 @@ var NodeGitStatusAdapter = class {
|
|
|
873
813
|
}
|
|
874
814
|
}
|
|
875
815
|
};
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/services/poIgnore/index.ts
|
|
818
|
+
function matchablePath(relPath) {
|
|
819
|
+
const normalized = relPath.replace(/\\/gu, "/");
|
|
820
|
+
if (normalized.length === 0 || normalized.includes("\0") || normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/") || /^[A-Za-z]:\//u.test(normalized)) return;
|
|
821
|
+
return normalized;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Builds the project's ignore test, or nothing when there are no rules.
|
|
825
|
+
*
|
|
826
|
+
* Shared because the same rules are needed twice: once when deciding what to
|
|
827
|
+
* record, and once when deciding what to list. A caller that built its own
|
|
828
|
+
* matcher would let the two drift apart.
|
|
829
|
+
*
|
|
830
|
+
* The test answers false for anything it cannot safely match, so a path that
|
|
831
|
+
* escapes the project stays visible rather than being hidden by accident.
|
|
832
|
+
*/
|
|
833
|
+
function createPoIgnoreMatcher(content) {
|
|
834
|
+
try {
|
|
835
|
+
if (content.trim().length === 0) return void 0;
|
|
836
|
+
const matcher = (0, ignore.default)().add(content);
|
|
837
|
+
return (relPath) => {
|
|
838
|
+
const candidate = matchablePath(relPath);
|
|
839
|
+
return candidate !== void 0 && matcher.ignores(candidate);
|
|
840
|
+
};
|
|
841
|
+
} catch {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/** Applies project-local gitignore rules while keeping unsafe candidates visible. */
|
|
846
|
+
function filterPoIgnoredFiles(items, content) {
|
|
847
|
+
const ignored = createPoIgnoreMatcher(content);
|
|
848
|
+
if (ignored === void 0) return items;
|
|
849
|
+
return items.filter((item) => !ignored(item.relPath));
|
|
850
|
+
}
|
|
876
851
|
/** How much of a file is read before deciding it is binary. */
|
|
877
852
|
const BINARY_SAMPLE_BYTES$1 = 8192;
|
|
878
853
|
const MISSING_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1302,7 +1277,7 @@ var NodeTreeManifestAdapter = class {
|
|
|
1302
1277
|
/** The frame type the hub publishes and the web plugin claims; declared in the poWeb block too. */
|
|
1303
1278
|
const filesChannelType = "file_edits";
|
|
1304
1279
|
/** Footer status key whose presence shows the activity group; the extension publishes it. */
|
|
1305
|
-
const filesStatusKey = "
|
|
1280
|
+
const filesStatusKey = "po-file-edit-files";
|
|
1306
1281
|
//#endregion
|
|
1307
1282
|
//#region src/types/apiRoutes.ts
|
|
1308
1283
|
/**
|
|
@@ -1700,7 +1675,7 @@ const api = {
|
|
|
1700
1675
|
function plansRoots(cwd, environment) {
|
|
1701
1676
|
const home = environment.HOME ?? node_os.default.homedir();
|
|
1702
1677
|
try {
|
|
1703
|
-
const configured = (0, _agimon_ai_po_config.
|
|
1678
|
+
const configured = (0, _agimon_ai_po_config.loadPoConfig)(cwd, home, environment).modes?.planning?.plansDirectory;
|
|
1704
1679
|
return [(0, _agimon_ai_po_config.resolvePlanningPlansDirectory)(configured, cwd, home)];
|
|
1705
1680
|
} catch {
|
|
1706
1681
|
return [];
|
|
@@ -1827,7 +1802,7 @@ function readSessionFiles(timelinePath, cwd) {
|
|
|
1827
1802
|
diffable: isDiffable(foldVersions(events, entry.path))
|
|
1828
1803
|
}));
|
|
1829
1804
|
try {
|
|
1830
|
-
return
|
|
1805
|
+
return filterPoIgnoredFiles(items, node_fs.default.readFileSync(node_path.default.join(cwd, ".poignore"), "utf8"));
|
|
1831
1806
|
} catch {
|
|
1832
1807
|
return items;
|
|
1833
1808
|
}
|
|
@@ -1883,10 +1858,10 @@ function createFilesChannel() {
|
|
|
1883
1858
|
}
|
|
1884
1859
|
//#endregion
|
|
1885
1860
|
//#region src/services/fileEditSession/index.ts
|
|
1886
|
-
const
|
|
1887
|
-
function
|
|
1861
|
+
const PO_IGNORE_FILE = ".poignore";
|
|
1862
|
+
function readPoIgnore(cwd) {
|
|
1888
1863
|
try {
|
|
1889
|
-
const matcher =
|
|
1864
|
+
const matcher = createPoIgnoreMatcher(node_fs.default.readFileSync(node_path.default.join(cwd, PO_IGNORE_FILE), "utf8"));
|
|
1890
1865
|
return matcher === void 0 ? void 0 : (filePath) => matcher(node_path.default.relative(cwd, filePath));
|
|
1891
1866
|
} catch {
|
|
1892
1867
|
return;
|
|
@@ -1905,8 +1880,8 @@ function createFileEditSession({ agent, host }) {
|
|
|
1905
1880
|
const editTracker = new EditTracker(timeline, snapshots, new NodeTreeManifestAdapter(), { git: new NodeGitStatusAdapter() });
|
|
1906
1881
|
if (host.context.directEvents === void 0) throw new Error("File-edit headless facet requires the session direct event bus.");
|
|
1907
1882
|
const directEvents = host.context.directEvents;
|
|
1908
|
-
const telemetry = (0, _agimon_ai_po_telemetry.
|
|
1909
|
-
serviceName: "
|
|
1883
|
+
const telemetry = (0, _agimon_ai_po_telemetry.createPoTelemetry)({
|
|
1884
|
+
serviceName: "po-file-edit",
|
|
1910
1885
|
packageName: "@agimon-ai/po-file-edit",
|
|
1911
1886
|
env: environment,
|
|
1912
1887
|
enableLogs: true,
|
|
@@ -1963,7 +1938,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
1963
1938
|
await timeline.clear();
|
|
1964
1939
|
await snapshots.clear();
|
|
1965
1940
|
}
|
|
1966
|
-
await telemetry.recordEvent("
|
|
1941
|
+
await telemetry.recordEvent("po_file_edit.timeline_finished", { outcome: "closed" });
|
|
1967
1942
|
}
|
|
1968
1943
|
} finally {
|
|
1969
1944
|
await telemetry.shutdown();
|
|
@@ -1986,7 +1961,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
1986
1961
|
const snapshotsPath = paths.snapshotsPath(executionContext.cwd, sessionKey);
|
|
1987
1962
|
timeline.initialize(timelinePath);
|
|
1988
1963
|
snapshots.initialize(snapshotsPath);
|
|
1989
|
-
const isIgnored =
|
|
1964
|
+
const isIgnored = readPoIgnore(executionContext.cwd);
|
|
1990
1965
|
editTracker.reset({
|
|
1991
1966
|
exclude: [
|
|
1992
1967
|
timelinePath,
|
|
@@ -2009,16 +1984,11 @@ function createFileEditSession({ agent, host }) {
|
|
|
2009
1984
|
directory: paths.stateDirectory(),
|
|
2010
1985
|
keep: timelinePath
|
|
2011
1986
|
});
|
|
2012
|
-
const legacy = paths.legacyStateDirectory(executionContext.cwd);
|
|
2013
|
-
if (legacy !== void 0 && legacy !== paths.stateDirectory()) await sweepSessionState({
|
|
2014
|
-
directory: legacy,
|
|
2015
|
-
removeDirectory: true
|
|
2016
|
-
});
|
|
2017
1987
|
} catch {}
|
|
2018
1988
|
});
|
|
2019
1989
|
if (!initialized) {
|
|
2020
1990
|
initialized = true;
|
|
2021
|
-
await track(() => telemetry.recordEvent("
|
|
1991
|
+
await track(() => telemetry.recordEvent("po_file_edit.timeline_started", { outcome: "initialized" }));
|
|
2022
1992
|
}
|
|
2023
1993
|
return () => active === runtime ? stopActivity() : Promise.resolve();
|
|
2024
1994
|
}
|
|
@@ -2035,7 +2005,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
2035
2005
|
const hookStartedAt = Date.now();
|
|
2036
2006
|
await editTracker.start(toolCallId, toolName, event.args, executionContext.cwd);
|
|
2037
2007
|
if (active !== runtime) return;
|
|
2038
|
-
await telemetry.recordEvent("
|
|
2008
|
+
await telemetry.recordEvent("po_file_edit.edit_started", {
|
|
2039
2009
|
"tool.name": toolName,
|
|
2040
2010
|
hook_ms: Date.now() - hookStartedAt
|
|
2041
2011
|
});
|
|
@@ -2056,7 +2026,7 @@ function createFileEditSession({ agent, host }) {
|
|
|
2056
2026
|
await editTracker.end(toolCallId, isError, executionContext.cwd);
|
|
2057
2027
|
if (active !== runtime) return;
|
|
2058
2028
|
publish(runtime);
|
|
2059
|
-
await telemetry.recordEvent("
|
|
2029
|
+
await telemetry.recordEvent("po_file_edit.edit_finished", {
|
|
2060
2030
|
"tool.name": toolName,
|
|
2061
2031
|
"tool.result.error": isError,
|
|
2062
2032
|
...startedAt === void 0 ? {} : { duration_ms: Date.now() - startedAt },
|
|
@@ -2081,7 +2051,7 @@ function createPresence({ agent, host, context }) {
|
|
|
2081
2051
|
cwd: execution.cwd,
|
|
2082
2052
|
workflowStep: execution.sessionContext?.provenance === _agimon_ai_po_core_hubChannel.WORKFLOW_STEP_SESSION_PROVENANCE,
|
|
2083
2053
|
isLive: () => host.context.sessionService?.isLive(execution.sessionId) ?? true,
|
|
2084
|
-
labelFor: (runId) => (0, _agimon_ai_po_core_childSession.
|
|
2054
|
+
labelFor: (runId) => (0, _agimon_ai_po_core_childSession.readPoChildSessionLabels)(context)?.labelFor(runId),
|
|
2085
2055
|
onError: (error) => host.context.onNotice(`Edit presence failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
2086
2056
|
});
|
|
2087
2057
|
}
|
|
@@ -2094,10 +2064,12 @@ const createFileEditServerRoot = (context) => {
|
|
|
2094
2064
|
...presence === void 0 ? {} : { presence }
|
|
2095
2065
|
},
|
|
2096
2066
|
activities: runtime.activities,
|
|
2097
|
-
...presence === void 0 ? {} : { services: [(cordis) =>
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2067
|
+
...presence === void 0 ? {} : { services: [(cordis) => {
|
|
2068
|
+
cordis.inject([_agimon_ai_po_core_childSession.PO_CHILD_SESSION_HOOK_REGISTRY_SERVICE], (registryContext) => {
|
|
2069
|
+
const registration = registryContext.get(_agimon_ai_po_core_childSession.PO_CHILD_SESSION_HOOK_REGISTRY_SERVICE).register(presence.childHooks);
|
|
2070
|
+
return () => registration.dispose();
|
|
2071
|
+
});
|
|
2072
|
+
}] },
|
|
2101
2073
|
onDispose: async (pluginContext) => {
|
|
2102
2074
|
presence?.dispose();
|
|
2103
2075
|
await runtime.onDispose?.(pluginContext);
|