@modelprofile.com/flexharness 10.17.0 → 10.18.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.
Files changed (104) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/errors.d.ts +4 -0
  3. package/dist_ts/errors.js +9 -1
  4. package/dist_ts/functions.toolpermissions.d.ts +24 -0
  5. package/dist_ts/functions.toolpermissions.js +38 -0
  6. package/dist_ts/index.d.ts +1 -0
  7. package/dist_ts/index.js +2 -1
  8. package/dist_ts/plugins.d.ts +1 -1
  9. package/dist_ts/plugins.js +1 -1
  10. package/dist_ts_chat_harness/classes.chatclient.d.ts +95 -0
  11. package/dist_ts_chat_harness/classes.chatclient.js +263 -0
  12. package/dist_ts_chat_harness/classes.chatrequesterror.d.ts +4 -0
  13. package/dist_ts_chat_harness/classes.chatrequesterror.js +8 -0
  14. package/dist_ts_chat_harness/classes.transcript.d.ts +1 -1
  15. package/dist_ts_chat_harness/classes.transcript.js +1 -1
  16. package/dist_ts_chat_harness/functions.intents.d.ts +1 -2
  17. package/dist_ts_chat_harness/functions.intents.js +1 -1
  18. package/dist_ts_chat_harness/functions.mapping.d.ts +1 -1
  19. package/dist_ts_chat_harness/functions.mapping.js +1 -1
  20. package/dist_ts_chat_harness/index.d.ts +3 -0
  21. package/dist_ts_chat_harness/index.js +3 -1
  22. package/dist_ts_chat_harness/interfaces.chat.d.ts +136 -0
  23. package/dist_ts_chat_harness/interfaces.chat.js +2 -0
  24. package/dist_ts_chat_harness/plugins.js +1 -1
  25. package/dist_ts_chat_harness_server/classes.chathub.d.ts +78 -0
  26. package/dist_ts_chat_harness_server/classes.chathub.js +332 -0
  27. package/dist_ts_chat_harness_server/classes.chatsubscription.d.ts +55 -0
  28. package/dist_ts_chat_harness_server/classes.chatsubscription.js +236 -0
  29. package/dist_ts_chat_harness_server/classes.sessionlists.d.ts +21 -0
  30. package/dist_ts_chat_harness_server/classes.sessionlists.js +70 -0
  31. package/dist_ts_chat_harness_server/classes.turngate.d.ts +38 -0
  32. package/dist_ts_chat_harness_server/classes.turngate.js +207 -0
  33. package/dist_ts_chat_harness_server/errors.d.ts +5 -0
  34. package/dist_ts_chat_harness_server/errors.js +9 -0
  35. package/dist_ts_chat_harness_server/functions.errorprojector.d.ts +11 -0
  36. package/dist_ts_chat_harness_server/functions.errorprojector.js +32 -0
  37. package/dist_ts_chat_harness_server/functions.validation.d.ts +18 -0
  38. package/dist_ts_chat_harness_server/functions.validation.js +88 -0
  39. package/dist_ts_chat_harness_server/index.d.ts +4 -0
  40. package/dist_ts_chat_harness_server/index.js +4 -0
  41. package/dist_ts_chat_harness_server/interfaces.d.ts +67 -0
  42. package/dist_ts_chat_harness_server/interfaces.js +2 -0
  43. package/dist_ts_chat_harness_server/plugins.d.ts +7 -0
  44. package/dist_ts_chat_harness_server/plugins.js +6 -0
  45. package/dist_ts_chat_harness_typedsocket/classes.clienttransport.d.ts +22 -0
  46. package/dist_ts_chat_harness_typedsocket/classes.clienttransport.js +55 -0
  47. package/dist_ts_chat_harness_typedsocket/classes.serverbinding.d.ts +46 -0
  48. package/dist_ts_chat_harness_typedsocket/classes.serverbinding.js +110 -0
  49. package/dist_ts_chat_harness_typedsocket/index.d.ts +3 -0
  50. package/dist_ts_chat_harness_typedsocket/index.js +3 -0
  51. package/dist_ts_chat_harness_typedsocket/interfaces.requests.d.ts +38 -0
  52. package/dist_ts_chat_harness_typedsocket/interfaces.requests.js +2 -0
  53. package/dist_ts_chat_harness_typedsocket/plugins.d.ts +9 -0
  54. package/dist_ts_chat_harness_typedsocket/plugins.js +8 -0
  55. package/dist_ts_tools/tool.filesystem.d.ts +2 -0
  56. package/dist_ts_tools/tool.filesystem.js +42 -1
  57. package/dist_ts_tools_node/classes.workspacereversion.d.ts +48 -0
  58. package/dist_ts_tools_node/classes.workspacereversion.js +202 -0
  59. package/dist_ts_tools_node/functions.fileimages.d.ts +22 -0
  60. package/dist_ts_tools_node/functions.fileimages.js +87 -0
  61. package/dist_ts_tools_node/index.d.ts +3 -1
  62. package/dist_ts_tools_node/index.js +2 -1
  63. package/dist_ts_tools_node/plugins.d.ts +1 -0
  64. package/dist_ts_tools_node/tool.context.d.ts +20 -0
  65. package/dist_ts_tools_node/tool.context.js +25 -4
  66. package/package.json +28 -4
  67. package/ts/00_commitinfo_data.ts +1 -1
  68. package/ts/errors.ts +10 -0
  69. package/ts/functions.toolpermissions.ts +60 -0
  70. package/ts/index.ts +6 -0
  71. package/ts/plugins.ts +1 -0
  72. package/ts_chat_harness/classes.chatclient.ts +343 -0
  73. package/ts_chat_harness/classes.chatrequesterror.ts +7 -0
  74. package/ts_chat_harness/classes.transcript.ts +1 -1
  75. package/ts_chat_harness/functions.intents.ts +2 -2
  76. package/ts_chat_harness/functions.mapping.ts +1 -1
  77. package/ts_chat_harness/index.ts +20 -0
  78. package/ts_chat_harness/interfaces.chat.ts +108 -0
  79. package/ts_chat_harness/readme.md +24 -0
  80. package/ts_chat_harness_server/classes.chathub.ts +400 -0
  81. package/ts_chat_harness_server/classes.chatsubscription.ts +257 -0
  82. package/ts_chat_harness_server/classes.sessionlists.ts +90 -0
  83. package/ts_chat_harness_server/classes.turngate.ts +247 -0
  84. package/ts_chat_harness_server/errors.ts +9 -0
  85. package/ts_chat_harness_server/functions.errorprojector.ts +40 -0
  86. package/ts_chat_harness_server/functions.validation.ts +104 -0
  87. package/ts_chat_harness_server/index.ts +19 -0
  88. package/ts_chat_harness_server/interfaces.ts +92 -0
  89. package/ts_chat_harness_server/plugins.ts +62 -0
  90. package/ts_chat_harness_server/readme.md +91 -0
  91. package/ts_chat_harness_typedsocket/classes.clienttransport.ts +87 -0
  92. package/ts_chat_harness_typedsocket/classes.serverbinding.ts +172 -0
  93. package/ts_chat_harness_typedsocket/index.ts +17 -0
  94. package/ts_chat_harness_typedsocket/interfaces.requests.ts +40 -0
  95. package/ts_chat_harness_typedsocket/plugins.ts +28 -0
  96. package/ts_chat_harness_typedsocket/readme.md +57 -0
  97. package/ts_tools/readme.md +10 -0
  98. package/ts_tools/tool.filesystem.ts +55 -0
  99. package/ts_tools_node/classes.workspacereversion.ts +255 -0
  100. package/ts_tools_node/functions.fileimages.ts +89 -0
  101. package/ts_tools_node/index.ts +7 -1
  102. package/ts_tools_node/plugins.ts +11 -0
  103. package/ts_tools_node/readme.md +27 -0
  104. package/ts_tools_node/tool.context.ts +49 -3
@@ -0,0 +1,202 @@
1
+ import * as plugins from './plugins.js';
2
+ import { fileImageBytes, fileImagesEqual, missingAncestors, readFileImage, removeEmptyDirectories, writeFileImage, } from './functions.fileimages.js';
3
+ const DEFAULT_MAX_CAPTURE_BYTES = 64 * 1024 * 1024;
4
+ const isCaptureReference = (valueArg) => !!valueArg
5
+ && typeof valueArg === 'object'
6
+ && !Array.isArray(valueArg)
7
+ && typeof valueArg.captureId === 'string';
8
+ /**
9
+ * Turn reversion for the local filesystem tools, held in memory: FlexHarness `/undo` and `/redo`
10
+ * move a turn's file changes together with its transcript.
11
+ *
12
+ * Give each run's `createLocalToolExecutionContext` the observer `observerFor(context.runId)`. The
13
+ * first change of a file in a turn records what the file held before; `finalize` records what it
14
+ * holds after. Undo puts the before images back and redo the after images, each only while every
15
+ * file still holds what the other direction left, so a later edit of the user is never overwritten.
16
+ * A turn that ran a shell command is nonrevertible, since the command may have changed files no
17
+ * observer saw.
18
+ *
19
+ * Captures live as long as the process. A capture FlexHarness asks about after a restart is
20
+ * `missing`, and undoing past it fails without changing anything. Only root-session runs are
21
+ * captured; a change of a run without a prepared capture is refused.
22
+ */
23
+ export class InMemoryWorkspaceReversion {
24
+ protocolVersion = 2;
25
+ workspace;
26
+ maxCaptureBytes;
27
+ captures = new Map();
28
+ captureIdsByRun = new Map();
29
+ constructor(optionsArg) {
30
+ this.workspace = { id: optionsArg.workspace.id, label: optionsArg.workspace.label };
31
+ this.maxCaptureBytes = optionsArg.maxCaptureBytes ?? DEFAULT_MAX_CAPTURE_BYTES;
32
+ if (!Number.isSafeInteger(this.maxCaptureBytes) || this.maxCaptureBytes <= 0) {
33
+ throw new Error('maxCaptureBytes must be a positive integer.');
34
+ }
35
+ }
36
+ /** The observer of one run's file changes and shell commands. */
37
+ observerFor(runIdArg) {
38
+ return {
39
+ beforeFileChange: (changeArg) => this.recordChange(runIdArg, changeArg),
40
+ beforeShellCommand: () => {
41
+ this.markNonrevertible(this.requirePreparedCapture(runIdArg), 'shell-command');
42
+ },
43
+ };
44
+ }
45
+ prepare(contextArg) {
46
+ contextArg.signal.throwIfAborted();
47
+ if (this.captures.has(contextArg.captureId))
48
+ return;
49
+ this.captures.set(contextArg.captureId, {
50
+ captureId: contextArg.captureId,
51
+ runId: contextArg.runId,
52
+ state: 'prepared',
53
+ files: new Map(),
54
+ bytes: 0,
55
+ appliedOperations: new Set(),
56
+ uncertainOperations: new Set(),
57
+ });
58
+ this.captureIdsByRun.set(contextArg.runId, contextArg.captureId);
59
+ }
60
+ inspectCapture(contextArg) {
61
+ const capture = this.captures.get(contextArg.captureId);
62
+ if (!capture)
63
+ return { status: 'missing' };
64
+ return capture.outcome ? { status: 'finalized', outcome: capture.outcome } : { status: 'prepared' };
65
+ }
66
+ async finalize(contextArg) {
67
+ contextArg.signal.throwIfAborted();
68
+ const capture = this.requireCapture(contextArg.captureId);
69
+ if (capture.outcome)
70
+ return capture.outcome;
71
+ const reference = { captureId: capture.captureId };
72
+ if (!capture.nonrevertibleReason) {
73
+ for (const [filePath, record] of capture.files) {
74
+ record.after = await readFileImage(filePath);
75
+ if (fileImagesEqual(record.before, record.after))
76
+ capture.files.delete(filePath);
77
+ }
78
+ }
79
+ capture.state = 'finalized';
80
+ if (capture.nonrevertibleReason) {
81
+ capture.outcome = {
82
+ disposition: 'nonrevertible',
83
+ reference: { ...reference },
84
+ reasonCode: capture.nonrevertibleReason,
85
+ affectedWorkspaces: [this.workspace],
86
+ };
87
+ }
88
+ else if (capture.files.size === 0) {
89
+ capture.outcome = { disposition: 'no-change', reference: { ...reference } };
90
+ }
91
+ else {
92
+ capture.outcome = { disposition: 'revertible', reference: { ...reference }, affectedWorkspaces: [this.workspace] };
93
+ }
94
+ this.captureIdsByRun.delete(capture.runId);
95
+ return capture.outcome;
96
+ }
97
+ async apply(contextArg) {
98
+ contextArg.signal.throwIfAborted();
99
+ const capture = this.captureOf(contextArg.reference);
100
+ if (capture.outcome?.disposition !== 'revertible') {
101
+ throw new Error('This turn has no revertible workspace changes.');
102
+ }
103
+ const records = [...capture.files.entries()];
104
+ const expected = (recordArg) => (contextArg.direction === 'undo' ? recordArg.after : recordArg.before);
105
+ const target = (recordArg) => (contextArg.direction === 'undo' ? recordArg.before : recordArg.after);
106
+ for (const [filePath, record] of records) {
107
+ if (!fileImagesEqual(await readFileImage(filePath), expected(record))) {
108
+ throw new Error(`"${filePath}" changed after the turn; its changes were not ${contextArg.direction === 'undo' ? 'undone' : 'redone'}.`);
109
+ }
110
+ }
111
+ const written = [];
112
+ try {
113
+ for (const entry of records) {
114
+ await writeFileImage(entry[0], target(entry[1]));
115
+ written.push(entry);
116
+ }
117
+ }
118
+ catch (error) {
119
+ await this.rollBack(written, expected, contextArg.operationId, capture);
120
+ throw error;
121
+ }
122
+ if (contextArg.direction === 'undo') {
123
+ for (const [, record] of records)
124
+ await removeEmptyDirectories(record.createdDirectories);
125
+ }
126
+ capture.appliedOperations.add(contextArg.operationId);
127
+ }
128
+ inspectApply(contextArg) {
129
+ const capture = this.captures.get(isCaptureReference(contextArg.reference) ? contextArg.reference.captureId : '');
130
+ if (!capture)
131
+ return { status: 'not-applied' };
132
+ if (capture.appliedOperations.has(contextArg.operationId))
133
+ return { status: 'applied' };
134
+ if (capture.uncertainOperations.has(contextArg.operationId))
135
+ return { status: 'unknown' };
136
+ return { status: 'not-applied' };
137
+ }
138
+ release(contextArg) {
139
+ const captureId = isCaptureReference(contextArg.reference) ? contextArg.reference.captureId : contextArg.captureId;
140
+ const capture = this.captures.get(captureId);
141
+ if (!capture)
142
+ return;
143
+ this.captures.delete(captureId);
144
+ if (this.captureIdsByRun.get(capture.runId) === captureId)
145
+ this.captureIdsByRun.delete(capture.runId);
146
+ }
147
+ async recordChange(runIdArg, changeArg) {
148
+ const capture = this.requirePreparedCapture(runIdArg);
149
+ if (capture.nonrevertibleReason || capture.files.has(changeArg.path))
150
+ return;
151
+ const before = await readFileImage(changeArg.path);
152
+ const createdDirectories = before.kind === 'absent' && changeArg.kind === 'write'
153
+ ? await missingAncestors(changeArg.path)
154
+ : [];
155
+ if (capture.files.has(changeArg.path))
156
+ return;
157
+ capture.bytes += fileImageBytes(before);
158
+ if (capture.bytes > this.maxCaptureBytes) {
159
+ this.markNonrevertible(capture, 'capture-too-large');
160
+ return;
161
+ }
162
+ capture.files.set(changeArg.path, { before, createdDirectories });
163
+ }
164
+ markNonrevertible(captureArg, reasonArg) {
165
+ captureArg.nonrevertibleReason ??= reasonArg;
166
+ captureArg.files.clear();
167
+ captureArg.bytes = 0;
168
+ }
169
+ async rollBack(writtenArg, expectedArg, operationIdArg, captureArg) {
170
+ try {
171
+ for (const [filePath, record] of writtenArg.reverse())
172
+ await writeFileImage(filePath, expectedArg(record));
173
+ }
174
+ catch {
175
+ // the workspace holds part of the change; FlexHarness learns that the outcome is unknown
176
+ captureArg.uncertainOperations.add(operationIdArg);
177
+ }
178
+ }
179
+ requirePreparedCapture(runIdArg) {
180
+ const captureId = this.captureIdsByRun.get(runIdArg);
181
+ const capture = captureId ? this.captures.get(captureId) : undefined;
182
+ if (!capture || capture.state !== 'prepared') {
183
+ throw new Error(`No workspace capture is open for run "${runIdArg}"; the change was refused.`);
184
+ }
185
+ return capture;
186
+ }
187
+ requireCapture(captureIdArg) {
188
+ const capture = this.captures.get(captureIdArg);
189
+ if (!capture)
190
+ throw new Error(`The workspace capture "${captureIdArg}" is not held.`);
191
+ return capture;
192
+ }
193
+ captureOf(referenceArg) {
194
+ if (!isCaptureReference(referenceArg))
195
+ throw new Error('The workspace capture reference is malformed.');
196
+ const capture = this.captures.get(referenceArg.captureId);
197
+ if (!capture)
198
+ throw new Error('The workspace changes of this turn are no longer held in memory.');
199
+ return capture;
200
+ }
201
+ }
202
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy53b3Jrc3BhY2VyZXZlcnNpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190b29sc19ub2RlL2NsYXNzZXMud29ya3NwYWNlcmV2ZXJzaW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFDTCxjQUFjLEVBQ2QsZUFBZSxFQUNmLGdCQUFnQixFQUNoQixhQUFhLEVBQ2Isc0JBQXNCLEVBQ3RCLGNBQWMsR0FFZixNQUFNLDJCQUEyQixDQUFDO0FBcUNuQyxNQUFNLHlCQUF5QixHQUFHLEVBQUUsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDO0FBRW5ELE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxRQUE0QixFQUFzRCxFQUFFLENBQzlHLENBQUMsQ0FBQyxRQUFRO09BQ1AsT0FBTyxRQUFRLEtBQUssUUFBUTtPQUM1QixDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO09BQ3hCLE9BQU8sUUFBUSxDQUFDLFNBQVMsS0FBSyxRQUFRLENBQUM7QUFFNUM7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLE9BQU8sMEJBQTBCO0lBQ3JCLGVBQWUsR0FBRyxDQUFDLENBQUM7SUFFbkIsU0FBUyxDQUFpQztJQUMxQyxlQUFlLENBQVM7SUFDeEIsUUFBUSxHQUFHLElBQUksR0FBRyxFQUFvQixDQUFDO0lBQ3ZDLGVBQWUsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUU3RCxZQUFZLFVBQThDO1FBQ3hELElBQUksQ0FBQyxTQUFTLEdBQUcsRUFBRSxFQUFFLEVBQUUsVUFBVSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLFVBQVUsQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDcEYsSUFBSSxDQUFDLGVBQWUsR0FBRyxVQUFVLENBQUMsZUFBZSxJQUFJLHlCQUF5QixDQUFDO1FBQy9FLElBQUksQ0FBQyxN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
@@ -0,0 +1,22 @@
1
+ /** What one path holds at a moment: nothing, a regular file, or a symbolic link. */
2
+ export type TFileImage = {
3
+ kind: 'absent';
4
+ } | {
5
+ kind: 'file';
6
+ bytes: Buffer;
7
+ mode: number;
8
+ } | {
9
+ kind: 'symlink';
10
+ target: string;
11
+ };
12
+ /** Reads what the path holds, without following a final symbolic link. */
13
+ export declare const readFileImage: (pathArg: string) => Promise<TFileImage>;
14
+ /** The bytes an image keeps in memory. */
15
+ export declare const fileImageBytes: (imageArg: TFileImage) => number;
16
+ export declare const fileImagesEqual: (leftArg: TFileImage, rightArg: TFileImage) => boolean;
17
+ /** Puts the image at the path, creating parent directories as needed. */
18
+ export declare const writeFileImage: (pathArg: string, imageArg: TFileImage) => Promise<void>;
19
+ /** The ancestors of the path that do not exist yet, deepest first: a write would create them. */
20
+ export declare const missingAncestors: (pathArg: string) => Promise<string[]>;
21
+ /** Removes the directories, deepest first, as far as they are empty. */
22
+ export declare const removeEmptyDirectories: (directoriesArg: readonly string[]) => Promise<void>;
@@ -0,0 +1,87 @@
1
+ import * as plugins from './plugins.js';
2
+ const isMissing = (error) => {
3
+ const code = error.code;
4
+ return code === 'ENOENT' || code === 'ENOTDIR';
5
+ };
6
+ /** Reads what the path holds, without following a final symbolic link. */
7
+ export const readFileImage = async (pathArg) => {
8
+ let stats;
9
+ try {
10
+ stats = await plugins.fs.promises.lstat(pathArg);
11
+ }
12
+ catch (error) {
13
+ if (isMissing(error))
14
+ return { kind: 'absent' };
15
+ throw error;
16
+ }
17
+ if (stats.isSymbolicLink())
18
+ return { kind: 'symlink', target: await plugins.fs.promises.readlink(pathArg) };
19
+ if (stats.isFile())
20
+ return { kind: 'file', bytes: await plugins.fs.promises.readFile(pathArg), mode: stats.mode & 0o7777 };
21
+ throw new Error(`"${pathArg}" is neither a file nor a symbolic link.`);
22
+ };
23
+ /** The bytes an image keeps in memory. */
24
+ export const fileImageBytes = (imageArg) => imageArg.kind === 'file'
25
+ ? imageArg.bytes.byteLength
26
+ : imageArg.kind === 'symlink'
27
+ ? Buffer.byteLength(imageArg.target)
28
+ : 0;
29
+ export const fileImagesEqual = (leftArg, rightArg) => {
30
+ if (leftArg.kind === 'absent' || rightArg.kind === 'absent')
31
+ return leftArg.kind === rightArg.kind;
32
+ if (leftArg.kind === 'symlink' || rightArg.kind === 'symlink') {
33
+ return leftArg.kind === 'symlink' && rightArg.kind === 'symlink' && leftArg.target === rightArg.target;
34
+ }
35
+ return leftArg.bytes.equals(rightArg.bytes);
36
+ };
37
+ /** Puts the image at the path, creating parent directories as needed. */
38
+ export const writeFileImage = async (pathArg, imageArg) => {
39
+ if (imageArg.kind === 'absent') {
40
+ await plugins.fs.promises.rm(pathArg, { force: true });
41
+ return;
42
+ }
43
+ await plugins.fs.promises.mkdir(plugins.path.dirname(pathArg), { recursive: true });
44
+ if (imageArg.kind === 'symlink') {
45
+ await plugins.fs.promises.rm(pathArg, { force: true });
46
+ await plugins.fs.promises.symlink(imageArg.target, pathArg);
47
+ return;
48
+ }
49
+ if ((await readFileImage(pathArg)).kind === 'symlink')
50
+ await plugins.fs.promises.rm(pathArg);
51
+ await plugins.fs.promises.writeFile(pathArg, imageArg.bytes);
52
+ await plugins.fs.promises.chmod(pathArg, imageArg.mode);
53
+ };
54
+ /** The ancestors of the path that do not exist yet, deepest first: a write would create them. */
55
+ export const missingAncestors = async (pathArg) => {
56
+ const missing = [];
57
+ let current = plugins.path.dirname(pathArg);
58
+ while (current !== plugins.path.dirname(current)) {
59
+ try {
60
+ await plugins.fs.promises.lstat(current);
61
+ break;
62
+ }
63
+ catch (error) {
64
+ if (!isMissing(error))
65
+ throw error;
66
+ missing.push(current);
67
+ current = plugins.path.dirname(current);
68
+ }
69
+ }
70
+ return missing;
71
+ };
72
+ /** Removes the directories, deepest first, as far as they are empty. */
73
+ export const removeEmptyDirectories = async (directoriesArg) => {
74
+ for (const directory of directoriesArg) {
75
+ try {
76
+ await plugins.fs.promises.rmdir(directory);
77
+ }
78
+ catch (error) {
79
+ const code = error.code;
80
+ if (code === 'ENOTEMPTY' || code === 'EEXIST')
81
+ return;
82
+ if (!isMissing(error))
83
+ throw error;
84
+ }
85
+ }
86
+ };
87
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnVuY3Rpb25zLmZpbGVpbWFnZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190b29sc19ub2RlL2Z1bmN0aW9ucy5maWxlaW1hZ2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBUXhDLE1BQU0sU0FBUyxHQUFHLENBQUMsS0FBYyxFQUFXLEVBQUU7SUFDNUMsTUFBTSxJQUFJLEdBQUksS0FBK0IsQ0FBQyxJQUFJLENBQUM7SUFDbkQsT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxTQUFTLENBQUM7QUFDakQsQ0FBQyxDQUFDO0FBRUYsMEVBQTBFO0FBQzFFLE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBRyxLQUFLLEVBQUUsT0FBZSxFQUF1QixFQUFFO0lBQzFFLElBQUksS0FBdUIsQ0FBQztJQUM1QixJQUFJLENBQUM7UUFDSCxLQUFLLEdBQUcsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkQsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLFNBQVMsQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQ2hELE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztJQUNELElBQUksS0FBSyxDQUFDLGNBQWMsRUFBRTtRQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO0lBQzVHLElBQUksS0FBSyxDQUFDLE1BQU0sRUFBRTtRQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEs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
@@ -1,5 +1,7 @@
1
1
  export { createLocalToolExecutionContext } from './tool.context.js';
2
- export type { ILocalToolExecutionContextOptions } from './tool.context.js';
2
+ export type { ILocalToolExecutionContextOptions, IToolFileChange, IToolFileChangeObserver, } from './tool.context.js';
3
+ export { InMemoryWorkspaceReversion } from './classes.workspacereversion.js';
4
+ export type { IInMemoryWorkspaceReversionOptions } from './classes.workspacereversion.js';
3
5
  export { FileToolJobStore } from './tool.persistence.js';
4
6
  export type { IFileToolJobStoreOptions } from './tool.persistence.js';
5
7
  export * from './tools.local.js';
@@ -1,4 +1,5 @@
1
1
  export { createLocalToolExecutionContext } from './tool.context.js';
2
+ export { InMemoryWorkspaceReversion } from './classes.workspacereversion.js';
2
3
  export { FileToolJobStore } from './tool.persistence.js';
3
4
  export * from './tools.local.js';
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190b29sc19ub2RlL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRXBFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXpELGNBQWMsa0JBQWtCLENBQUMifQ==
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190b29sc19ub2RlL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBTXBFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRTdFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXpELGNBQWMsa0JBQWtCLENBQUMifQ==
@@ -11,3 +11,4 @@ import { createFilesystemTools, createShellTools } from '@modelprofile.com/flexh
11
11
  export { createFilesystemTools, createShellTools };
12
12
  export type { ICreateFilesystemToolsOptions, ICreateShellToolsOptions, ToolSet } from '@modelprofile.com/flexharness/tools';
13
13
  export type { IBrowserToolInput, IShellCommand, IShellFinishedResult, IShellStartedResult, IToolBrowserContext, IToolBrowserRunOptions, IToolExecutionContext, IToolFilesystemContext, IToolFilesystemListOptions, IToolFilesystemReadOptions, IToolInvocationOptions, IToolJobContext, IToolJobEvent, IToolJobHandle, IToolJobOutputEvent, IToolJobRequest, IToolJobSnapshot, IToolJobState, IToolJobStore, IToolPermissionRequest, IToolRunOptions, IToolShellContext, IToolShellResult, TBrowserToolAction, TToolJobEvent, TToolJobEventListener, } from '@modelprofile.com/flexharness-agent';
14
+ export type { IFlexAffectedWorkspace, IFlexTurnReversionApplyContext, IFlexTurnReversionApplyInspection, IFlexTurnReversionBaseContext, IFlexTurnReversionProviderV2, IFlexTurnReversionReleaseContext, TFlexTurnReversionCaptureInspectionV2, TFlexTurnReversionFinalizedOutcome, TJsonValue, } from '@modelprofile.com/flexharness';
@@ -1,4 +1,22 @@
1
1
  import type { IToolJobStore, IToolPermissionRequest, IToolJobState, IToolExecutionContext } from './plugins.js';
2
+ /** A file a tool is about to change, named by its canonical path. */
3
+ export interface IToolFileChange {
4
+ kind: 'write' | 'delete';
5
+ /**
6
+ * The canonical absolute path the operation changes. A write follows a symlink to its target; a
7
+ * delete removes the link itself, so its path is the link's.
8
+ */
9
+ path: string;
10
+ }
11
+ /**
12
+ * Sees every change the local filesystem tools make, after the path checks and before the change,
13
+ * and every shell command before it starts. A rejection refuses the operation.
14
+ */
15
+ export interface IToolFileChangeObserver {
16
+ beforeFileChange(change: IToolFileChange): Promise<void> | void;
17
+ /** A shell command or background job may change files the observer does not see. */
18
+ beforeShellCommand(): Promise<void> | void;
19
+ }
2
20
  export interface ILocalToolExecutionContextOptions {
3
21
  cwd?: string;
4
22
  rootDir?: string;
@@ -7,6 +25,8 @@ export interface ILocalToolExecutionContextOptions {
7
25
  abortSignal?: AbortSignal;
8
26
  /** Guards each local filesystem operation or shell side effect. */
9
27
  beforeOperation?: () => Promise<void> | void;
28
+ /** Observes every file change and shell command, for example to capture a turn for undo. */
29
+ fileChangeObserver?: IToolFileChangeObserver;
10
30
  /** Hard combined stdout/stderr capture limit for each local shell command. Default: 1 MiB. */
11
31
  maxShellOutputBytes?: number;
12
32
  /** Number of terminal background job snapshots retained for get/list. Default: 100. */