@agimon-ai/po-file-edit 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @agimon-ai/po-file-edit
2
+
3
+ Native session-scoped file-change tracking and review APIs for Po and po-web.
4
+
5
+ Part of the [Po distribution](https://www.npmjs.com/package/@agimon-ai/po).
6
+
7
+ The package tracks files changed through session tool calls and captures content for diffs, without
8
+ requiring a Git repository. `edit` and `write` name their file in the call, so the package can capture
9
+ the before and after content within its size and format limits. Bash calls instead trigger a bounded
10
+ tree comparison. See [What this cannot see](#what-this-cannot-see) for gaps in that coverage.
11
+
12
+ > **Alpha:** timeline and UI contracts may change between releases.
13
+
14
+ ## Requirements
15
+
16
+ - Node.js 24 or newer
17
+ - Git for diffs against `HEAD`
18
+
19
+ ## Install
20
+
21
+ `po init` and `dpi init` include File Edit in `default.packages` in `.po/modes.yaml`.
22
+ Remove it or move it to a named layer to change which major modes load it.
23
+
24
+ | Entry | Purpose |
25
+ | ------------------------------------------- | ------------------------------------------------------------ |
26
+ | `@agimon-ai/po-file-edit/extensions/server` | Native tracking, file API and changed-file channel lifecycle |
27
+ | `@agimon-ai/po-file-edit/extensions/web` | po-web review surfaces |
28
+ | `@agimon-ai/po-file-edit` | Shared library API |
29
+
30
+ ## In the cockpit
31
+
32
+ The `# files` group in the activity dock lists every file the session changed. Selecting one opens a
33
+ temporary tab holding three views of it:
34
+
35
+ - **diff**: what the session did to the file, followed by each recorded change on its own, so a file
36
+ edited four times shows four distinct diffs rather than one merged blur.
37
+ - **source**: the file as it stands, editable. Saving writes straight to disk and is refused if the
38
+ file changed since it was opened, so a hand edit cannot silently discard what the agent wrote.
39
+ - **preview**: rendered Markdown, or HTML in a sandboxed frame.
40
+
41
+ Selecting text in any view raises a comment box. Comments queue on the tab and one **send review**
42
+ builds a single message quoting each anchor, so several comments cost one turn.
43
+
44
+ Diffs here do not use Git. The package captures the file's content before an `edit` or `write` runs
45
+ and keeps it, content-addressed, beside the session timeline; both go when the session ends.
46
+
47
+ ### What this cannot see
48
+
49
+ - A file the session changed and then deleted. The dock lists things to open, so a row that can only
50
+ answer "this is gone" is left out. The timeline still holds the change, so a tab already open on
51
+ the file keeps working and says why it has nothing to show.
52
+ - A file changed by a background process that outlives its Bash call. The tree is compared when the
53
+ call ends.
54
+ - A file changed outside any tool. There is no always-on watcher.
55
+ - Content past 1 MiB, and binary files. These are listed as changed, without content.
56
+ - A file found only by comparing the tree has no baseline, so it is listed, readable and editable,
57
+ but never diffed. Rows and versions say so rather than showing an empty diff.
58
+ - The tree walk skips dependency and build directories and is capped on entries and depth, so a very
59
+ deep or very large checkout can under-report.
60
+
61
+ ## Data behavior
62
+
63
+ The timeline contains file paths, and the snapshot store holds copies of the file content the
64
+ session changed. That content can be sensitive. Both live under the Pi agent directory,
65
+ `$PI_CODING_AGENT_DIR` or `~/.po/agent/doom-file-edit`, never inside the working tree, and should be
66
+ protected with the same access controls as Pi session data.
67
+
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
69
+ each session also sweeps state that no longer belongs to anyone: a timeline and its snapshots are
70
+ removed once nothing has written to them for a week. An older build kept this state in the
71
+ repository's git common directory, and that directory is cleared away under the same rule.
72
+
73
+ The session API reads and writes only files the timeline already records, so a request cannot reach
74
+ a file this session never changed.
75
+
76
+ ## Public API
77
+
78
+ ```ts
79
+ import { createFileEditDependencies } from '@agimon-ai/po-file-edit';
80
+ import type { FileDiff, FileEditEntry } from '@agimon-ai/po-file-edit';
81
+ ```
82
+
83
+ The root also exports editor-resolution and timeline domain types for host integrations.
84
+
85
+ ## Development
86
+
87
+ ```bash
88
+ pnpm build
89
+ pnpm typecheck
90
+ pnpm test
91
+ pnpm lint
92
+ ```
93
+
94
+ Maintained by [Agimon](https://agimon.ai/about).
95
+
96
+ ## License
97
+
98
+ MIT
99
+
100
+ The native server loader lives in `src/extensions`. Execution-boundary hooks and one session activity own tracking, startup sweeps and telemetry. Final disposal drains admitted work before clearing parent-owned stores and shutting down telemetry. Terminal UI construction helpers and types are retired.
@@ -0,0 +1,156 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let _agimon_ai_po_core_apiContracts = require("@agimon-ai/po-core/apiContracts");
6
+ let typebox = require("typebox");
7
+ //#region src/schemas/apiContracts.ts
8
+ const S = typebox.Type.String();
9
+ const N = typebox.Type.Number();
10
+ const B = typebox.Type.Boolean();
11
+ const O = typebox.Type.Optional;
12
+ const literals = (values) => typebox.Type.Union(values.map((value) => typebox.Type.Literal(value)));
13
+ const Tool = literals([
14
+ "edit",
15
+ "write",
16
+ "bash",
17
+ "user"
18
+ ]);
19
+ const Hunk = typebox.Type.Object({
20
+ start: N,
21
+ rows: typebox.Type.Array(typebox.Type.Object({
22
+ marker: literals([
23
+ "+",
24
+ "-",
25
+ " "
26
+ ]),
27
+ line: N,
28
+ content: S
29
+ }))
30
+ });
31
+ const Diff = {
32
+ additions: N,
33
+ removals: N,
34
+ hunks: O(typebox.Type.Array(Hunk)),
35
+ note: O(S)
36
+ };
37
+ const Working = typebox.Type.Object({
38
+ content: S,
39
+ hash: S,
40
+ unavailable: B,
41
+ reason: O(S)
42
+ });
43
+ const FileDetailSchema = typebox.Type.Object({
44
+ path: S,
45
+ relPath: S,
46
+ versions: typebox.Type.Array(typebox.Type.Object({
47
+ index: N,
48
+ tool: Tool,
49
+ at: N,
50
+ origin: literals(["tool", "scan"]),
51
+ ...Diff
52
+ })),
53
+ cumulative: typebox.Type.Object(Diff),
54
+ working: Working
55
+ });
56
+ const FilesPayloadSchema = typebox.Type.Object({ items: typebox.Type.Array(typebox.Type.Object({
57
+ path: S,
58
+ relPath: S,
59
+ tool: Tool,
60
+ at: N,
61
+ count: N,
62
+ diffable: B
63
+ })) });
64
+ const query = [{
65
+ name: "path",
66
+ in: "query",
67
+ required: true,
68
+ schema: typebox.Type.String({ minLength: 1 })
69
+ }];
70
+ const apiContracts = (0, _agimon_ai_po_core_apiContracts.defineApiContract)({
71
+ version: 1,
72
+ dynamic: [],
73
+ http: [
74
+ {
75
+ id: "files.detail",
76
+ scope: "session",
77
+ basePath: "file-edits",
78
+ path: "/detail",
79
+ method: "GET",
80
+ authentication: "owner",
81
+ description: "Read recorded changes and current file content.",
82
+ parameters: query,
83
+ responses: (0, _agimon_ai_po_core_apiContracts.jsonApiResponses)(FileDetailSchema)
84
+ },
85
+ {
86
+ id: "files.preview",
87
+ scope: "session",
88
+ basePath: "file-edits",
89
+ path: "/preview",
90
+ method: "GET",
91
+ authentication: "owner",
92
+ description: "Read a file contained in the session directory.",
93
+ parameters: query,
94
+ responses: (0, _agimon_ai_po_core_apiContracts.jsonApiResponses)(typebox.Type.Object({
95
+ path: S,
96
+ relPath: S,
97
+ working: Working
98
+ }))
99
+ },
100
+ {
101
+ id: "files.save",
102
+ scope: "session",
103
+ basePath: "file-edits",
104
+ path: "/content",
105
+ method: "PUT",
106
+ authentication: "owner",
107
+ description: "Save a recorded file if its hash still matches.",
108
+ body: {
109
+ required: true,
110
+ contentType: "application/json",
111
+ schema: typebox.Type.Object({
112
+ path: S,
113
+ expectedHash: S,
114
+ content: S
115
+ })
116
+ },
117
+ responses: (0, _agimon_ai_po_core_apiContracts.jsonApiResponses)(typebox.Type.Object({ hash: S }))
118
+ },
119
+ {
120
+ id: "files.delete",
121
+ scope: "session",
122
+ basePath: "file-edits",
123
+ path: "/content",
124
+ method: "DELETE",
125
+ authentication: "owner",
126
+ description: "Delete a recorded file.",
127
+ parameters: query,
128
+ responses: {
129
+ "204": { description: "File deleted or already absent." },
130
+ "404": {
131
+ description: "Unrecorded file.",
132
+ schema: typebox.Type.Object({ error: S })
133
+ },
134
+ "409": {
135
+ description: "Deletion failed.",
136
+ schema: typebox.Type.Object({ error: S })
137
+ }
138
+ }
139
+ }
140
+ ],
141
+ sockets: ["global", "workspace"].map((scope) => ({
142
+ id: "files.channel",
143
+ scope,
144
+ service: "po.hub.v1",
145
+ member: "file_edits",
146
+ kind: "channel",
147
+ direction: "server-to-client",
148
+ description: "File edits channel payload in the hub frame payload property.",
149
+ input: FilesPayloadSchema
150
+ }))
151
+ });
152
+ //#endregion
153
+ exports.apiContracts = apiContracts;
154
+ exports.default = apiContracts;
155
+
156
+ //# sourceMappingURL=apiContracts.cjs.map
@@ -0,0 +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.v1',\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,53 @@
1
+ import { Type } from "typebox";
2
+ //#region src/schemas/apiContracts.d.ts
3
+ export declare const apiContracts: {
4
+ version: 1;
5
+ protocols?: Record<string, string> | undefined;
6
+ schemas?: Record<string, boolean | object> | undefined;
7
+ http: {
8
+ id: string;
9
+ scope: "global" | "session" | "workspace";
10
+ basePath?: string | undefined;
11
+ path: string;
12
+ method: "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
13
+ description: string;
14
+ availability?: string | undefined;
15
+ authentication: "device" | "none" | "owner";
16
+ parameters?: {
17
+ name: string;
18
+ in: "header" | "path" | "query";
19
+ required: boolean;
20
+ schema: boolean | object;
21
+ }[] | undefined;
22
+ body?: {
23
+ contentType: string;
24
+ contentTypes?: string[] | undefined;
25
+ required: boolean;
26
+ schema: boolean | object;
27
+ } | undefined;
28
+ responses: Record<string, {
29
+ description: string;
30
+ contentType?: string | undefined;
31
+ schema?: boolean | object | undefined;
32
+ events?: Record<string, boolean | object> | undefined;
33
+ }>;
34
+ }[];
35
+ sockets: {
36
+ id: string;
37
+ scope: "global" | "session" | "workspace";
38
+ description: string;
39
+ availability?: string | undefined;
40
+ service: string;
41
+ member: string;
42
+ direction: "client-to-server" | "server-to-client";
43
+ kind: "channel" | "method" | "state";
44
+ input: boolean | object;
45
+ output?: boolean | object | undefined;
46
+ errors?: boolean | object | undefined;
47
+ }[];
48
+ dynamic: string[];
49
+ gaps?: string[] | undefined;
50
+ };
51
+ //#endregion
52
+ export { apiContracts as default };
53
+ //# sourceMappingURL=apiContracts.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiContracts.d.mts","names":[],"sources":["../src/schemas/apiContracts.ts"],"mappings":";;qBA0Ba"}
@@ -0,0 +1,151 @@
1
+ import { defineApiContract, jsonApiResponses } from "@agimon-ai/po-core/apiContracts";
2
+ import { Type } from "typebox";
3
+ //#region src/schemas/apiContracts.ts
4
+ const S = Type.String();
5
+ const N = Type.Number();
6
+ const B = Type.Boolean();
7
+ const O = Type.Optional;
8
+ const literals = (values) => Type.Union(values.map((value) => Type.Literal(value)));
9
+ const Tool = literals([
10
+ "edit",
11
+ "write",
12
+ "bash",
13
+ "user"
14
+ ]);
15
+ const Hunk = Type.Object({
16
+ start: N,
17
+ rows: Type.Array(Type.Object({
18
+ marker: literals([
19
+ "+",
20
+ "-",
21
+ " "
22
+ ]),
23
+ line: N,
24
+ content: S
25
+ }))
26
+ });
27
+ const Diff = {
28
+ additions: N,
29
+ removals: N,
30
+ hunks: O(Type.Array(Hunk)),
31
+ note: O(S)
32
+ };
33
+ const Working = Type.Object({
34
+ content: S,
35
+ hash: S,
36
+ unavailable: B,
37
+ reason: O(S)
38
+ });
39
+ const FileDetailSchema = Type.Object({
40
+ path: S,
41
+ relPath: S,
42
+ versions: Type.Array(Type.Object({
43
+ index: N,
44
+ tool: Tool,
45
+ at: N,
46
+ origin: literals(["tool", "scan"]),
47
+ ...Diff
48
+ })),
49
+ cumulative: Type.Object(Diff),
50
+ working: Working
51
+ });
52
+ const FilesPayloadSchema = Type.Object({ items: Type.Array(Type.Object({
53
+ path: S,
54
+ relPath: S,
55
+ tool: Tool,
56
+ at: N,
57
+ count: N,
58
+ diffable: B
59
+ })) });
60
+ const query = [{
61
+ name: "path",
62
+ in: "query",
63
+ required: true,
64
+ schema: Type.String({ minLength: 1 })
65
+ }];
66
+ const apiContracts = defineApiContract({
67
+ version: 1,
68
+ dynamic: [],
69
+ http: [
70
+ {
71
+ id: "files.detail",
72
+ scope: "session",
73
+ basePath: "file-edits",
74
+ path: "/detail",
75
+ method: "GET",
76
+ authentication: "owner",
77
+ description: "Read recorded changes and current file content.",
78
+ parameters: query,
79
+ responses: jsonApiResponses(FileDetailSchema)
80
+ },
81
+ {
82
+ id: "files.preview",
83
+ scope: "session",
84
+ basePath: "file-edits",
85
+ path: "/preview",
86
+ method: "GET",
87
+ authentication: "owner",
88
+ description: "Read a file contained in the session directory.",
89
+ parameters: query,
90
+ responses: jsonApiResponses(Type.Object({
91
+ path: S,
92
+ relPath: S,
93
+ working: Working
94
+ }))
95
+ },
96
+ {
97
+ id: "files.save",
98
+ scope: "session",
99
+ basePath: "file-edits",
100
+ path: "/content",
101
+ method: "PUT",
102
+ authentication: "owner",
103
+ description: "Save a recorded file if its hash still matches.",
104
+ body: {
105
+ required: true,
106
+ contentType: "application/json",
107
+ schema: Type.Object({
108
+ path: S,
109
+ expectedHash: S,
110
+ content: S
111
+ })
112
+ },
113
+ responses: jsonApiResponses(Type.Object({ hash: S }))
114
+ },
115
+ {
116
+ id: "files.delete",
117
+ scope: "session",
118
+ basePath: "file-edits",
119
+ path: "/content",
120
+ method: "DELETE",
121
+ authentication: "owner",
122
+ description: "Delete a recorded file.",
123
+ parameters: query,
124
+ responses: {
125
+ "204": { description: "File deleted or already absent." },
126
+ "404": {
127
+ description: "Unrecorded file.",
128
+ schema: Type.Object({ error: S })
129
+ },
130
+ "409": {
131
+ description: "Deletion failed.",
132
+ schema: Type.Object({ error: S })
133
+ }
134
+ }
135
+ }
136
+ ],
137
+ sockets: ["global", "workspace"].map((scope) => ({
138
+ id: "files.channel",
139
+ scope,
140
+ service: "po.hub.v1",
141
+ member: "file_edits",
142
+ kind: "channel",
143
+ direction: "server-to-client",
144
+ description: "File edits channel payload in the hub frame payload property.",
145
+ input: FilesPayloadSchema
146
+ }))
147
+ });
148
+ //#endregion
149
+ export { apiContracts, apiContracts as default };
150
+
151
+ //# sourceMappingURL=apiContracts.mjs.map
@@ -0,0 +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.v1',\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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"}