@agimon-ai/po-file-edit 0.0.0-alpha.1 → 0.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apiContracts.cjs +1 -1
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.mjs +1 -1
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/extensions/native.mjs +51 -0
- package/dist/extensions/native.mjs.map +1 -0
- package/dist/extensions/server.cjs +272 -20
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +301 -50
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +42 -360
- package/dist/extensions/web.mjs.map +1 -1
- package/package.json +45 -16
package/dist/apiContracts.cjs
CHANGED
|
@@ -141,7 +141,7 @@ const apiContracts = (0, _agimon_ai_po_core_apiContracts.defineApiContract)({
|
|
|
141
141
|
sockets: ["global", "workspace"].map((scope) => ({
|
|
142
142
|
id: "files.channel",
|
|
143
143
|
scope,
|
|
144
|
-
service: "po.hub
|
|
144
|
+
service: "po.hub",
|
|
145
145
|
member: "file_edits",
|
|
146
146
|
kind: "channel",
|
|
147
147
|
direction: "server-to-client",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiContracts.cjs","names":["Type","defineApiContract","jsonApiResponses"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/po-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'po.hub
|
|
1
|
+
{"version":3,"file":"apiContracts.cjs","names":["Type","defineApiContract","jsonApiResponses"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/po-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'po.hub',\n member: 'file_edits',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'File edits channel payload in the hub frame payload property.',\n input: FilesPayloadSchema,\n })),\n});\nexport default apiContracts;\n"],"mappings":"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"}
|
package/dist/apiContracts.mjs
CHANGED
|
@@ -137,7 +137,7 @@ const apiContracts = defineApiContract({
|
|
|
137
137
|
sockets: ["global", "workspace"].map((scope) => ({
|
|
138
138
|
id: "files.channel",
|
|
139
139
|
scope,
|
|
140
|
-
service: "po.hub
|
|
140
|
+
service: "po.hub",
|
|
141
141
|
member: "file_edits",
|
|
142
142
|
kind: "channel",
|
|
143
143
|
direction: "server-to-client",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/po-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'po.hub
|
|
1
|
+
{"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/po-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'po.hub',\n member: 'file_edits',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'File edits channel payload in the hub frame payload property.',\n input: FilesPayloadSchema,\n })),\n});\nexport default apiContracts;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defineChannelFile, defineNativePlugin, defineSessionStore } from "@agimon-ai/po-core/native";
|
|
2
|
+
//#region src/types/webFiles.ts
|
|
3
|
+
/** The frame type the hub publishes and the web plugin claims; declared in the poWeb block too. */
|
|
4
|
+
const filesChannelType = "file_edits";
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/filesModel.ts
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function isItem(value) {
|
|
11
|
+
return isRecord(value) && typeof value.path === "string" && typeof value.relPath === "string" && typeof value.tool === "string" && typeof value.at === "number" && typeof value.count === "number";
|
|
12
|
+
}
|
|
13
|
+
/** The shared wire gate for browser and native stores. */
|
|
14
|
+
function parseFilesPayload(input) {
|
|
15
|
+
if (!isRecord(input) || !Array.isArray(input.items)) return null;
|
|
16
|
+
return { items: input.items.filter(isItem) };
|
|
17
|
+
}
|
|
18
|
+
/** The plugin's session data channel: 'file_edits' payloads into the store. */
|
|
19
|
+
const filesChannel = defineSessionStore({
|
|
20
|
+
items: [],
|
|
21
|
+
detail: {},
|
|
22
|
+
loading: [],
|
|
23
|
+
errors: {},
|
|
24
|
+
comments: []
|
|
25
|
+
}).channel({
|
|
26
|
+
channel: filesChannelType,
|
|
27
|
+
parse: parseFilesPayload,
|
|
28
|
+
reduce: (current, { items }) => ({
|
|
29
|
+
...current,
|
|
30
|
+
items
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/file-edits.native.ts
|
|
35
|
+
var file_edits_native_default = defineChannelFile(filesChannel);
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region generated/native.ts
|
|
38
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
39
|
+
...identity,
|
|
40
|
+
...value
|
|
41
|
+
};
|
|
42
|
+
const nativePlugin = defineNativePlugin({
|
|
43
|
+
id: "files",
|
|
44
|
+
session: { get channels() {
|
|
45
|
+
return [via({ channel: "file_edits" }, file_edits_native_default)];
|
|
46
|
+
} }
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export { nativePlugin };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=native.mjs.map
|
|
@@ -0,0 +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 = 'doom-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"}
|
|
@@ -26,10 +26,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
//#endregion
|
|
27
27
|
let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
|
|
28
28
|
let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
|
|
29
|
-
let
|
|
30
|
-
|
|
29
|
+
let _agimon_ai_po_core_childSession = require("@agimon-ai/po-core/childSession");
|
|
30
|
+
let _agimon_ai_po_core_hubChannel = require("@agimon-ai/po-core/hubChannel");
|
|
31
|
+
let node_fs_promises = require("node:fs/promises");
|
|
32
|
+
node_fs_promises = __toESM(node_fs_promises, 1);
|
|
31
33
|
let node_path = require("node:path");
|
|
32
34
|
node_path = __toESM(node_path, 1);
|
|
35
|
+
let _agimon_ai_po_hashline = require("@agimon-ai/po-hashline");
|
|
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);
|
|
33
39
|
let _agimon_ai_po_core_childProcess = require("@agimon-ai/po-core/childProcess");
|
|
34
40
|
let _agimon_ai_po_telemetry = require("@agimon-ai/po-telemetry");
|
|
35
41
|
let ignore = require("ignore");
|
|
@@ -38,10 +44,193 @@ let node_child_process = require("node:child_process");
|
|
|
38
44
|
let node_crypto = require("node:crypto");
|
|
39
45
|
let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
|
|
40
46
|
let node_util = require("node:util");
|
|
41
|
-
let
|
|
42
|
-
|
|
47
|
+
let node_os = require("node:os");
|
|
48
|
+
node_os = __toESM(node_os, 1);
|
|
49
|
+
let _agimon_ai_po_config = require("@agimon-ai/po-config");
|
|
43
50
|
let hono = require("hono");
|
|
44
|
-
|
|
51
|
+
//#region src/constants/editPresence.ts
|
|
52
|
+
const PRESENCE_NOTE_TITLE = "[Po: other active agents edited files in this result]";
|
|
53
|
+
const PRESENCE_NOTE_GUIDANCE = "Coordinate before editing: intercom send to the session id, or ask the subagent by name if it is on your team. Without intercom, tell the user.";
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/services/editPresence/index.ts
|
|
56
|
+
const PATH_TOOLS = /* @__PURE__ */ new Set([
|
|
57
|
+
"read",
|
|
58
|
+
"edit",
|
|
59
|
+
"write"
|
|
60
|
+
]);
|
|
61
|
+
const EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write"]);
|
|
62
|
+
const MAX_GREP_FILES = 50;
|
|
63
|
+
const MAX_NOTE_LINES = 10;
|
|
64
|
+
const MINUTE_MS = 6e4;
|
|
65
|
+
const SHORT_ID_LENGTH = 8;
|
|
66
|
+
/** Pi's built-in grep prints match rows as `relative/path:line: text`. */
|
|
67
|
+
const PI_GREP_ROW = /^(.+?):(\d+): /u;
|
|
68
|
+
function forgetPresence(ledger, agentKey) {
|
|
69
|
+
for (const [file, entries] of ledger) {
|
|
70
|
+
entries.delete(agentKey);
|
|
71
|
+
if (entries.size === 0) ledger.delete(file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function stringArg(args, key) {
|
|
75
|
+
if (typeof args !== "object" || args === null) return void 0;
|
|
76
|
+
const value = args[key];
|
|
77
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
78
|
+
}
|
|
79
|
+
function textOf(content) {
|
|
80
|
+
return content.map((part) => part.type === "text" && "text" in part && typeof part.text === "string" ? part.text : "").join("\n");
|
|
81
|
+
}
|
|
82
|
+
async function grepFiles(call) {
|
|
83
|
+
const lines = textOf(call.content).split("\n");
|
|
84
|
+
const headers = lines.flatMap((line) => {
|
|
85
|
+
const header = (0, _agimon_ai_po_hashline.parseFileHeader)(line);
|
|
86
|
+
return header === void 0 ? [] : [(0, _agimon_ai_po_hashline_files.resolveInputPath)(header.path, call.cwd)];
|
|
87
|
+
});
|
|
88
|
+
if (headers.length > 0) return [...new Set(headers)].slice(0, MAX_GREP_FILES);
|
|
89
|
+
const rows = lines.flatMap((line) => {
|
|
90
|
+
const match = PI_GREP_ROW.exec(line)?.[1];
|
|
91
|
+
return match === void 0 ? [] : [match];
|
|
92
|
+
});
|
|
93
|
+
if (rows.length === 0) return [];
|
|
94
|
+
const searchPath = (0, _agimon_ai_po_hashline_files.resolveInputPath)(stringArg(call.args, "path") ?? ".", call.cwd);
|
|
95
|
+
if ((await node_fs_promises.default.stat(searchPath).catch(() => void 0))?.isFile()) return [searchPath];
|
|
96
|
+
return [...new Set(rows.map((row) => node_path.default.resolve(searchPath, row)))].slice(0, MAX_GREP_FILES);
|
|
97
|
+
}
|
|
98
|
+
async function touchedFiles(call) {
|
|
99
|
+
if (PATH_TOOLS.has(call.toolName)) {
|
|
100
|
+
const input = stringArg(call.args, "path");
|
|
101
|
+
return input === void 0 ? [] : [await (0, _agimon_ai_po_hashline_files.resolveReadInputPath)(input, call.cwd)];
|
|
102
|
+
}
|
|
103
|
+
return call.toolName === "grep" ? grepFiles(call) : [];
|
|
104
|
+
}
|
|
105
|
+
function ago(elapsed) {
|
|
106
|
+
const minutes = Math.floor(elapsed / MINUTE_MS);
|
|
107
|
+
return minutes < 1 ? "under 1m ago" : `${minutes}m ago`;
|
|
108
|
+
}
|
|
109
|
+
function liveOthers(ledger, file, self) {
|
|
110
|
+
const entries = ledger.get(file);
|
|
111
|
+
if (entries === void 0) return [];
|
|
112
|
+
const live = [];
|
|
113
|
+
for (const [key, entry] of entries) {
|
|
114
|
+
if (key === self) continue;
|
|
115
|
+
if (entry.agent.isLive()) live.push(entry);
|
|
116
|
+
else entries.delete(key);
|
|
117
|
+
}
|
|
118
|
+
return live.sort((left, right) => right.at - left.at);
|
|
119
|
+
}
|
|
120
|
+
function record(ledger, file, agent, tag, at) {
|
|
121
|
+
const entries = ledger.get(file) ?? /* @__PURE__ */ new Map();
|
|
122
|
+
entries.set(agent.key, {
|
|
123
|
+
agent,
|
|
124
|
+
tag,
|
|
125
|
+
at
|
|
126
|
+
});
|
|
127
|
+
ledger.set(file, entries);
|
|
128
|
+
}
|
|
129
|
+
function renderNote(lines) {
|
|
130
|
+
const shown = lines.slice(0, MAX_NOTE_LINES);
|
|
131
|
+
const more = lines.length - shown.length;
|
|
132
|
+
return [
|
|
133
|
+
PRESENCE_NOTE_TITLE,
|
|
134
|
+
...shown,
|
|
135
|
+
...more > 0 ? [`- and ${more} more`] : [],
|
|
136
|
+
PRESENCE_NOTE_GUIDANCE
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Notes live editors of the files a tool call touched, then records the caller's own successful edit.
|
|
141
|
+
* Returns the content with one note appended, or undefined when there is nothing to add, so callers
|
|
142
|
+
* never rewrite a result needlessly.
|
|
143
|
+
*/
|
|
144
|
+
async function observePresence(ledger, agent, call, now = Date.now()) {
|
|
145
|
+
const recording = EDIT_TOOLS.has(call.toolName) && !call.isError;
|
|
146
|
+
if (!recording && ledger.size === 0) return void 0;
|
|
147
|
+
const lines = [];
|
|
148
|
+
for (const file of await touchedFiles(call)) {
|
|
149
|
+
const real = await node_fs_promises.default.realpath(file).catch(() => void 0);
|
|
150
|
+
if (real === void 0) continue;
|
|
151
|
+
const others = liveOthers(ledger, real, agent.key);
|
|
152
|
+
let bytes;
|
|
153
|
+
if (others.length > 0) {
|
|
154
|
+
bytes = await node_fs_promises.default.readFile(real).catch(() => void 0);
|
|
155
|
+
const current = bytes === void 0 ? void 0 : (0, _agimon_ai_po_hashline_files.computeFileTag)(bytes);
|
|
156
|
+
const shown = (0, _agimon_ai_po_hashline_files.displayPath)(file, call.cwd);
|
|
157
|
+
for (const entry of others) {
|
|
158
|
+
const state = entry.tag === current ? "unchanged" : "changed";
|
|
159
|
+
lines.push(`- ${shown}: ${entry.agent.label()}, ${ago(now - entry.at)}, file ${state} since their edit.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (recording) {
|
|
163
|
+
bytes ??= await node_fs_promises.default.readFile(real).catch(() => void 0);
|
|
164
|
+
if (bytes !== void 0) record(ledger, real, agent, (0, _agimon_ai_po_hashline_files.computeFileTag)(bytes), now);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (lines.length === 0) return void 0;
|
|
168
|
+
return [...call.content, {
|
|
169
|
+
type: "text",
|
|
170
|
+
text: renderNote(lines)
|
|
171
|
+
}];
|
|
172
|
+
}
|
|
173
|
+
/** Edit presence for one hub session and the in-process children it spawns. */
|
|
174
|
+
function createSessionPresence(options) {
|
|
175
|
+
const { sessionId, ledger } = options;
|
|
176
|
+
const sessionAgent = {
|
|
177
|
+
key: `session:${sessionId}`,
|
|
178
|
+
label: () => `session ${sessionId}${options.workflowStep ? " (workflow step)" : ""}`,
|
|
179
|
+
contact: sessionId,
|
|
180
|
+
isLive: options.isLive
|
|
181
|
+
};
|
|
182
|
+
const safely = async (work) => {
|
|
183
|
+
try {
|
|
184
|
+
return await work();
|
|
185
|
+
} catch (error) {
|
|
186
|
+
options.onError?.(error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
toolResult: {
|
|
192
|
+
event: "tool_result",
|
|
193
|
+
async handle(event, execution) {
|
|
194
|
+
const toolName = event.toolName;
|
|
195
|
+
if (execution.sessionId !== sessionId || typeof toolName !== "string" || !Array.isArray(event.content)) return;
|
|
196
|
+
const content = event.content;
|
|
197
|
+
const next = await safely(() => observePresence(ledger, sessionAgent, {
|
|
198
|
+
toolName,
|
|
199
|
+
args: event.args,
|
|
200
|
+
content,
|
|
201
|
+
isError: event.isError === true,
|
|
202
|
+
cwd: execution.cwd
|
|
203
|
+
}));
|
|
204
|
+
return next === void 0 ? void 0 : { content: next };
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
childHooks: { bind(scope) {
|
|
208
|
+
const { runId, parentSessionId, agent } = scope.request;
|
|
209
|
+
const child = {
|
|
210
|
+
key: `run:${runId}`,
|
|
211
|
+
label: () => `${options.labelFor(runId) ?? `${agent} (run ${runId.slice(0, SHORT_ID_LENGTH)})`} (subagent of session ${parentSessionId})`,
|
|
212
|
+
contact: parentSessionId,
|
|
213
|
+
isLive: () => !scope.signal.aborted
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
beforeTool: () => void 0,
|
|
217
|
+
async afterTool(event) {
|
|
218
|
+
const next = await safely(() => observePresence(ledger, child, {
|
|
219
|
+
toolName: event.toolName,
|
|
220
|
+
args: event.args,
|
|
221
|
+
content: event.content,
|
|
222
|
+
isError: event.isError,
|
|
223
|
+
cwd: scope.request.cwd || options.cwd
|
|
224
|
+
}));
|
|
225
|
+
return next === void 0 ? void 0 : { content: next };
|
|
226
|
+
},
|
|
227
|
+
dispose: async () => forgetPresence(ledger, child.key)
|
|
228
|
+
};
|
|
229
|
+
} },
|
|
230
|
+
dispose: () => forgetPresence(ledger, sessionAgent.key)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
45
234
|
//#region src/services/doomIgnore/index.ts
|
|
46
235
|
function matchablePath(relPath) {
|
|
47
236
|
const normalized = relPath.replace(/\\/gu, "/");
|
|
@@ -1128,23 +1317,23 @@ const filesStatusKey = "doom-file-edit-files";
|
|
|
1128
1317
|
* This is the one file the routes, the Hono app and the browser all read, so a
|
|
1129
1318
|
* path cannot move on one side alone.
|
|
1130
1319
|
*/
|
|
1131
|
-
|
|
1320
|
+
const routes = {
|
|
1132
1321
|
detail: {
|
|
1133
1322
|
method: "GET",
|
|
1134
1323
|
path: "/detail",
|
|
1135
1324
|
query: ["path"],
|
|
1136
|
-
response:
|
|
1325
|
+
response: void 0
|
|
1137
1326
|
},
|
|
1138
1327
|
preview: {
|
|
1139
1328
|
method: "GET",
|
|
1140
1329
|
path: "/preview",
|
|
1141
1330
|
query: ["path"],
|
|
1142
|
-
response:
|
|
1331
|
+
response: void 0
|
|
1143
1332
|
},
|
|
1144
1333
|
save: {
|
|
1145
1334
|
method: "PUT",
|
|
1146
1335
|
path: "/content",
|
|
1147
|
-
response:
|
|
1336
|
+
response: void 0
|
|
1148
1337
|
},
|
|
1149
1338
|
/** The same path as `save`; the method is the whole of the difference. */
|
|
1150
1339
|
remove: {
|
|
@@ -1162,10 +1351,11 @@ var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
|
|
|
1162
1351
|
file: {
|
|
1163
1352
|
method: "GET",
|
|
1164
1353
|
path: "/file",
|
|
1354
|
+
raw: true,
|
|
1165
1355
|
query: ["path"],
|
|
1166
1356
|
host: true
|
|
1167
1357
|
}
|
|
1168
|
-
}
|
|
1358
|
+
};
|
|
1169
1359
|
//#endregion
|
|
1170
1360
|
//#region src/types/fileEditsApi.ts
|
|
1171
1361
|
/**
|
|
@@ -1399,7 +1589,14 @@ function createFileEditsApi(options = {}) {
|
|
|
1399
1589
|
const root = node_path.default.resolve(cwd);
|
|
1400
1590
|
return isInside(root, filePath) && await isRealPathInside(root, filePath);
|
|
1401
1591
|
};
|
|
1402
|
-
|
|
1592
|
+
/** The Po-owned folder holding the file, checked the same two ways as the working directory. */
|
|
1593
|
+
const readableRootOf = async (filePath) => {
|
|
1594
|
+
for (const root of options.readableRoots?.() ?? []) {
|
|
1595
|
+
const resolved = node_path.default.resolve(root);
|
|
1596
|
+
if (isInside(resolved, filePath) && await isRealPathInside(resolved, filePath)) return resolved;
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
app.get(routes.detail.path, async (context) => {
|
|
1403
1600
|
const requested = context.req.query(PATH_QUERY_PARAM);
|
|
1404
1601
|
const versions = await recordedVersions(requested);
|
|
1405
1602
|
if (versions === void 0 || requested === void 0) return context.json({ error: "This session recorded no changes to that file." }, 404);
|
|
@@ -1425,21 +1622,22 @@ function createFileEditsApi(options = {}) {
|
|
|
1425
1622
|
* these routes no working directory has no boundary to enforce and so serves
|
|
1426
1623
|
* nothing.
|
|
1427
1624
|
*/
|
|
1428
|
-
app.get(
|
|
1625
|
+
app.get(routes.preview.path, async (context) => {
|
|
1429
1626
|
const requested = context.req.query(PATH_QUERY_PARAM);
|
|
1430
1627
|
if (requested === void 0 || requested === "" || requested.includes("\0")) return context.json({ error: "A preview names a path." }, 400);
|
|
1431
1628
|
if (cwd === "") return context.json({ error: "This session has no working directory to read from." }, 403);
|
|
1432
1629
|
const root = node_path.default.resolve(cwd);
|
|
1433
1630
|
const filePath = node_path.default.resolve(root, requested);
|
|
1434
|
-
|
|
1631
|
+
const inSession = await isInsideSession(filePath);
|
|
1632
|
+
if (!inSession && await readableRootOf(filePath) === void 0) return context.json({ error: "The path leaves the session directory." }, 403);
|
|
1435
1633
|
const body = {
|
|
1436
1634
|
path: filePath,
|
|
1437
|
-
relPath: node_path.default.relative(root, filePath),
|
|
1635
|
+
relPath: inSession ? node_path.default.relative(root, filePath) : filePath,
|
|
1438
1636
|
working: await readWorking(filePath)
|
|
1439
1637
|
};
|
|
1440
1638
|
return context.json(body);
|
|
1441
1639
|
});
|
|
1442
|
-
app.put(
|
|
1640
|
+
app.put(routes.save.path, async (context) => {
|
|
1443
1641
|
let request;
|
|
1444
1642
|
try {
|
|
1445
1643
|
request = await context.req.json();
|
|
@@ -1460,7 +1658,7 @@ function createFileEditsApi(options = {}) {
|
|
|
1460
1658
|
const body = { hash: hashOf(request.content) };
|
|
1461
1659
|
return context.json(body);
|
|
1462
1660
|
});
|
|
1463
|
-
app.delete(
|
|
1661
|
+
app.delete(routes.remove.path, async (context) => {
|
|
1464
1662
|
const requested = context.req.query(PATH_QUERY_PARAM);
|
|
1465
1663
|
if (await recordedVersions(requested) === void 0 || requested === void 0) return context.json({ error: "This session recorded no changes to that file." }, 404);
|
|
1466
1664
|
const filePath = node_path.default.resolve(cwd, requested);
|
|
@@ -1478,10 +1676,15 @@ function createFileEditsApi(options = {}) {
|
|
|
1478
1676
|
const api = {
|
|
1479
1677
|
basePath: API_BASE_PATH,
|
|
1480
1678
|
start(context) {
|
|
1679
|
+
const environment = context.environment ?? process.env;
|
|
1680
|
+
const cwd = context.cwd;
|
|
1481
1681
|
const app = createFileEditsApi({
|
|
1482
1682
|
paths: new FileEditPaths(context.environment),
|
|
1483
1683
|
...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
|
|
1484
|
-
...
|
|
1684
|
+
...cwd === void 0 ? {} : {
|
|
1685
|
+
cwd,
|
|
1686
|
+
readableRoots: () => plansRoots(cwd, environment)
|
|
1687
|
+
}
|
|
1485
1688
|
});
|
|
1486
1689
|
return {
|
|
1487
1690
|
fetch: (request) => app.fetch(request),
|
|
@@ -1489,6 +1692,20 @@ const api = {
|
|
|
1489
1692
|
};
|
|
1490
1693
|
}
|
|
1491
1694
|
};
|
|
1695
|
+
/**
|
|
1696
|
+
* The plans directory Plan mode writes to, resolved the way Plan resolves it, so
|
|
1697
|
+
* a plan link in the transcript opens in the file view. A config that cannot be
|
|
1698
|
+
* read admits nothing extra rather than failing the preview.
|
|
1699
|
+
*/
|
|
1700
|
+
function plansRoots(cwd, environment) {
|
|
1701
|
+
const home = environment.HOME ?? node_os.default.homedir();
|
|
1702
|
+
try {
|
|
1703
|
+
const configured = (0, _agimon_ai_po_config.loadDoomConfig)(cwd, home, environment).modes?.planning?.plansDirectory;
|
|
1704
|
+
return [(0, _agimon_ai_po_config.resolvePlanningPlansDirectory)(configured, cwd, home)];
|
|
1705
|
+
} catch {
|
|
1706
|
+
return [];
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1492
1709
|
//#endregion
|
|
1493
1710
|
//#region src/services/sessionStateSweep/index.ts
|
|
1494
1711
|
/**
|
|
@@ -1854,12 +2071,37 @@ function createFileEditSession({ agent, host }) {
|
|
|
1854
2071
|
}
|
|
1855
2072
|
//#endregion
|
|
1856
2073
|
//#region src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts
|
|
2074
|
+
function createPresence({ agent, host, context }) {
|
|
2075
|
+
const ledger = host.context.editPresence;
|
|
2076
|
+
if (!agent || !ledger) return void 0;
|
|
2077
|
+
const execution = agent.context;
|
|
2078
|
+
return createSessionPresence({
|
|
2079
|
+
ledger,
|
|
2080
|
+
sessionId: execution.sessionId,
|
|
2081
|
+
cwd: execution.cwd,
|
|
2082
|
+
workflowStep: execution.sessionContext?.provenance === _agimon_ai_po_core_hubChannel.WORKFLOW_STEP_SESSION_PROVENANCE,
|
|
2083
|
+
isLive: () => host.context.sessionService?.isLive(execution.sessionId) ?? true,
|
|
2084
|
+
labelFor: (runId) => (0, _agimon_ai_po_core_childSession.readDoomChildSessionLabels)(context)?.labelFor(runId),
|
|
2085
|
+
onError: (error) => host.context.onNotice(`Edit presence failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
1857
2088
|
const createFileEditServerRoot = (context) => {
|
|
1858
2089
|
const runtime = createFileEditSession(context);
|
|
2090
|
+
const presence = createPresence(context);
|
|
1859
2091
|
return {
|
|
1860
|
-
value:
|
|
2092
|
+
value: {
|
|
2093
|
+
...runtime,
|
|
2094
|
+
...presence === void 0 ? {} : { presence }
|
|
2095
|
+
},
|
|
1861
2096
|
activities: runtime.activities,
|
|
1862
|
-
|
|
2097
|
+
...presence === void 0 ? {} : { services: [(cordis) => cordis.inject([_agimon_ai_po_core_childSession.DOOM_CHILD_SESSION_HOOK_REGISTRY_SERVICE], (registryContext) => {
|
|
2098
|
+
const registration = registryContext.get(_agimon_ai_po_core_childSession.DOOM_CHILD_SESSION_HOOK_REGISTRY_SERVICE).register(presence.childHooks);
|
|
2099
|
+
return () => registration.dispose();
|
|
2100
|
+
})] },
|
|
2101
|
+
onDispose: async (pluginContext) => {
|
|
2102
|
+
presence?.dispose();
|
|
2103
|
+
await runtime.onDispose?.(pluginContext);
|
|
2104
|
+
}
|
|
1863
2105
|
};
|
|
1864
2106
|
};
|
|
1865
2107
|
//#endregion
|
|
@@ -1888,6 +2130,12 @@ var tool_execution_end_server_default = (0, _agimon_ai_po_core_extensionFile.def
|
|
|
1888
2130
|
//#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server.ts
|
|
1889
2131
|
var tool_execution_start_server_default = (0, _agimon_ai_po_core_extensionFile.defineHook)((context) => context.root.hooks[0]);
|
|
1890
2132
|
//#endregion
|
|
2133
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/tool-result.server.ts
|
|
2134
|
+
var tool_result_server_default = (0, _agimon_ai_po_core_extensionFile.defineHook)(({ root }) => ({
|
|
2135
|
+
event: "tool_result",
|
|
2136
|
+
handle: async (event, execution) => root.presence?.toolResult.handle(event, execution)
|
|
2137
|
+
}));
|
|
2138
|
+
//#endregion
|
|
1891
2139
|
//#region generated/server.ts
|
|
1892
2140
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
1893
2141
|
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
@@ -1918,7 +2166,11 @@ const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
|
|
|
1918
2166
|
return [() => via({ frameType: "file_edits" }, fileEdits_server_default())];
|
|
1919
2167
|
},
|
|
1920
2168
|
get hooks() {
|
|
1921
|
-
return [
|
|
2169
|
+
return [
|
|
2170
|
+
via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession)),
|
|
2171
|
+
via({ event: "tool_execution_start" }, at(tool_execution_start_server_default, contextSession)),
|
|
2172
|
+
via({ event: "tool_result" }, at(tool_result_server_default, contextSession))
|
|
2173
|
+
];
|
|
1922
2174
|
},
|
|
1923
2175
|
services: [...scopeSession.services ?? []],
|
|
1924
2176
|
get activities() {
|