@hyperdreamer/pi-webui 1.14.0 → 1.15.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.
@@ -1,17 +1,103 @@
1
1
  // Generated from pi-webui-plugins/workspace-tasks/workspaceTasksClient.ts. Do not edit directly.
2
- import { TASKS_CONFIG_PATH, parseTasksConfigText } from "./config.js";
2
+ import { TASKS_CONFIG_PATH, parseTasksConfigText, serializeWorkspaceTasksConfig, } from "./config.js";
3
3
  export const tasksConfigMissingMessage = "No workspace tasks configured here.";
4
4
  export const tasksConfigMissingHint = `${TASKS_CONFIG_PATH} is optional. Create it in this workspace if you want custom tasks.`;
5
+ export const tasksConfigInvalidMessage = "Workspace tasks configuration is invalid.";
6
+ export const tasksConfigInvalidHint = `Fix ${TASKS_CONFIG_PATH}, then click Refresh.`;
5
7
  export const tasksConfigUnavailableMessage = "Could not load workspace tasks.";
6
8
  export const tasksConfigRefreshHint = `Fix ${TASKS_CONFIG_PATH}, then click Refresh.`;
7
- const missingWorkspaceFileError = "Path does not exist";
8
- export async function loadWorkspaceTasksConfig(files) {
9
+ const configCache = new Map();
10
+ const workspaceRuntimes = new Map();
11
+ const subscribers = new Set();
12
+ let stateEpoch = 0;
13
+ export function loadWorkspaceTasksConfig(files) {
14
+ return readCurrentWorkspaceTasksConfig(files);
15
+ }
16
+ export function getWorkspaceTasksCacheEntry(workspaceKey) {
17
+ return configCache.get(workspaceKey);
18
+ }
19
+ export function ensureWorkspaceTasksConfig(files, workspaceKey) {
20
+ const cached = configCache.get(workspaceKey);
21
+ if (cached !== undefined)
22
+ return cached;
23
+ const runtime = getWorkspaceRuntime(workspaceKey);
24
+ const loadingEntry = {
25
+ state: { kind: "loading" },
26
+ refreshRequired: runtime.refreshRequired,
27
+ };
28
+ configCache.set(workspaceKey, loadingEntry);
29
+ void enqueueWorkspaceOperation(workspaceKey, async (operation) => {
30
+ const state = await loadWorkspaceTasksConfigSafely(files);
31
+ if (state.kind === "unavailable")
32
+ operation.runtime.refreshRequired = true;
33
+ publishState(operation, state, operation.runtime.refreshRequired);
34
+ }).catch(() => undefined);
35
+ return loadingEntry;
36
+ }
37
+ export function refreshWorkspaceTasksConfig(files, workspaceKey) {
38
+ return enqueueWorkspaceOperation(workspaceKey, async (operation) => {
39
+ const state = await loadWorkspaceTasksConfigSafely(files);
40
+ operation.runtime.refreshRequired = state.kind === "unavailable";
41
+ publishState(operation, state, operation.runtime.refreshRequired);
42
+ return state;
43
+ }, (operation) => {
44
+ if (!isCurrentOperation(operation))
45
+ return;
46
+ configCache.set(workspaceKey, {
47
+ state: { kind: "loading" },
48
+ refreshRequired: operation.runtime.refreshRequired,
49
+ });
50
+ });
51
+ }
52
+ export function guardedWriteWorkspaceTasksConfig(files, workspaceKey, sourceSnapshot, nextConfig) {
53
+ return enqueueWorkspaceOperation(workspaceKey, async (operation) => {
54
+ if (operation.runtime.refreshRequired) {
55
+ return blockMutation(operation, "A successful Refresh is required before another workspace task mutation.");
56
+ }
57
+ const preflight = await loadWorkspaceTasksConfigSafely(files);
58
+ if (preflight.kind === "unavailable") {
59
+ return blockMutation(operation, unavailableDetail(preflight));
60
+ }
61
+ const currentSnapshot = preflight.snapshot;
62
+ if (!snapshotsEqual(sourceSnapshot, currentSnapshot)) {
63
+ return blockMutation(operation, `The ${TASKS_CONFIG_PATH} file changed outside this panel. Refresh before trying again.`, "conflict");
64
+ }
65
+ const payload = serializeWorkspaceTasksConfig(nextConfig);
66
+ try {
67
+ await files.writeFile(TASKS_CONFIG_PATH, payload);
68
+ }
69
+ catch (error) {
70
+ return blockMutation(operation, `Unable to write ${TASKS_CONFIG_PATH}: ${formatUnknownError(error)}`, "write-failed");
71
+ }
72
+ const postWrite = await loadWorkspaceTasksConfigSafely(files);
73
+ if (postWrite.kind === "loaded" && postWrite.snapshot.content === payload) {
74
+ operation.runtime.refreshRequired = false;
75
+ publishState(operation, postWrite, false);
76
+ return { kind: "written", state: postWrite };
77
+ }
78
+ const detail = postWriteReloadDetail(postWrite);
79
+ blockMutation(operation, detail, undefined, postWrite);
80
+ return { kind: "written-but-unreloaded", detail };
81
+ });
82
+ }
83
+ export function subscribeWorkspaceTasksConfig(listener) {
84
+ subscribers.add(listener);
85
+ return () => {
86
+ subscribers.delete(listener);
87
+ };
88
+ }
89
+ export function clearWorkspaceTasksStateForTesting() {
90
+ stateEpoch += 1;
91
+ configCache.clear();
92
+ workspaceRuntimes.clear();
93
+ }
94
+ async function readCurrentWorkspaceTasksConfig(files) {
9
95
  let file;
10
96
  try {
11
97
  file = await files.readFile(TASKS_CONFIG_PATH);
12
98
  }
13
99
  catch (error) {
14
- if (errorMessage(error) === missingWorkspaceFileError)
100
+ if (isMissingWorkspaceFileError(error))
15
101
  return missing();
16
102
  return unavailable(`Unable to read ${TASKS_CONFIG_PATH}: ${formatUnknownError(error)}`);
17
103
  }
@@ -20,15 +106,107 @@ export async function loadWorkspaceTasksConfig(files) {
20
106
  if (file.truncated)
21
107
  return unavailable(`${TASKS_CONFIG_PATH} is too large and was truncated`);
22
108
  const result = parseTasksConfigText(file.content);
23
- if (!result.ok)
24
- return unavailable(result.error);
25
- return { kind: "loaded", config: result.config, path: TASKS_CONFIG_PATH };
109
+ if (!result.ok) {
110
+ return {
111
+ kind: "invalid",
112
+ message: tasksConfigInvalidMessage,
113
+ hint: tasksConfigInvalidHint,
114
+ detail: result.error,
115
+ snapshot: { kind: "text", content: file.content },
116
+ };
117
+ }
118
+ return {
119
+ kind: "loaded",
120
+ config: result.config,
121
+ path: TASKS_CONFIG_PATH,
122
+ snapshot: { kind: "text", content: file.content },
123
+ };
124
+ }
125
+ async function loadWorkspaceTasksConfigSafely(files) {
126
+ try {
127
+ return await readCurrentWorkspaceTasksConfig(files);
128
+ }
129
+ catch (error) {
130
+ return unavailable(`Unable to read ${TASKS_CONFIG_PATH}: ${formatUnknownError(error)}`);
131
+ }
132
+ }
133
+ function enqueueWorkspaceOperation(workspaceKey, operation, onRequest) {
134
+ const runtime = getWorkspaceRuntime(workspaceKey);
135
+ const context = {
136
+ key: workspaceKey,
137
+ runtime,
138
+ generation: runtime.requestGeneration + 1,
139
+ epoch: stateEpoch,
140
+ };
141
+ runtime.requestGeneration = context.generation;
142
+ onRequest?.(context);
143
+ const result = runtime.tail.then(() => operation(context));
144
+ runtime.tail = result.then(() => undefined, () => undefined);
145
+ return result;
146
+ }
147
+ function getWorkspaceRuntime(workspaceKey) {
148
+ const existing = workspaceRuntimes.get(workspaceKey);
149
+ if (existing !== undefined)
150
+ return existing;
151
+ const runtime = {
152
+ requestGeneration: 0,
153
+ refreshRequired: false,
154
+ tail: Promise.resolve(),
155
+ };
156
+ workspaceRuntimes.set(workspaceKey, runtime);
157
+ return runtime;
158
+ }
159
+ function publishState(operation, state, refreshRequired) {
160
+ if (!isCurrentOperation(operation))
161
+ return;
162
+ configCache.set(operation.key, { state, refreshRequired });
163
+ notifySubscribers(operation.key);
164
+ }
165
+ function blockMutation(operation, detail, kind = "preflight-unavailable", postWriteState) {
166
+ operation.runtime.refreshRequired = true;
167
+ if (isCurrentOperation(operation)) {
168
+ const existing = configCache.get(operation.key);
169
+ if (postWriteState !== undefined) {
170
+ configCache.set(operation.key, { state: postWriteState, refreshRequired: true });
171
+ }
172
+ else if (existing !== undefined) {
173
+ configCache.set(operation.key, { ...existing, refreshRequired: true });
174
+ }
175
+ notifySubscribers(operation.key);
176
+ }
177
+ return { kind, detail };
178
+ }
179
+ function isCurrentOperation(operation) {
180
+ return operation.epoch === stateEpoch
181
+ && workspaceRuntimes.get(operation.key) === operation.runtime
182
+ && operation.runtime.requestGeneration === operation.generation;
183
+ }
184
+ function notifySubscribers(workspaceKey) {
185
+ for (const listener of subscribers)
186
+ listener(workspaceKey);
187
+ }
188
+ function snapshotsEqual(left, right) {
189
+ if (left.kind !== right.kind)
190
+ return false;
191
+ if (left.kind === "missing" || right.kind === "missing")
192
+ return true;
193
+ return left.content === right.content;
194
+ }
195
+ function postWriteReloadDetail(result) {
196
+ if (result.kind === "loaded")
197
+ return `Unable to reload ${TASKS_CONFIG_PATH}: the post-write snapshot did not match the canonical payload.`;
198
+ if (result.kind === "missing")
199
+ return `Unable to reload ${TASKS_CONFIG_PATH}: the file is missing after the write.`;
200
+ if (result.kind === "invalid")
201
+ return `Unable to reload ${TASKS_CONFIG_PATH}: ${result.detail}`;
202
+ return `Unable to reload ${TASKS_CONFIG_PATH}: ${unavailableDetail(result)}`;
26
203
  }
27
204
  function missing() {
28
205
  return {
29
206
  kind: "missing",
30
207
  message: tasksConfigMissingMessage,
31
208
  hint: tasksConfigMissingHint,
209
+ snapshot: { kind: "missing" },
32
210
  };
33
211
  }
34
212
  function unavailable(detail) {
@@ -39,8 +217,25 @@ function unavailable(detail) {
39
217
  detail,
40
218
  };
41
219
  }
42
- function errorMessage(error) {
43
- return error instanceof Error ? error.message : undefined;
220
+ function unavailableDetail(result) {
221
+ return result.detail ?? tasksConfigUnavailableMessage;
222
+ }
223
+ function isMissingWorkspaceFileError(error) {
224
+ const message = errorField(error, "message");
225
+ const code = errorField(error, "code");
226
+ return message === "Path does not exist"
227
+ || code?.toLowerCase() === "enoent"
228
+ || message?.toLowerCase() === "enoent"
229
+ || message?.toLowerCase().includes("no such file or directory") === true
230
+ || code?.toLowerCase().includes("no such file or directory") === true;
231
+ }
232
+ function errorField(error, field) {
233
+ if (typeof error === "object" && error !== null && hasStringField(error, field))
234
+ return error[field];
235
+ return error instanceof Error && field === "message" ? error.message : undefined;
236
+ }
237
+ function hasStringField(value, field) {
238
+ return field in value && typeof Reflect.get(value, field) === "string";
44
239
  }
45
240
  function formatUnknownError(error) {
46
241
  return error instanceof Error ? error.message : String(error);
@@ -564,7 +564,12 @@ export class PiSessionService {
564
564
  },
565
565
  onCompactionEnd: (session, result, detail) => {
566
566
  this.endSessionEntryMutation(session);
567
- this.publishActivity(session, result === "success" ? "compaction complete" : "compaction failed", result === "success" ? "idle" : "error", detail);
567
+ const activity = result === "success"
568
+ ? { label: "compaction complete", phase: "idle" }
569
+ : result === "cancelled"
570
+ ? { label: "compaction cancelled", phase: "idle" }
571
+ : { label: "compaction failed", phase: "error" };
572
+ this.publishActivity(session, activity.label, activity.phase, detail);
568
573
  this.publishStatus(session);
569
574
  },
570
575
  reloadSession: (session) => this.reloadSessionRuntime(session),
@@ -2508,26 +2513,31 @@ export class PiSessionService {
2508
2513
  }
2509
2514
  }
2510
2515
  async abortSessionOperations(session) {
2511
- let branchSummaryAbortFailed = false;
2512
- let branchSummaryAbortError;
2516
+ const failures = [];
2517
+ try {
2518
+ session.abortCompaction();
2519
+ this.commandService.cancelManualCompaction(session.sessionId);
2520
+ }
2521
+ catch (error) {
2522
+ failures.push(error);
2523
+ }
2513
2524
  try {
2514
2525
  session.abortBranchSummary?.();
2515
2526
  }
2516
2527
  catch (error) {
2517
- branchSummaryAbortFailed = true;
2518
- branchSummaryAbortError = error;
2528
+ failures.push(error);
2519
2529
  }
2520
2530
  try {
2521
2531
  await session.abort();
2522
2532
  }
2523
- catch (abortError) {
2524
- if (branchSummaryAbortFailed) {
2525
- throw new AggregateError([branchSummaryAbortError, abortError], "Failed to abort session operations", { cause: abortError });
2526
- }
2527
- throw abortError;
2533
+ catch (error) {
2534
+ failures.push(error);
2535
+ }
2536
+ if (failures.length === 1)
2537
+ throw failures[0];
2538
+ if (failures.length > 1) {
2539
+ throw new AggregateError(failures, "Failed to abort session operations", { cause: failures[failures.length - 1] });
2528
2540
  }
2529
- if (branchSummaryAbortFailed)
2530
- throw branchSummaryAbortError;
2531
2541
  }
2532
2542
  async assertWritable(ref) {
2533
2543
  if ((await this.getArchived(ref)) !== undefined)