@mearl/client 2.20.6 → 2.22.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 (130) hide show
  1. package/README.md +153 -3
  2. package/dist/actionTimeouts.js +1 -1
  3. package/dist/artifacts.d.ts +1 -1
  4. package/dist/browserRegistry.d.ts +1 -1
  5. package/dist/check.d.ts +1 -1
  6. package/dist/cli.js +14 -8
  7. package/dist/commands.d.ts +23 -14
  8. package/dist/commands.js +24 -4
  9. package/dist/{generated-app-profile-definition.d.ts → generated/app-profile-definition.d.ts} +2 -2
  10. package/dist/{generated-app-profile-runtime-types.d.ts → generated/app-profile-runtime-types.d.ts} +2 -2
  11. package/dist/{generated-browser-action-protocol.d.ts → generated/browser-action-protocol.d.ts} +11 -1
  12. package/dist/{generated-browser-action-protocol.js → generated/browser-action-protocol.js} +3 -0
  13. package/dist/generated/plugin-diagnostics.d.ts +110 -0
  14. package/dist/generated/plugin-diagnostics.js +1 -0
  15. package/dist/generated/plugin-execution.d.ts +27 -0
  16. package/dist/generated/plugin-execution.js +1 -0
  17. package/dist/generated/plugin-models.d.ts +64 -0
  18. package/dist/generated/plugin-models.js +1 -0
  19. package/dist/generated/plugin-pages.d.ts +76 -0
  20. package/dist/generated/plugin-pages.js +59 -0
  21. package/dist/generated/plugin-recovery.d.ts +32 -0
  22. package/dist/generated/plugin-recovery.js +1 -0
  23. package/dist/generated/plugin-state.d.ts +50 -0
  24. package/dist/generated/plugin-state.js +1 -0
  25. package/dist/generated/plugin-tasks.d.ts +74 -0
  26. package/dist/generated/plugin-tasks.js +7 -0
  27. package/dist/generated/toolbox-icon.d.ts +10 -0
  28. package/dist/generated/toolbox-icon.js +37 -0
  29. package/dist/index.d.ts +15 -4
  30. package/dist/index.js +7 -1
  31. package/dist/pageEvalArguments.d.ts +1 -1
  32. package/dist/pageEvalArguments.js +1 -1
  33. package/dist/plugin-task-host.d.ts +1 -0
  34. package/dist/plugin-task-host.js +14 -0
  35. package/dist/plugin-worker.d.ts +1 -0
  36. package/dist/plugin-worker.js +17 -0
  37. package/dist/plugin.d.ts +1 -65
  38. package/dist/plugin.js +1 -391
  39. package/dist/pluginCliOutput.d.ts +2 -0
  40. package/dist/pluginCliOutput.js +7 -0
  41. package/dist/pluginModelSessionWorker.d.ts +23 -0
  42. package/dist/pluginModelSessionWorker.js +60 -0
  43. package/dist/plugins/clients/pluginHostConnection.d.ts +35 -0
  44. package/dist/plugins/clients/pluginHostConnection.js +256 -0
  45. package/dist/plugins/clients/pluginModels.d.ts +17 -0
  46. package/dist/plugins/clients/pluginModels.js +47 -0
  47. package/dist/plugins/clients/pluginPages.d.ts +21 -0
  48. package/dist/plugins/clients/pluginPages.js +46 -0
  49. package/dist/plugins/clients/pluginState.d.ts +26 -0
  50. package/dist/plugins/clients/pluginState.js +28 -0
  51. package/dist/plugins/clients/pluginTasks.d.ts +51 -0
  52. package/dist/plugins/clients/pluginTasks.js +181 -0
  53. package/dist/{officialPluginInstaller.js → plugins/officialPluginInstaller.js} +1 -3
  54. package/dist/{pluginPageBundle.js → plugins/pluginPageBundle.js} +74 -17
  55. package/dist/{pluginRegistry.d.ts → plugins/pluginRegistry.d.ts} +3 -4
  56. package/dist/{pluginRegistry.js → plugins/pluginRegistry.js} +16 -15
  57. package/dist/plugins/pluginTaskProtocol.d.ts +47 -0
  58. package/dist/plugins/pluginTaskProtocol.js +1 -0
  59. package/dist/plugins/pluginTaskWire.d.ts +4 -0
  60. package/dist/plugins/pluginTaskWire.js +42 -0
  61. package/dist/plugins/runtime/pluginCommandWorker.d.ts +2 -0
  62. package/dist/plugins/runtime/pluginCommandWorker.js +174 -0
  63. package/dist/plugins/runtime/pluginDiagnostics.d.ts +26 -0
  64. package/dist/plugins/runtime/pluginDiagnostics.js +309 -0
  65. package/dist/plugins/runtime/pluginExecution.d.ts +21 -0
  66. package/dist/plugins/runtime/pluginExecution.js +43 -0
  67. package/dist/plugins/runtime/pluginExecutor.d.ts +29 -0
  68. package/dist/plugins/runtime/pluginExecutor.js +223 -0
  69. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +55 -0
  70. package/dist/plugins/runtime/pluginExecutorProtocol.js +47 -0
  71. package/dist/plugins/runtime/pluginExecutorSupervisor.d.ts +4 -0
  72. package/dist/plugins/runtime/pluginExecutorSupervisor.js +63 -0
  73. package/dist/plugins/runtime/pluginHost.d.ts +20 -0
  74. package/dist/plugins/runtime/pluginHost.js +53 -0
  75. package/dist/plugins/runtime/pluginHostFiles.d.ts +1 -0
  76. package/dist/plugins/runtime/pluginHostFiles.js +21 -0
  77. package/dist/plugins/runtime/pluginHostPaths.d.ts +11 -0
  78. package/dist/plugins/runtime/pluginHostPaths.js +22 -0
  79. package/dist/plugins/runtime/pluginHostServer.d.ts +7 -0
  80. package/dist/plugins/runtime/pluginHostServer.js +252 -0
  81. package/dist/plugins/runtime/pluginModelCatalog.d.ts +14 -0
  82. package/dist/plugins/runtime/pluginModelCatalog.js +51 -0
  83. package/dist/plugins/runtime/pluginModelSessions.d.ts +16 -0
  84. package/dist/plugins/runtime/pluginModelSessions.js +234 -0
  85. package/dist/plugins/runtime/pluginPageManager.d.ts +57 -0
  86. package/dist/plugins/runtime/pluginPageManager.js +877 -0
  87. package/dist/plugins/runtime/pluginProcessTree.d.ts +2 -0
  88. package/dist/plugins/runtime/pluginProcessTree.js +31 -0
  89. package/dist/plugins/runtime/pluginRevision.d.ts +14 -0
  90. package/dist/plugins/runtime/pluginRevision.js +227 -0
  91. package/dist/plugins/runtime/pluginRuntimeCleanup.d.ts +2 -0
  92. package/dist/plugins/runtime/pluginRuntimeCleanup.js +55 -0
  93. package/dist/plugins/runtime/pluginStateManager.d.ts +24 -0
  94. package/dist/plugins/runtime/pluginStateManager.js +132 -0
  95. package/dist/plugins/runtime/pluginStateStore.d.ts +57 -0
  96. package/dist/plugins/runtime/pluginStateStore.js +396 -0
  97. package/dist/plugins/runtime/pluginTaskManager.d.ts +92 -0
  98. package/dist/plugins/runtime/pluginTaskManager.js +1080 -0
  99. package/dist/plugins/runtime/pluginTaskStore.d.ts +53 -0
  100. package/dist/plugins/runtime/pluginTaskStore.js +319 -0
  101. package/dist/plugins/runtime/pythonPlugin.d.ts +31 -0
  102. package/dist/{pythonPlugin.js → plugins/runtime/pythonPlugin.js} +236 -31
  103. package/dist/plugins/sdk/plugin.d.ts +118 -0
  104. package/dist/plugins/sdk/plugin.js +672 -0
  105. package/dist/{pluginAppProfiles.d.ts → plugins/sdk/pluginAppProfiles.d.ts} +1 -1
  106. package/dist/{pluginAppProfiles.js → plugins/sdk/pluginAppProfiles.js} +2 -2
  107. package/dist/plugins/sdk/pluginModelDefinition.d.ts +6 -0
  108. package/dist/plugins/sdk/pluginModelDefinition.js +75 -0
  109. package/dist/plugins/sdk/pluginPageDefinition.d.ts +3 -0
  110. package/dist/plugins/sdk/pluginPageDefinition.js +62 -0
  111. package/dist/plugins/sdk/pluginRecoveryDefinition.d.ts +3 -0
  112. package/dist/plugins/sdk/pluginRecoveryDefinition.js +56 -0
  113. package/dist/plugins/sdk/pluginStateDefinition.d.ts +4 -0
  114. package/dist/plugins/sdk/pluginStateDefinition.js +68 -0
  115. package/dist/runtimePaths.d.ts +4 -0
  116. package/dist/runtimePaths.js +8 -0
  117. package/dist/socket.d.ts +2 -2
  118. package/dist/socket.js +10 -6
  119. package/dist/unified.d.ts +3 -2
  120. package/dist/unified.js +83 -2
  121. package/package.json +7 -3
  122. package/dist/pythonPlugin.d.ts +0 -20
  123. /package/dist/{generated-app-profile-definition.js → generated/app-profile-definition.js} +0 -0
  124. /package/dist/{generated-app-profile-runtime-types.js → generated/app-profile-runtime-types.js} +0 -0
  125. /package/dist/{officialPluginCatalog.d.ts → plugins/officialPluginCatalog.d.ts} +0 -0
  126. /package/dist/{officialPluginCatalog.js → plugins/officialPluginCatalog.js} +0 -0
  127. /package/dist/{officialPluginInstaller.d.ts → plugins/officialPluginInstaller.d.ts} +0 -0
  128. /package/dist/{pluginPageBundle.d.ts → plugins/pluginPageBundle.d.ts} +0 -0
  129. /package/dist/{pluginSource.d.ts → plugins/pluginSource.d.ts} +0 -0
  130. /package/dist/{pluginSource.js → plugins/pluginSource.js} +0 -0
@@ -0,0 +1,53 @@
1
+ import type { PluginTaskDiagnosticMetadata } from '../../generated/plugin-diagnostics.js';
2
+ import type { PluginTask, PluginTaskEvent, PluginTaskEvents, PluginTaskResultChunk } from '../../generated/plugin-tasks.js';
3
+ import type { PluginTaskCheckpoint } from '../../generated/plugin-recovery.js';
4
+ import type { PluginOptions } from '../sdk/plugin.js';
5
+ export declare const PLUGIN_TASK_RESULT_CHUNK_BYTES: number;
6
+ export interface PluginTaskRecord {
7
+ task: PluginTask;
8
+ events: PluginTaskEvent[];
9
+ dedupeKey?: string;
10
+ requestHash?: string;
11
+ access: 'public' | 'toolbox' | 'runtime';
12
+ checkpoint?: PluginTaskCheckpoint;
13
+ diagnostics?: PluginTaskDiagnosticMetadata;
14
+ execution?: {
15
+ entry: string;
16
+ options: PluginOptions;
17
+ bridge: 'service' | 'caller';
18
+ executionTimeoutMs?: number;
19
+ };
20
+ executorOwner?: {
21
+ pid: number;
22
+ processIdentity?: string;
23
+ };
24
+ }
25
+ export declare function pluginTaskExecutionUnconfirmed(record: PluginTaskRecord): boolean;
26
+ export declare class PluginTaskStore {
27
+ readonly directory: string;
28
+ private activeTaskIds?;
29
+ private dedupeIndex?;
30
+ private readonly dedupeKeys;
31
+ constructor(directory?: string);
32
+ private forgetDedupe;
33
+ private indexDedupe;
34
+ /** The service owns writes; the index retains identities and reads the current matching record. */
35
+ findDedupe(key: string, activeOnly?: boolean): PluginTaskRecord | undefined;
36
+ private path;
37
+ read(taskId: string): PluginTaskRecord;
38
+ write(record: PluginTaskRecord): void;
39
+ private indexActive;
40
+ /** The service owns writes; only active identities remain indexed in memory. */
41
+ listActive(): PluginTaskRecord[];
42
+ update(taskId: string, update: Partial<PluginTask>, type?: PluginTaskEvent['type'], fields?: Pick<Partial<PluginTaskRecord>, 'checkpoint' | 'diagnostics'>): PluginTask;
43
+ list(): PluginTaskRecord[];
44
+ diagnosticRecords(taskId?: string): {
45
+ records: PluginTaskRecord[];
46
+ limited: boolean;
47
+ };
48
+ events(taskId: string, cursor: number): PluginTaskEvents;
49
+ saveResult(taskId: string, value: unknown): PluginTask['result'];
50
+ resultChunk(taskId: string, index: number): PluginTaskResultChunk;
51
+ recover(epoch: string): void;
52
+ prune(now?: number): void;
53
+ }
@@ -0,0 +1,319 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { closeSync, openSync, opendirSync, readFileSync, readSync, readdirSync, rmSync, statSync, } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { isPluginTaskActive, isPluginTaskId } from '../../generated/plugin-tasks.js';
5
+ import { writePluginHostFile } from './pluginHostFiles.js';
6
+ import { pluginHostPaths } from './pluginHostPaths.js';
7
+ import { isProcessGroupAlive, isProcessOwnerAlive } from '@mearl/daemon-core';
8
+ export const PLUGIN_TASK_RESULT_CHUNK_BYTES = 192 * 1024;
9
+ const MAX_EVENT_BYTES = 512 * 1024;
10
+ const MAX_RESULT_BYTES = 64 * 1024 * 1024;
11
+ const RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
12
+ export function pluginTaskExecutionUnconfirmed(record) {
13
+ const owner = record.executorOwner;
14
+ if (owner && (!Number.isSafeInteger(owner.pid) || owner.pid < 1))
15
+ return true;
16
+ if ((owner && isProcessOwnerAlive(owner)) ||
17
+ record.task.error?.details?.executionStopped === false ||
18
+ (process.platform === 'win32' && record.task.error?.code === 'PLUGIN_TASK_HOST_RESTARTED'))
19
+ return true;
20
+ if (owner && process.platform !== 'win32')
21
+ return isProcessGroupAlive(owner.pid);
22
+ return false;
23
+ }
24
+ export class PluginTaskStore {
25
+ directory;
26
+ activeTaskIds;
27
+ dedupeIndex;
28
+ dedupeKeys = new Map();
29
+ constructor(directory = pluginHostPaths().tasks) {
30
+ this.directory = directory;
31
+ }
32
+ forgetDedupe(taskId) {
33
+ const key = this.dedupeKeys.get(taskId);
34
+ if (!key)
35
+ return;
36
+ const ids = this.dedupeIndex?.get(key);
37
+ ids?.delete(taskId);
38
+ if (!ids?.size)
39
+ this.dedupeIndex?.delete(key);
40
+ this.dedupeKeys.delete(taskId);
41
+ }
42
+ indexDedupe(record) {
43
+ if (!this.dedupeIndex)
44
+ return;
45
+ const { dedupeKey: key, task } = record;
46
+ const keep = key && (!key.startsWith('active:') || isPluginTaskActive(task.state));
47
+ if (keep && this.dedupeKeys.get(task.taskId) === key)
48
+ return;
49
+ this.forgetDedupe(task.taskId);
50
+ if (!keep)
51
+ return;
52
+ const ids = this.dedupeIndex.get(key) ?? new Set();
53
+ ids.add(task.taskId);
54
+ this.dedupeIndex.set(key, ids);
55
+ this.dedupeKeys.set(task.taskId, key);
56
+ }
57
+ /** The service owns writes; the index retains identities and reads the current matching record. */
58
+ findDedupe(key, activeOnly = false) {
59
+ if (!this.dedupeIndex) {
60
+ this.dedupeIndex = new Map();
61
+ for (const record of this.list())
62
+ this.indexDedupe(record);
63
+ }
64
+ for (const taskId of this.dedupeIndex.get(key) ?? []) {
65
+ let record;
66
+ try {
67
+ record = this.read(taskId);
68
+ }
69
+ catch (error) {
70
+ if (error.code !== 'PLUGIN_TASK_NOT_FOUND')
71
+ throw error;
72
+ this.forgetDedupe(taskId);
73
+ continue;
74
+ }
75
+ this.indexDedupe(record);
76
+ if (record.dedupeKey === key && (!activeOnly || isPluginTaskActive(record.task.state)))
77
+ return record;
78
+ }
79
+ return undefined;
80
+ }
81
+ path(taskId, file) {
82
+ if (!isPluginTaskId(taskId))
83
+ throw Object.assign(new Error('Invalid plugin task ID'), { code: 'PLUGIN_TASK_INVALID_ID' });
84
+ return join(this.directory, taskId, file);
85
+ }
86
+ read(taskId) {
87
+ let record;
88
+ try {
89
+ record = JSON.parse(readFileSync(this.path(taskId, 'task.json'), 'utf8'));
90
+ }
91
+ catch (error) {
92
+ if (error.code === 'ENOENT')
93
+ throw Object.assign(new Error('Plugin task was not found'), {
94
+ code: 'PLUGIN_TASK_NOT_FOUND',
95
+ });
96
+ throw error;
97
+ }
98
+ if (record?.task?.taskId !== taskId || !Array.isArray(record.events))
99
+ throw new Error('Invalid persistent plugin task record');
100
+ return record;
101
+ }
102
+ write(record) {
103
+ writePluginHostFile(this.path(record.task.taskId, 'task.json'), JSON.stringify(record));
104
+ this.indexDedupe(record);
105
+ this.indexActive(record);
106
+ }
107
+ indexActive(record) {
108
+ if (isPluginTaskActive(record.task.state))
109
+ this.activeTaskIds?.add(record.task.taskId);
110
+ else
111
+ this.activeTaskIds?.delete(record.task.taskId);
112
+ }
113
+ /** The service owns writes; only active identities remain indexed in memory. */
114
+ listActive() {
115
+ if (!this.activeTaskIds) {
116
+ this.activeTaskIds = new Set();
117
+ for (const record of this.list())
118
+ this.indexActive(record);
119
+ }
120
+ const records = [];
121
+ for (const taskId of this.activeTaskIds) {
122
+ try {
123
+ const record = this.read(taskId);
124
+ this.indexActive(record);
125
+ if (isPluginTaskActive(record.task.state))
126
+ records.push(record);
127
+ }
128
+ catch (error) {
129
+ if (error.code !== 'PLUGIN_TASK_NOT_FOUND')
130
+ throw error;
131
+ this.activeTaskIds.delete(taskId);
132
+ }
133
+ }
134
+ return records;
135
+ }
136
+ update(taskId, update, type = 'state', fields = {}) {
137
+ const record = this.read(taskId);
138
+ Object.assign(record, fields);
139
+ record.task = {
140
+ ...record.task,
141
+ ...update,
142
+ sequence: record.task.sequence + 1,
143
+ updatedAt: Date.now(),
144
+ };
145
+ const event = {
146
+ sequence: record.task.sequence,
147
+ time: record.task.updatedAt,
148
+ type,
149
+ state: record.task.state,
150
+ ...(type === 'progress' ? { progress: record.task.progress } : {}),
151
+ ...(type === 'checkpoint' ? { checkpoint: record.task.checkpoint } : {}),
152
+ };
153
+ record.events.push(event);
154
+ let bytes = Buffer.byteLength(JSON.stringify(record.events));
155
+ while (record.events.length > 256 || (bytes > MAX_EVENT_BYTES && record.events.length > 1)) {
156
+ bytes -= Buffer.byteLength(JSON.stringify(record.events.shift())) + 1;
157
+ }
158
+ this.write(record);
159
+ return record.task;
160
+ }
161
+ list() {
162
+ let names;
163
+ try {
164
+ names = readdirSync(this.directory);
165
+ }
166
+ catch (error) {
167
+ if (error.code === 'ENOENT')
168
+ return [];
169
+ throw error;
170
+ }
171
+ return names.filter(isPluginTaskId).map(name => this.read(name));
172
+ }
173
+ diagnosticRecords(taskId) {
174
+ const records = [];
175
+ let bytes = 0, scanned = 0, limited = false;
176
+ if (taskId) {
177
+ let size;
178
+ try {
179
+ size = statSync(this.path(taskId, 'task.json')).size;
180
+ }
181
+ catch (error) {
182
+ if (error.code === 'ENOENT')
183
+ return { records, limited };
184
+ throw error;
185
+ }
186
+ if (size > 16 * 1024 * 1024)
187
+ return { records, limited: true };
188
+ records.push(this.read(taskId));
189
+ bytes += size;
190
+ }
191
+ let directory;
192
+ try {
193
+ directory = opendirSync(this.directory);
194
+ }
195
+ catch (error) {
196
+ if (error.code === 'ENOENT')
197
+ return { records, limited };
198
+ throw error;
199
+ }
200
+ try {
201
+ let item;
202
+ while ((item = directory.readSync()) !== null) {
203
+ if (++scanned > 2048) {
204
+ limited = true;
205
+ break;
206
+ }
207
+ if (!isPluginTaskId(item.name) || item.name === taskId)
208
+ continue;
209
+ try {
210
+ const size = statSync(this.path(item.name, 'task.json')).size;
211
+ if (records.length >= 512 || bytes + size > 16 * 1024 * 1024) {
212
+ limited = true;
213
+ break;
214
+ }
215
+ bytes += size;
216
+ records.push(this.read(item.name));
217
+ }
218
+ catch {
219
+ limited = true;
220
+ }
221
+ }
222
+ }
223
+ finally {
224
+ directory.closeSync();
225
+ }
226
+ return { records, limited };
227
+ }
228
+ events(taskId, cursor) {
229
+ const record = this.read(taskId);
230
+ if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > record.task.sequence)
231
+ throw new Error('Invalid plugin task event cursor');
232
+ const first = record.events[0]?.sequence ?? record.task.sequence + 1;
233
+ const events = [];
234
+ let bytes = 0;
235
+ for (const event of record.events) {
236
+ if (event.sequence <= cursor)
237
+ continue;
238
+ const size = Buffer.byteLength(JSON.stringify(event));
239
+ if (bytes + size > 192 * 1024 && events.length)
240
+ break;
241
+ events.push(event);
242
+ bytes += size;
243
+ }
244
+ return {
245
+ task: record.task,
246
+ events,
247
+ firstAvailableSequence: first,
248
+ nextCursor: events.at(-1)?.sequence ?? cursor,
249
+ gap: cursor < first - 1,
250
+ };
251
+ }
252
+ saveResult(taskId, value) {
253
+ const bytes = Buffer.from(JSON.stringify({ value }));
254
+ if (bytes.length > MAX_RESULT_BYTES)
255
+ throw Object.assign(new Error('Plugin task result exceeds 64 MiB'), {
256
+ code: 'PLUGIN_TASK_RESULT_TOO_LARGE',
257
+ });
258
+ writePluginHostFile(this.path(taskId, 'result.json'), bytes);
259
+ return {
260
+ bytes: bytes.length,
261
+ sha256: createHash('sha256').update(bytes).digest('hex'),
262
+ chunks: Math.ceil(bytes.length / PLUGIN_TASK_RESULT_CHUNK_BYTES),
263
+ };
264
+ }
265
+ resultChunk(taskId, index) {
266
+ const task = this.read(taskId).task;
267
+ if (task.state !== 'success' || !task.result)
268
+ throw new Error('Plugin task result is not available');
269
+ if (!Number.isSafeInteger(index) || index < 0 || index >= task.result.chunks)
270
+ throw new Error('Invalid plugin task result chunk');
271
+ const file = this.path(taskId, 'result.json');
272
+ if (statSync(file).size !== task.result.bytes)
273
+ throw new Error('Plugin task result size does not match its record');
274
+ const offset = index * PLUGIN_TASK_RESULT_CHUNK_BYTES;
275
+ const buffer = Buffer.alloc(Math.min(PLUGIN_TASK_RESULT_CHUNK_BYTES, task.result.bytes - offset));
276
+ const descriptor = openSync(file, 'r');
277
+ try {
278
+ if (readSync(descriptor, buffer, 0, buffer.length, offset) !== buffer.length)
279
+ throw new Error('Plugin task result is incomplete');
280
+ }
281
+ finally {
282
+ closeSync(descriptor);
283
+ }
284
+ return { taskId, index, data: buffer.toString('base64'), ...task.result };
285
+ }
286
+ recover(epoch) {
287
+ this.activeTaskIds = new Set();
288
+ for (const record of this.list()) {
289
+ this.indexActive(record);
290
+ const { task } = record;
291
+ if (isPluginTaskActive(task.state) && task.host.epoch !== epoch) {
292
+ this.update(task.taskId, {
293
+ state: 'interrupted',
294
+ error: {
295
+ code: 'PLUGIN_TASK_HOST_RESTARTED',
296
+ message: 'The plugin execution service restarted; external operations may need reconciliation',
297
+ },
298
+ });
299
+ }
300
+ else if (!isPluginTaskActive(task.state) &&
301
+ Date.now() - task.updatedAt > RETENTION_MS &&
302
+ !pluginTaskExecutionUnconfirmed(record)) {
303
+ rmSync(join(this.directory, task.taskId), { recursive: true, force: true });
304
+ this.forgetDedupe(task.taskId);
305
+ }
306
+ }
307
+ }
308
+ prune(now = Date.now()) {
309
+ for (const record of this.list()) {
310
+ const { task } = record;
311
+ if (!isPluginTaskActive(task.state) &&
312
+ now - task.updatedAt > RETENTION_MS &&
313
+ !pluginTaskExecutionUnconfirmed(record)) {
314
+ rmSync(join(this.directory, task.taskId), { recursive: true, force: true });
315
+ this.forgetDedupe(task.taskId);
316
+ }
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,31 @@
1
+ import type { PluginOptions, PluginApp } from '../sdk/plugin.js';
2
+ import type { PluginCommandContext } from './pluginExecution.js';
3
+ export interface PythonPluginCommand {
4
+ description: string;
5
+ signature: string;
6
+ toolboxOnly: boolean;
7
+ runtimeOnly: boolean;
8
+ recoveryOnly: boolean;
9
+ migrationOnly: boolean;
10
+ }
11
+ export interface PythonPluginDescriptor {
12
+ commands: PythonPluginCommand[];
13
+ description?: string;
14
+ name?: string;
15
+ toolbox?: unknown;
16
+ pageRuntime?: unknown;
17
+ appProfiles?: unknown;
18
+ stateStorage?: unknown;
19
+ taskRecovery?: unknown;
20
+ }
21
+ export interface PythonPluginApp {
22
+ state?: PluginApp['state'];
23
+ model?: PluginApp['model'];
24
+ checkpoint?: PluginApp['checkpoint'];
25
+ page?: PluginApp['page'];
26
+ readonly options: Readonly<PluginOptions>;
27
+ invoke(action: string, payload?: Record<string, any>, options?: PluginOptions): Promise<any>;
28
+ progress(message: string): void;
29
+ }
30
+ export declare function describePythonPlugin(entry: string, options: Readonly<PluginOptions>): Promise<PythonPluginDescriptor>;
31
+ export declare function runPythonPluginCommand(entry: string, command: string, input: Record<string, any> | import('../../generated/plugin-recovery.js').PluginCheckpointMigrationInput, app: PythonPluginApp, context: PluginCommandContext, recovery?: boolean | 'migration', expectedDescriptor?: PythonPluginDescriptor, runtimeOnly?: boolean): Promise<unknown>;