@agimon-ai/doompi-edit 0.0.1-alpha.48 → 0.0.1-alpha.49

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.
@@ -0,0 +1,160 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let _agimon_ai_doompi_core_mcp_facet = require("@agimon-ai/doompi-core/mcp-facet");
6
+ let node_fs = require("node:fs");
7
+ let node_fs_promises = require("node:fs/promises");
8
+ let _agimon_ai_doompi_hashline = require("@agimon-ai/doompi-hashline");
9
+ let _agimon_ai_doompi_hashline_files = require("@agimon-ai/doompi-hashline/files");
10
+ let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
11
+ let typebox = require("typebox");
12
+ //#region src/schemas/editTool.ts
13
+ const HashlineRangeSchema = typebox.Type.Object({
14
+ from: typebox.Type.String({ description: "One inclusive starting anchor, for example 5#abc. Do not paste multiple lines." }),
15
+ to: typebox.Type.String({ description: "One inclusive ending anchor, for example 8#def. Do not paste multiple lines." }),
16
+ content: typebox.Type.Optional(typebox.Type.Union([typebox.Type.String({ description: "Replacement content. Empty content deletes the selected lines." }), typebox.Type.Null({ description: "Delete the selected lines." })]))
17
+ });
18
+ const EditParamsSchema = typebox.Type.Object({
19
+ path: typebox.Type.String({ description: "Path to the file returned by a compatible hashline read or grep tool." }),
20
+ hash: typebox.Type.String({ description: "Eight-character exact-byte file tag returned by read or grep." }),
21
+ edits: typebox.Type.Array(HashlineRangeSchema, {
22
+ minItems: 1,
23
+ description: "Non-overlapping inclusive ranges from the same original snapshot."
24
+ })
25
+ });
26
+ //#endregion
27
+ //#region src/services/editTool/index.ts
28
+ function createHeadlessEditTool() {
29
+ return {
30
+ name: "edit",
31
+ label: "edit",
32
+ description: "Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.",
33
+ promptSnippet: "Edit files with snapshot-bound hashline ranges",
34
+ promptGuidelines: ["Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.", "Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks."],
35
+ parameters: EditParamsSchema,
36
+ executionMode: "parallel",
37
+ execute: (_toolCallId, params, signal, _onUpdate, context) => executeHashlineEdit(params, context.cwd, signal)
38
+ };
39
+ }
40
+ function assertNotAborted(signal) {
41
+ if (signal?.aborted) throw new Error("Operation aborted");
42
+ }
43
+ async function executeHashlineEdit(params, cwd, signal) {
44
+ const absolutePath = (0, _agimon_ai_doompi_hashline_files.resolveInputPath)(params.path, cwd);
45
+ const expectedHash = (0, _agimon_ai_doompi_hashline.normalizeFileTag)(params.hash);
46
+ return (0, _earendil_works_pi_coding_agent.withFileMutationQueue)(absolutePath, async () => {
47
+ assertNotAborted(signal);
48
+ await (0, node_fs_promises.access)(absolutePath, node_fs.constants.R_OK | node_fs.constants.W_OK);
49
+ const beforeBytes = await (0, node_fs_promises.readFile)(absolutePath);
50
+ assertNotAborted(signal);
51
+ const actualHash = (0, _agimon_ai_doompi_hashline_files.computeFileTag)(beforeBytes);
52
+ if (actualHash !== expectedHash) throw new Error(`Stale file hash ${expectedHash}. Current hash is ${actualHash}. Re-read the file and retry.`);
53
+ const hasBom = beforeBytes.subarray(0, 3).equals(Buffer.from([
54
+ 239,
55
+ 187,
56
+ 191
57
+ ]));
58
+ const decoded = (0, _agimon_ai_doompi_hashline_files.decodeUtf8)(beforeBytes, params.path);
59
+ const withoutBom = (0, _agimon_ai_doompi_hashline.stripBom)(decoded);
60
+ const before = (0, _agimon_ai_doompi_hashline.normalizeToLf)(withoutBom);
61
+ const applied = (0, _agimon_ai_doompi_hashline.applyHashlineEdits)(before, params.edits);
62
+ const editedText = restoreOriginalLineEndings(withoutBom, applied.edits);
63
+ if ((0, _agimon_ai_doompi_hashline.normalizeToLf)(editedText) !== applied.content) throw new Error("Could not preserve the file line endings safely. The file was not changed.");
64
+ const diff = (0, _earendil_works_pi_coding_agent.generateDiffString)(before, applied.content);
65
+ const patch = (0, _earendil_works_pi_coding_agent.generateUnifiedPatch)((0, _agimon_ai_doompi_hashline_files.displayPath)(absolutePath, cwd), before, applied.content);
66
+ const details = {
67
+ diff: diff.diff,
68
+ patch,
69
+ firstChangedLine: diff.firstChangedLine
70
+ };
71
+ assertNotAborted(signal);
72
+ if (!(await (0, node_fs_promises.readFile)(absolutePath)).equals(beforeBytes)) throw new Error("The file changed while the edit was being prepared. Re-read it and retry.");
73
+ assertNotAborted(signal);
74
+ if (applied.content !== before) {
75
+ const output = Buffer.from(`${hasBom ? "" : ""}${editedText}`, "utf8");
76
+ await (0, node_fs_promises.writeFile)(absolutePath, output);
77
+ }
78
+ const count = applied.edits.length;
79
+ const noun = count === 1 ? "range" : "ranges";
80
+ return {
81
+ content: [{
82
+ type: "text",
83
+ text: `${applied.content === before ? `No changes needed in ${params.path}.` : `Edited ${params.path} (${count} ${noun}).`} Re-read before editing it again.`
84
+ }],
85
+ details
86
+ };
87
+ });
88
+ }
89
+ function restoreOriginalLineEndings(original, edits) {
90
+ const tokens = tokenizeLines(original);
91
+ const defaultEnding = tokens.find((token) => token.ending !== "")?.ending ?? "\n";
92
+ for (const edit of [...edits].reverse()) {
93
+ const start = edit.from.line - 1;
94
+ const count = edit.to.line - edit.from.line + 1;
95
+ const removed = tokens.slice(start, start + count);
96
+ const inheritedEnding = removed.at(-1)?.ending ?? "";
97
+ const internalEnding = removed.find((token) => token.ending !== "")?.ending ?? defaultEnding;
98
+ const lines = replacementLines(edit.content);
99
+ if (lines.length === 0 && removed.at(-1)?.ending === "" && start > 0) {
100
+ const previous = tokens[start - 1];
101
+ if (previous) tokens[start - 1] = {
102
+ ...previous,
103
+ ending: ""
104
+ };
105
+ }
106
+ const replacements = lines.map((text, index) => ({
107
+ text,
108
+ ending: index === lines.length - 1 ? inheritedEnding : internalEnding
109
+ }));
110
+ tokens.splice(start, count, ...replacements);
111
+ }
112
+ return tokens.map((token) => `${token.text}${token.ending}`).join("");
113
+ }
114
+ function tokenizeLines(content) {
115
+ const tokens = [];
116
+ let start = 0;
117
+ for (let index = 0; index < content.length; index += 1) {
118
+ const character = content[index];
119
+ if (character !== "\r" && character !== "\n") continue;
120
+ const ending = character === "\r" && content[index + 1] === "\n" ? "\r\n" : character;
121
+ tokens.push({
122
+ text: content.slice(start, index),
123
+ ending
124
+ });
125
+ if (ending === "\r\n") index += 1;
126
+ start = index + 1;
127
+ }
128
+ tokens.push({
129
+ text: content.slice(start),
130
+ ending: ""
131
+ });
132
+ return tokens;
133
+ }
134
+ function replacementLines(content) {
135
+ if (content === null || content === "") return [];
136
+ const normalized = (0, _agimon_ai_doompi_hashline.normalizeToLf)(content);
137
+ const trimmed = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
138
+ return trimmed === "" ? [] : trimmed.split("\n");
139
+ }
140
+ //#endregion
141
+ //#region src/extensions/workspaces/sessions/(backend)/tool/edit.mcp.ts
142
+ var edit_mcp_default = (0, _agimon_ai_doompi_core_mcp_facet.defineMcpTool)(createHeadlessEditTool);
143
+ //#endregion
144
+ //#region generated/mcp.ts
145
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
146
+ const via = (identity, value) => ({
147
+ ...identity,
148
+ ...value
149
+ });
150
+ const mcp = (0, _agimon_ai_doompi_core_mcp_facet.defineMcpPlugin)({
151
+ name: "@agimon-ai/doompi-edit",
152
+ session: (context) => ({ get tools() {
153
+ return [via({ name: "edit" }, at(edit_mcp_default, context))];
154
+ } })
155
+ });
156
+ //#endregion
157
+ exports.default = mcp;
158
+ exports.mcp = mcp;
159
+
160
+ //# sourceMappingURL=mcp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.cjs","names":["Type","resolveInputPath","normalizeFileTag","withFileMutationQueue","access","constants","readFile","computeFileTag","decodeUtf8","stripBom","normalizeToLf","applyHashlineEdits","generateDiffString","generateUnifiedPatch","displayPath","writeFile","defineMcpTool","defineMcpPlugin","toolEdit"],"sources":["../../src/schemas/editTool.ts","../../src/services/editTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/edit.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const HashlineRangeSchema = Type.Object({\n from: Type.String({ description: 'One inclusive starting anchor, for example 5#abc. Do not paste multiple lines.' }),\n to: Type.String({ description: 'One inclusive ending anchor, for example 8#def. Do not paste multiple lines.' }),\n content: Type.Optional(\n Type.Union([\n Type.String({ description: 'Replacement content. Empty content deletes the selected lines.' }),\n Type.Null({ description: 'Delete the selected lines.' }),\n ]),\n ),\n});\n\nexport const EditParamsSchema = Type.Object({\n path: Type.String({ description: 'Path to the file returned by a compatible hashline read or grep tool.' }),\n hash: Type.String({ description: 'Eight-character exact-byte file tag returned by read or grep.' }),\n edits: Type.Array(HashlineRangeSchema, {\n minItems: 1,\n description: 'Non-overlapping inclusive ranges from the same original snapshot.',\n }),\n});\n\nexport type HashlineRange = Static<typeof HashlineRangeSchema>;\nexport type EditParams = Static<typeof EditParamsSchema>;\n","import { constants } from 'node:fs';\nimport { access, readFile, writeFile } from 'node:fs/promises';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport {\n applyHashlineEdits,\n normalizeFileTag,\n normalizeToLf,\n stripBom,\n type PreparedHashlineEdit,\n} from '@agimon-ai/doompi-hashline';\nimport { computeFileTag, decodeUtf8, displayPath, resolveInputPath } from '@agimon-ai/doompi-hashline/files';\nimport {\n generateDiffString,\n generateUnifiedPatch,\n withFileMutationQueue,\n type EditToolDetails,\n} from '@earendil-works/pi-coding-agent';\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { EditParamsSchema, type EditParams } from '../../schemas/editTool';\n\nexport function createHashlineEditTool(): ToolDefinition<typeof EditParamsSchema> {\n return {\n name: 'edit',\n label: 'edit',\n description:\n 'Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.',\n promptSnippet: 'Edit files with snapshot-bound hashline ranges',\n promptGuidelines: [\n 'Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.',\n 'Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks.',\n 'Put multiple disjoint changes to one file in one edit call. All anchors must describe the original snapshot.',\n 'Omit content or pass an empty string to delete an inclusive range. Merge overlapping ranges before calling edit.',\n ],\n parameters: EditParamsSchema,\n executionMode: 'parallel',\n async execute(_toolCallId, params, signal, _onUpdate, ctx) {\n return executeHashlineEdit(params as EditParams, ctx.cwd, signal);\n },\n };\n}\n\nexport function createHeadlessEditTool(): DoomHeadlessTool<typeof EditParamsSchema> {\n return {\n name: 'edit',\n label: 'edit',\n description:\n 'Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.',\n promptSnippet: 'Edit files with snapshot-bound hashline ranges',\n promptGuidelines: [\n 'Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.',\n 'Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks.',\n ],\n parameters: EditParamsSchema,\n executionMode: 'parallel',\n execute: (\n _toolCallId: string,\n params: EditParams,\n signal: AbortSignal | undefined,\n _onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => executeHashlineEdit(params, context.cwd, signal),\n };\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n\nexport async function executeHashlineEdit(\n params: EditParams,\n cwd: string,\n signal: AbortSignal | undefined,\n): Promise<{ content: [{ type: 'text'; text: string }]; details: EditToolDetails }> {\n const absolutePath = resolveInputPath(params.path, cwd);\n const expectedHash = normalizeFileTag(params.hash);\n return withFileMutationQueue(absolutePath, async () => {\n assertNotAborted(signal);\n await access(absolutePath, constants.R_OK | constants.W_OK);\n const beforeBytes = await readFile(absolutePath);\n assertNotAborted(signal);\n\n const actualHash = computeFileTag(beforeBytes);\n if (actualHash !== expectedHash) {\n throw new Error(`Stale file hash ${expectedHash}. Current hash is ${actualHash}. Re-read the file and retry.`);\n }\n\n const hasBom = beforeBytes.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf]));\n const decoded = decodeUtf8(beforeBytes, params.path);\n const withoutBom = stripBom(decoded);\n const before = normalizeToLf(withoutBom);\n const applied = applyHashlineEdits(before, params.edits);\n const editedText = restoreOriginalLineEndings(withoutBom, applied.edits);\n if (normalizeToLf(editedText) !== applied.content) {\n throw new Error('Could not preserve the file line endings safely. The file was not changed.');\n }\n const diff = generateDiffString(before, applied.content);\n const patch = generateUnifiedPatch(displayPath(absolutePath, cwd), before, applied.content);\n const details: EditToolDetails = { diff: diff.diff, patch, firstChangedLine: diff.firstChangedLine };\n\n assertNotAborted(signal);\n const currentBytes = await readFile(absolutePath);\n if (!currentBytes.equals(beforeBytes)) {\n throw new Error('The file changed while the edit was being prepared. Re-read it and retry.');\n }\n assertNotAborted(signal);\n\n if (applied.content !== before) {\n const output = Buffer.from(`${hasBom ? '\\ufeff' : ''}${editedText}`, 'utf8');\n await writeFile(absolutePath, output);\n }\n\n const count = applied.edits.length;\n const noun = count === 1 ? 'range' : 'ranges';\n const message =\n applied.content === before ? `No changes needed in ${params.path}.` : `Edited ${params.path} (${count} ${noun}).`;\n return { content: [{ type: 'text', text: `${message} Re-read before editing it again.` }], details };\n });\n}\n\ninterface LineToken {\n readonly text: string;\n readonly ending: '\\r\\n' | '\\n' | '\\r' | '';\n}\n\nfunction restoreOriginalLineEndings(original: string, edits: readonly PreparedHashlineEdit[]): string {\n const tokens = tokenizeLines(original);\n const defaultEnding = tokens.find((token) => token.ending !== '')?.ending ?? '\\n';\n for (const edit of [...edits].reverse()) {\n const start = edit.from.line - 1;\n const count = edit.to.line - edit.from.line + 1;\n const removed = tokens.slice(start, start + count);\n const inheritedEnding = removed.at(-1)?.ending ?? '';\n const internalEnding = removed.find((token) => token.ending !== '')?.ending ?? defaultEnding;\n const lines = replacementLines(edit.content);\n if (lines.length === 0 && removed.at(-1)?.ending === '' && start > 0) {\n const previous = tokens[start - 1];\n if (previous) tokens[start - 1] = { ...previous, ending: '' };\n }\n const replacements = lines.map<LineToken>((text, index) => ({\n text,\n ending: index === lines.length - 1 ? inheritedEnding : internalEnding,\n }));\n tokens.splice(start, count, ...replacements);\n }\n return tokens.map((token) => `${token.text}${token.ending}`).join('');\n}\n\nfunction tokenizeLines(content: string): LineToken[] {\n const tokens: LineToken[] = [];\n let start = 0;\n for (let index = 0; index < content.length; index += 1) {\n const character = content[index];\n if (character !== '\\r' && character !== '\\n') continue;\n const ending = character === '\\r' && content[index + 1] === '\\n' ? '\\r\\n' : character;\n tokens.push({ text: content.slice(start, index), ending });\n if (ending === '\\r\\n') index += 1;\n start = index + 1;\n }\n tokens.push({ text: content.slice(start), ending: '' });\n return tokens;\n}\n\nfunction replacementLines(content: string | null): string[] {\n if (content === null || content === '') return [];\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport { createHeadlessEditTool } from '../../../../../services/editTool';\n\nexport default defineMcpTool(createHeadlessEditTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport toolEdit from '../src/extensions/workspaces/sessions/(backend)/tool/edit.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T => ({ ...identity, ...(value as object) }) as T;\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-edit',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [via({ name: 'edit' }, at(toolEdit, context))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":";;;;;;;;;;;;AAEA,MAAa,sBAAsBA,QAAAA,KAAK,OAAO;CAC7C,MAAMA,QAAAA,KAAK,OAAO,EAAE,aAAa,iFAAiF,CAAC;CACnH,IAAIA,QAAAA,KAAK,OAAO,EAAE,aAAa,+EAA+E,CAAC;CAC/G,SAASA,QAAAA,KAAK,SACZA,QAAAA,KAAK,MAAM,CACTA,QAAAA,KAAK,OAAO,EAAE,aAAa,iEAAiE,CAAC,GAC7FA,QAAAA,KAAK,KAAK,EAAE,aAAa,6BAA6B,CAAC,CACzD,CAAC,CACH;AACF,CAAC;AAED,MAAa,mBAAmBA,QAAAA,KAAK,OAAO;CAC1C,MAAMA,QAAAA,KAAK,OAAO,EAAE,aAAa,wEAAwE,CAAC;CAC1G,MAAMA,QAAAA,KAAK,OAAO,EAAE,aAAa,gEAAgE,CAAC;CAClG,OAAOA,QAAAA,KAAK,MAAM,qBAAqB;EACrC,UAAU;EACV,aAAa;CACf,CAAC;AACH,CAAC;;;AC2BD,SAAgB,yBAAoE;CAClF,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,kBAAkB,CAChB,qHACA,yGACF;EACA,YAAY;EACZ,eAAe;EACf,UACE,aACA,QACA,QACA,WACA,YACG,oBAAoB,QAAQ,QAAQ,KAAK,MAAM;CACtD;AACF;AAEA,SAAgB,iBAAiB,QAAuC;CACtE,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;AAC1D;AAEA,eAAsB,oBACpB,QACA,KACA,QACkF;CAClF,MAAM,gBAAA,GAAeC,iCAAAA,iBAAAA,CAAiB,OAAO,MAAM,GAAG;CACtD,MAAM,gBAAA,GAAeC,2BAAAA,iBAAAA,CAAiB,OAAO,IAAI;CACjD,QAAA,GAAOC,gCAAAA,sBAAAA,CAAsB,cAAc,YAAY;EACrD,iBAAiB,MAAM;EACvB,OAAA,GAAMC,iBAAAA,OAAAA,CAAO,cAAcC,QAAAA,UAAU,OAAOA,QAAAA,UAAU,IAAI;EAC1D,MAAM,cAAc,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,YAAY;EAC/C,iBAAiB,MAAM;EAEvB,MAAM,cAAA,GAAaC,iCAAAA,eAAAA,CAAe,WAAW;EAC7C,IAAI,eAAe,cACjB,MAAM,IAAI,MAAM,mBAAmB,aAAa,oBAAoB,WAAW,8BAA8B;EAG/G,MAAM,SAAS,YAAY,SAAS,GAAG,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK;GAAC;GAAM;GAAM;EAAI,CAAC,CAAC;EAChF,MAAM,WAAA,GAAUC,iCAAAA,WAAAA,CAAW,aAAa,OAAO,IAAI;EACnD,MAAM,cAAA,GAAaC,2BAAAA,SAAAA,CAAS,OAAO;EACnC,MAAM,UAAA,GAASC,2BAAAA,cAAAA,CAAc,UAAU;EACvC,MAAM,WAAA,GAAUC,2BAAAA,mBAAAA,CAAmB,QAAQ,OAAO,KAAK;EACvD,MAAM,aAAa,2BAA2B,YAAY,QAAQ,KAAK;EACvE,KAAA,GAAID,2BAAAA,cAAAA,CAAc,UAAU,MAAM,QAAQ,SACxC,MAAM,IAAI,MAAM,4EAA4E;EAE9F,MAAM,QAAA,GAAOE,gCAAAA,mBAAAA,CAAmB,QAAQ,QAAQ,OAAO;EACvD,MAAM,SAAA,GAAQC,gCAAAA,qBAAAA,EAAAA,GAAqBC,iCAAAA,YAAAA,CAAY,cAAc,GAAG,GAAG,QAAQ,QAAQ,OAAO;EAC1F,MAAM,UAA2B;GAAE,MAAM,KAAK;GAAM;GAAO,kBAAkB,KAAK;EAAiB;EAEnG,iBAAiB,MAAM;EAEvB,IAAI,EAAC,OAAA,GADsBR,iBAAAA,SAAAA,CAAS,YAAY,EAAA,CAC9B,OAAO,WAAW,GAClC,MAAM,IAAI,MAAM,2EAA2E;EAE7F,iBAAiB,MAAM;EAEvB,IAAI,QAAQ,YAAY,QAAQ;GAC9B,MAAM,SAAS,OAAO,KAAK,GAAG,SAAS,MAAW,KAAK,cAAc,MAAM;GAC3E,OAAA,GAAMS,iBAAAA,UAAAA,CAAU,cAAc,MAAM;EACtC;EAEA,MAAM,QAAQ,QAAQ,MAAM;EAC5B,MAAM,OAAO,UAAU,IAAI,UAAU;EAGrC,OAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,GADvC,QAAQ,YAAY,SAAS,wBAAwB,OAAO,KAAK,KAAK,UAAU,OAAO,KAAK,IAAI,MAAM,GAAG,KAAK,IAC5D;GAAmC,CAAC;GAAG;EAAQ;CACrG,CAAC;AACH;AAOA,SAAS,2BAA2B,UAAkB,OAAgD;CACpG,MAAM,SAAS,cAAc,QAAQ;CACrC,MAAM,gBAAgB,OAAO,MAAM,UAAU,MAAM,WAAW,EAAE,CAAC,EAAE,UAAU;CAC7E,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,GAAG;EACvC,MAAM,QAAQ,KAAK,KAAK,OAAO;EAC/B,MAAM,QAAQ,KAAK,GAAG,OAAO,KAAK,KAAK,OAAO;EAC9C,MAAM,UAAU,OAAO,MAAM,OAAO,QAAQ,KAAK;EACjD,MAAM,kBAAkB,QAAQ,GAAG,EAAE,CAAC,EAAE,UAAU;EAClD,MAAM,iBAAiB,QAAQ,MAAM,UAAU,MAAM,WAAW,EAAE,CAAC,EAAE,UAAU;EAC/E,MAAM,QAAQ,iBAAiB,KAAK,OAAO;EAC3C,IAAI,MAAM,WAAW,KAAK,QAAQ,GAAG,EAAE,CAAC,EAAE,WAAW,MAAM,QAAQ,GAAG;GACpE,MAAM,WAAW,OAAO,QAAQ;GAChC,IAAI,UAAU,OAAO,QAAQ,KAAK;IAAE,GAAG;IAAU,QAAQ;GAAG;EAC9D;EACA,MAAM,eAAe,MAAM,KAAgB,MAAM,WAAW;GAC1D;GACA,QAAQ,UAAU,MAAM,SAAS,IAAI,kBAAkB;EACzD,EAAE;EACF,OAAO,OAAO,OAAO,OAAO,GAAG,YAAY;CAC7C;CACA,OAAO,OAAO,KAAK,UAAU,GAAG,MAAM,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK,EAAE;AACtE;AAEA,SAAS,cAAc,SAA8B;CACnD,MAAM,SAAsB,CAAC;CAC7B,IAAI,QAAQ;CACZ,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,YAAY,QAAQ;EAC1B,IAAI,cAAc,QAAQ,cAAc,MAAM;EAC9C,MAAM,SAAS,cAAc,QAAQ,QAAQ,QAAQ,OAAO,OAAO,SAAS;EAC5E,OAAO,KAAK;GAAE,MAAM,QAAQ,MAAM,OAAO,KAAK;GAAG;EAAO,CAAC;EACzD,IAAI,WAAW,QAAQ,SAAS;EAChC,QAAQ,QAAQ;CAClB;CACA,OAAO,KAAK;EAAE,MAAM,QAAQ,MAAM,KAAK;EAAG,QAAQ;CAAG,CAAC;CACtD,OAAO;AACT;AAEA,SAAS,iBAAiB,SAAkC;CAC1D,IAAI,YAAY,QAAQ,YAAY,IAAI,OAAO,CAAC;CAChD,MAAM,cAAA,GAAaL,2BAAAA,cAAAA,CAAc,OAAO;CACxC,MAAM,UAAU,WAAW,SAAS,IAAI,IAAI,WAAW,MAAM,GAAG,EAAE,IAAI;CACtE,OAAO,YAAY,KAAK,CAAC,IAAI,QAAQ,MAAM,IAAI;AACjD;;;ACzKA,IAAA,oBAAA,GAAeM,iCAAAA,cAAAA,CAAc,sBAAsB;;;ACEnD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AACpE,MAAM,OAAU,UAAmC,WAAuB;CAAE,GAAG;CAAU,GAAI;AAAiB;AAE9G,MAAa,OAAA,GAAMC,iCAAAA,gBAAAA,CAAgB;CACjC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAAuC;EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGC,kBAAU,OAAO,CAAC,CAAC;CAAG,EACtG;AACF,CAAC"}
@@ -0,0 +1,5 @@
1
+ //#region generated/mcp.d.ts
2
+ export declare const mcp: import("@agimon-ai/doompi-core/mcp-facet").DoomMcpPluginDefinition;
3
+ //#endregion
4
+ export { mcp as default };
5
+ //# sourceMappingURL=mcp.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.cts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAUa,gDAAG"}
@@ -0,0 +1,5 @@
1
+ //#region generated/mcp.d.ts
2
+ export declare const mcp: import("@agimon-ai/doompi-core/mcp-facet").DoomMcpPluginDefinition;
3
+ //#endregion
4
+ export { mcp as default };
5
+ //# sourceMappingURL=mcp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAUa,gDAAG"}
@@ -0,0 +1,155 @@
1
+ import { defineMcpPlugin, defineMcpTool } from "@agimon-ai/doompi-core/mcp-facet";
2
+ import { constants } from "node:fs";
3
+ import { access, readFile, writeFile } from "node:fs/promises";
4
+ import { applyHashlineEdits, normalizeFileTag, normalizeToLf, stripBom } from "@agimon-ai/doompi-hashline";
5
+ import { computeFileTag, decodeUtf8, displayPath, resolveInputPath } from "@agimon-ai/doompi-hashline/files";
6
+ import { generateDiffString, generateUnifiedPatch, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
7
+ import { Type } from "typebox";
8
+ //#region src/schemas/editTool.ts
9
+ const HashlineRangeSchema = Type.Object({
10
+ from: Type.String({ description: "One inclusive starting anchor, for example 5#abc. Do not paste multiple lines." }),
11
+ to: Type.String({ description: "One inclusive ending anchor, for example 8#def. Do not paste multiple lines." }),
12
+ content: Type.Optional(Type.Union([Type.String({ description: "Replacement content. Empty content deletes the selected lines." }), Type.Null({ description: "Delete the selected lines." })]))
13
+ });
14
+ const EditParamsSchema = Type.Object({
15
+ path: Type.String({ description: "Path to the file returned by a compatible hashline read or grep tool." }),
16
+ hash: Type.String({ description: "Eight-character exact-byte file tag returned by read or grep." }),
17
+ edits: Type.Array(HashlineRangeSchema, {
18
+ minItems: 1,
19
+ description: "Non-overlapping inclusive ranges from the same original snapshot."
20
+ })
21
+ });
22
+ //#endregion
23
+ //#region src/services/editTool/index.ts
24
+ function createHeadlessEditTool() {
25
+ return {
26
+ name: "edit",
27
+ label: "edit",
28
+ description: "Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.",
29
+ promptSnippet: "Edit files with snapshot-bound hashline ranges",
30
+ promptGuidelines: ["Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.", "Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks."],
31
+ parameters: EditParamsSchema,
32
+ executionMode: "parallel",
33
+ execute: (_toolCallId, params, signal, _onUpdate, context) => executeHashlineEdit(params, context.cwd, signal)
34
+ };
35
+ }
36
+ function assertNotAborted(signal) {
37
+ if (signal?.aborted) throw new Error("Operation aborted");
38
+ }
39
+ async function executeHashlineEdit(params, cwd, signal) {
40
+ const absolutePath = resolveInputPath(params.path, cwd);
41
+ const expectedHash = normalizeFileTag(params.hash);
42
+ return withFileMutationQueue(absolutePath, async () => {
43
+ assertNotAborted(signal);
44
+ await access(absolutePath, constants.R_OK | constants.W_OK);
45
+ const beforeBytes = await readFile(absolutePath);
46
+ assertNotAborted(signal);
47
+ const actualHash = computeFileTag(beforeBytes);
48
+ if (actualHash !== expectedHash) throw new Error(`Stale file hash ${expectedHash}. Current hash is ${actualHash}. Re-read the file and retry.`);
49
+ const hasBom = beforeBytes.subarray(0, 3).equals(Buffer.from([
50
+ 239,
51
+ 187,
52
+ 191
53
+ ]));
54
+ const decoded = decodeUtf8(beforeBytes, params.path);
55
+ const withoutBom = stripBom(decoded);
56
+ const before = normalizeToLf(withoutBom);
57
+ const applied = applyHashlineEdits(before, params.edits);
58
+ const editedText = restoreOriginalLineEndings(withoutBom, applied.edits);
59
+ if (normalizeToLf(editedText) !== applied.content) throw new Error("Could not preserve the file line endings safely. The file was not changed.");
60
+ const diff = generateDiffString(before, applied.content);
61
+ const patch = generateUnifiedPatch(displayPath(absolutePath, cwd), before, applied.content);
62
+ const details = {
63
+ diff: diff.diff,
64
+ patch,
65
+ firstChangedLine: diff.firstChangedLine
66
+ };
67
+ assertNotAborted(signal);
68
+ if (!(await readFile(absolutePath)).equals(beforeBytes)) throw new Error("The file changed while the edit was being prepared. Re-read it and retry.");
69
+ assertNotAborted(signal);
70
+ if (applied.content !== before) {
71
+ const output = Buffer.from(`${hasBom ? "" : ""}${editedText}`, "utf8");
72
+ await writeFile(absolutePath, output);
73
+ }
74
+ const count = applied.edits.length;
75
+ const noun = count === 1 ? "range" : "ranges";
76
+ return {
77
+ content: [{
78
+ type: "text",
79
+ text: `${applied.content === before ? `No changes needed in ${params.path}.` : `Edited ${params.path} (${count} ${noun}).`} Re-read before editing it again.`
80
+ }],
81
+ details
82
+ };
83
+ });
84
+ }
85
+ function restoreOriginalLineEndings(original, edits) {
86
+ const tokens = tokenizeLines(original);
87
+ const defaultEnding = tokens.find((token) => token.ending !== "")?.ending ?? "\n";
88
+ for (const edit of [...edits].reverse()) {
89
+ const start = edit.from.line - 1;
90
+ const count = edit.to.line - edit.from.line + 1;
91
+ const removed = tokens.slice(start, start + count);
92
+ const inheritedEnding = removed.at(-1)?.ending ?? "";
93
+ const internalEnding = removed.find((token) => token.ending !== "")?.ending ?? defaultEnding;
94
+ const lines = replacementLines(edit.content);
95
+ if (lines.length === 0 && removed.at(-1)?.ending === "" && start > 0) {
96
+ const previous = tokens[start - 1];
97
+ if (previous) tokens[start - 1] = {
98
+ ...previous,
99
+ ending: ""
100
+ };
101
+ }
102
+ const replacements = lines.map((text, index) => ({
103
+ text,
104
+ ending: index === lines.length - 1 ? inheritedEnding : internalEnding
105
+ }));
106
+ tokens.splice(start, count, ...replacements);
107
+ }
108
+ return tokens.map((token) => `${token.text}${token.ending}`).join("");
109
+ }
110
+ function tokenizeLines(content) {
111
+ const tokens = [];
112
+ let start = 0;
113
+ for (let index = 0; index < content.length; index += 1) {
114
+ const character = content[index];
115
+ if (character !== "\r" && character !== "\n") continue;
116
+ const ending = character === "\r" && content[index + 1] === "\n" ? "\r\n" : character;
117
+ tokens.push({
118
+ text: content.slice(start, index),
119
+ ending
120
+ });
121
+ if (ending === "\r\n") index += 1;
122
+ start = index + 1;
123
+ }
124
+ tokens.push({
125
+ text: content.slice(start),
126
+ ending: ""
127
+ });
128
+ return tokens;
129
+ }
130
+ function replacementLines(content) {
131
+ if (content === null || content === "") return [];
132
+ const normalized = normalizeToLf(content);
133
+ const trimmed = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
134
+ return trimmed === "" ? [] : trimmed.split("\n");
135
+ }
136
+ //#endregion
137
+ //#region src/extensions/workspaces/sessions/(backend)/tool/edit.mcp.ts
138
+ var edit_mcp_default = defineMcpTool(createHeadlessEditTool);
139
+ //#endregion
140
+ //#region generated/mcp.ts
141
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
142
+ const via = (identity, value) => ({
143
+ ...identity,
144
+ ...value
145
+ });
146
+ const mcp = defineMcpPlugin({
147
+ name: "@agimon-ai/doompi-edit",
148
+ session: (context) => ({ get tools() {
149
+ return [via({ name: "edit" }, at(edit_mcp_default, context))];
150
+ } })
151
+ });
152
+ //#endregion
153
+ export { mcp as default, mcp };
154
+
155
+ //# sourceMappingURL=mcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.mjs","names":["toolEdit"],"sources":["../../src/schemas/editTool.ts","../../src/services/editTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/edit.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const HashlineRangeSchema = Type.Object({\n from: Type.String({ description: 'One inclusive starting anchor, for example 5#abc. Do not paste multiple lines.' }),\n to: Type.String({ description: 'One inclusive ending anchor, for example 8#def. Do not paste multiple lines.' }),\n content: Type.Optional(\n Type.Union([\n Type.String({ description: 'Replacement content. Empty content deletes the selected lines.' }),\n Type.Null({ description: 'Delete the selected lines.' }),\n ]),\n ),\n});\n\nexport const EditParamsSchema = Type.Object({\n path: Type.String({ description: 'Path to the file returned by a compatible hashline read or grep tool.' }),\n hash: Type.String({ description: 'Eight-character exact-byte file tag returned by read or grep.' }),\n edits: Type.Array(HashlineRangeSchema, {\n minItems: 1,\n description: 'Non-overlapping inclusive ranges from the same original snapshot.',\n }),\n});\n\nexport type HashlineRange = Static<typeof HashlineRangeSchema>;\nexport type EditParams = Static<typeof EditParamsSchema>;\n","import { constants } from 'node:fs';\nimport { access, readFile, writeFile } from 'node:fs/promises';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport {\n applyHashlineEdits,\n normalizeFileTag,\n normalizeToLf,\n stripBom,\n type PreparedHashlineEdit,\n} from '@agimon-ai/doompi-hashline';\nimport { computeFileTag, decodeUtf8, displayPath, resolveInputPath } from '@agimon-ai/doompi-hashline/files';\nimport {\n generateDiffString,\n generateUnifiedPatch,\n withFileMutationQueue,\n type EditToolDetails,\n} from '@earendil-works/pi-coding-agent';\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { EditParamsSchema, type EditParams } from '../../schemas/editTool';\n\nexport function createHashlineEditTool(): ToolDefinition<typeof EditParamsSchema> {\n return {\n name: 'edit',\n label: 'edit',\n description:\n 'Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.',\n promptSnippet: 'Edit files with snapshot-bound hashline ranges',\n promptGuidelines: [\n 'Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.',\n 'Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks.',\n 'Put multiple disjoint changes to one file in one edit call. All anchors must describe the original snapshot.',\n 'Omit content or pass an empty string to delete an inclusive range. Merge overlapping ranges before calling edit.',\n ],\n parameters: EditParamsSchema,\n executionMode: 'parallel',\n async execute(_toolCallId, params, signal, _onUpdate, ctx) {\n return executeHashlineEdit(params as EditParams, ctx.cwd, signal);\n },\n };\n}\n\nexport function createHeadlessEditTool(): DoomHeadlessTool<typeof EditParamsSchema> {\n return {\n name: 'edit',\n label: 'edit',\n description:\n 'Edit one file using its exact snapshot hash and inclusive anchors such as 5#abc from read or grep. Each from and to value must contain one anchor, not a pasted block. All ranges refer to the original snapshot. Empty or omitted content deletes a range.',\n promptSnippet: 'Edit files with snapshot-bound hashline ranges',\n promptGuidelines: [\n 'Copy path, hash, and anchors from the latest compatible read or grep result. Re-read after every successful edit.',\n 'Pass one anchor such as 5#abc in each from and to value. Do not paste tagged lines or multiline blocks.',\n ],\n parameters: EditParamsSchema,\n executionMode: 'parallel',\n execute: (\n _toolCallId: string,\n params: EditParams,\n signal: AbortSignal | undefined,\n _onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => executeHashlineEdit(params, context.cwd, signal),\n };\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n\nexport async function executeHashlineEdit(\n params: EditParams,\n cwd: string,\n signal: AbortSignal | undefined,\n): Promise<{ content: [{ type: 'text'; text: string }]; details: EditToolDetails }> {\n const absolutePath = resolveInputPath(params.path, cwd);\n const expectedHash = normalizeFileTag(params.hash);\n return withFileMutationQueue(absolutePath, async () => {\n assertNotAborted(signal);\n await access(absolutePath, constants.R_OK | constants.W_OK);\n const beforeBytes = await readFile(absolutePath);\n assertNotAborted(signal);\n\n const actualHash = computeFileTag(beforeBytes);\n if (actualHash !== expectedHash) {\n throw new Error(`Stale file hash ${expectedHash}. Current hash is ${actualHash}. Re-read the file and retry.`);\n }\n\n const hasBom = beforeBytes.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf]));\n const decoded = decodeUtf8(beforeBytes, params.path);\n const withoutBom = stripBom(decoded);\n const before = normalizeToLf(withoutBom);\n const applied = applyHashlineEdits(before, params.edits);\n const editedText = restoreOriginalLineEndings(withoutBom, applied.edits);\n if (normalizeToLf(editedText) !== applied.content) {\n throw new Error('Could not preserve the file line endings safely. The file was not changed.');\n }\n const diff = generateDiffString(before, applied.content);\n const patch = generateUnifiedPatch(displayPath(absolutePath, cwd), before, applied.content);\n const details: EditToolDetails = { diff: diff.diff, patch, firstChangedLine: diff.firstChangedLine };\n\n assertNotAborted(signal);\n const currentBytes = await readFile(absolutePath);\n if (!currentBytes.equals(beforeBytes)) {\n throw new Error('The file changed while the edit was being prepared. Re-read it and retry.');\n }\n assertNotAborted(signal);\n\n if (applied.content !== before) {\n const output = Buffer.from(`${hasBom ? '\\ufeff' : ''}${editedText}`, 'utf8');\n await writeFile(absolutePath, output);\n }\n\n const count = applied.edits.length;\n const noun = count === 1 ? 'range' : 'ranges';\n const message =\n applied.content === before ? `No changes needed in ${params.path}.` : `Edited ${params.path} (${count} ${noun}).`;\n return { content: [{ type: 'text', text: `${message} Re-read before editing it again.` }], details };\n });\n}\n\ninterface LineToken {\n readonly text: string;\n readonly ending: '\\r\\n' | '\\n' | '\\r' | '';\n}\n\nfunction restoreOriginalLineEndings(original: string, edits: readonly PreparedHashlineEdit[]): string {\n const tokens = tokenizeLines(original);\n const defaultEnding = tokens.find((token) => token.ending !== '')?.ending ?? '\\n';\n for (const edit of [...edits].reverse()) {\n const start = edit.from.line - 1;\n const count = edit.to.line - edit.from.line + 1;\n const removed = tokens.slice(start, start + count);\n const inheritedEnding = removed.at(-1)?.ending ?? '';\n const internalEnding = removed.find((token) => token.ending !== '')?.ending ?? defaultEnding;\n const lines = replacementLines(edit.content);\n if (lines.length === 0 && removed.at(-1)?.ending === '' && start > 0) {\n const previous = tokens[start - 1];\n if (previous) tokens[start - 1] = { ...previous, ending: '' };\n }\n const replacements = lines.map<LineToken>((text, index) => ({\n text,\n ending: index === lines.length - 1 ? inheritedEnding : internalEnding,\n }));\n tokens.splice(start, count, ...replacements);\n }\n return tokens.map((token) => `${token.text}${token.ending}`).join('');\n}\n\nfunction tokenizeLines(content: string): LineToken[] {\n const tokens: LineToken[] = [];\n let start = 0;\n for (let index = 0; index < content.length; index += 1) {\n const character = content[index];\n if (character !== '\\r' && character !== '\\n') continue;\n const ending = character === '\\r' && content[index + 1] === '\\n' ? '\\r\\n' : character;\n tokens.push({ text: content.slice(start, index), ending });\n if (ending === '\\r\\n') index += 1;\n start = index + 1;\n }\n tokens.push({ text: content.slice(start), ending: '' });\n return tokens;\n}\n\nfunction replacementLines(content: string | null): string[] {\n if (content === null || content === '') return [];\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport { createHeadlessEditTool } from '../../../../../services/editTool';\n\nexport default defineMcpTool(createHeadlessEditTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport toolEdit from '../src/extensions/workspaces/sessions/(backend)/tool/edit.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T => ({ ...identity, ...(value as object) }) as T;\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-edit',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [via({ name: 'edit' }, at(toolEdit, context))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-edit",
3
- "version": "0.0.1-alpha.48",
3
+ "version": "0.0.1-alpha.49",
4
4
  "description": "Snapshot-bound hashline edit tool for Pi and DoomPi.",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,15 +61,15 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@agimon-ai/doompi-core": "0.0.1-alpha.76",
65
- "@agimon-ai/doompi-hashline": "0.0.1-alpha.45",
66
- "@agimon-ai/doompi-ui": "0.0.1-alpha.76",
67
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.34",
64
+ "@agimon-ai/doompi-core": "0.0.1-alpha.77",
65
+ "@agimon-ai/doompi-hashline": "0.0.1-alpha.46",
66
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.77",
67
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.35",
68
68
  "@deepseek-ai/cordis": "4.0.2",
69
69
  "typebox": "1.3.30"
70
70
  },
71
71
  "devDependencies": {
72
- "@agimon-ai/doompi-build": "0.0.1-alpha.5",
72
+ "@agimon-ai/doompi-build": "0.0.1-alpha.6",
73
73
  "@earendil-works/pi-coding-agent": "0.85.1",
74
74
  "@earendil-works/pi-tui": "0.85.1",
75
75
  "@tanstack/react-store": "0.11.1",
@@ -98,6 +98,13 @@
98
98
  "engines": {
99
99
  "node": ">=22.19.0"
100
100
  },
101
+ "doompiMcp": {
102
+ "entry": "./generated/mcp.ts",
103
+ "dist": "./dist/extensions/mcp.mjs",
104
+ "scopes": [
105
+ "session"
106
+ ]
107
+ },
101
108
  "doompiServer": {
102
109
  "entry": "./generated/server.ts",
103
110
  "dist": "./dist/extensions/server.mjs",
@@ -123,7 +130,8 @@
123
130
  ]
124
131
  },
125
132
  "scripts": {
126
- "build": "tsdown",
133
+ "build": "tsdown && tsdown --config tsdown.mcp.config.ts",
134
+ "build:mcp": "tsdown --config tsdown.mcp.config.ts",
127
135
  "test": "vitest --run",
128
136
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
129
137
  "lint": "oxlint . && oxfmt . --check",