gsd-pi 2.64.0-dev.1a85e85 → 2.64.0-dev.4ac9673
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/dist/headless.js +3 -1
- package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +22 -7
- package/dist/resources/extensions/bg-shell/process-manager.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +24 -13
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +1 -0
- package/dist/resources/extensions/gsd/notification-overlay.js +13 -9
- package/dist/resources/extensions/gsd/notification-store.js +10 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +9 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +33 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +66 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -0
- package/packages/pi-tui/dist/components/loader.d.ts +4 -2
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +27 -9
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/dist/components/text.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/text.js +2 -0
- package/packages/pi-tui/dist/components/text.js.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.js +12 -1
- package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +4 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +35 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +82 -0
- package/packages/pi-tui/src/components/loader.ts +27 -10
- package/packages/pi-tui/src/components/text.ts +1 -0
- package/packages/pi-tui/src/overlay-layout.ts +13 -1
- package/packages/pi-tui/src/tui.ts +34 -0
- package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +19 -7
- package/src/resources/extensions/bg-shell/process-manager.ts +8 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +25 -13
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +1 -0
- package/src/resources/extensions/gsd/notification-overlay.ts +14 -9
- package/src/resources/extensions/gsd/notification-store.ts +8 -3
- package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +34 -1
- /package/dist/web/standalone/.next/static/{ffabZXz8JdN3EzX9EKt-R → 1btalZ1AEGX9RBvxBqJlC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ffabZXz8JdN3EzX9EKt-R → 1btalZ1AEGX9RBvxBqJlC}/_ssgManifest.js +0 -0
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
processes,
|
|
18
18
|
pendingAlerts,
|
|
19
|
+
pushAlert,
|
|
19
20
|
cleanupAll,
|
|
20
21
|
cleanupSessionProcesses,
|
|
21
22
|
persistManifest,
|
|
@@ -37,19 +38,30 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// Clean up on session shutdown
|
|
41
|
-
pi.on("session_shutdown", async () => {
|
|
42
|
-
cleanupAll();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
41
|
// Register signal handlers to clean up bg processes on unexpected exit (fixes #428)
|
|
46
42
|
const signalCleanup = () => {
|
|
47
43
|
cleanupAll();
|
|
44
|
+
// Also kill bash-tool spawned children that bg-shell doesn't track
|
|
45
|
+
try {
|
|
46
|
+
const { listDescendants } = require("@gsd/native") as typeof import("@gsd/native");
|
|
47
|
+
const descendants = listDescendants(process.pid);
|
|
48
|
+
for (const childPid of descendants) {
|
|
49
|
+
try { process.kill(childPid, "SIGKILL"); } catch {}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
48
52
|
};
|
|
49
53
|
process.on("SIGTERM", signalCleanup);
|
|
50
54
|
process.on("SIGINT", signalCleanup);
|
|
51
55
|
process.on("beforeExit", signalCleanup);
|
|
52
56
|
|
|
57
|
+
// Clean up on session shutdown — remove signal handlers to prevent accumulation
|
|
58
|
+
pi.on("session_shutdown", async () => {
|
|
59
|
+
process.off("SIGTERM", signalCleanup);
|
|
60
|
+
process.off("SIGINT", signalCleanup);
|
|
61
|
+
process.off("beforeExit", signalCleanup);
|
|
62
|
+
cleanupAll();
|
|
63
|
+
});
|
|
64
|
+
|
|
53
65
|
// ── Compaction Awareness: Survive Context Resets ───────────────
|
|
54
66
|
|
|
55
67
|
/** Build a compact state summary of all alive processes for context re-injection */
|
|
@@ -65,7 +77,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
65
77
|
return ` - id:${p.id} "${p.label}" [${p.processType}] status:${p.status} uptime:${formatUptime(Date.now() - p.startedAt)}${portInfo}${urlInfo}${errInfo}${groupInfo}`;
|
|
66
78
|
}).join("\n");
|
|
67
79
|
|
|
68
|
-
|
|
80
|
+
pushAlert(null,
|
|
69
81
|
`${reason} ${alive.length} background process(es) are still running:\n${processSummaries}\nUse bg_shell digest/output/kill with these IDs.`
|
|
70
82
|
);
|
|
71
83
|
}
|
|
@@ -150,7 +162,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
150
162
|
` - ${s.id}: ${s.label} (pid ${s.pid}, type: ${s.processType}${s.group ? `, group: ${s.group}` : ""})`
|
|
151
163
|
).join("\n");
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
pushAlert(null,
|
|
154
166
|
`${surviving.length} background process(es) from previous session still running:\n${summary}\n Note: These processes are outside bg_shell's control. Kill them manually if needed.`
|
|
155
167
|
);
|
|
156
168
|
}
|
|
@@ -33,6 +33,8 @@ export const processes = new Map<string, BgProcess>();
|
|
|
33
33
|
/** Pending alerts to inject into the next agent context */
|
|
34
34
|
export let pendingAlerts: string[] = [];
|
|
35
35
|
|
|
36
|
+
const MAX_PENDING_ALERTS = 50;
|
|
37
|
+
|
|
36
38
|
/** Replace the pendingAlerts array (used by the extension entry point) */
|
|
37
39
|
export function setPendingAlerts(alerts: string[]): void {
|
|
38
40
|
pendingAlerts = alerts;
|
|
@@ -58,8 +60,12 @@ export function addEvent(bg: BgProcess, event: Omit<ProcessEvent, "timestamp">):
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
export function pushAlert(bg: BgProcess, message: string): void {
|
|
62
|
-
|
|
63
|
+
export function pushAlert(bg: BgProcess | null, message: string): void {
|
|
64
|
+
const prefix = bg ? `[bg:${bg.id} ${bg.label}] ` : "";
|
|
65
|
+
pendingAlerts.push(`${prefix}${message}`);
|
|
66
|
+
if (pendingAlerts.length > MAX_PENDING_ALERTS) {
|
|
67
|
+
pendingAlerts.splice(0, pendingAlerts.length - MAX_PENDING_ALERTS);
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export function getInfo(p: BgProcess): BgProcessInfo {
|
|
@@ -804,27 +804,39 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
804
804
|
return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ""];
|
|
805
805
|
};
|
|
806
806
|
const coerced = { ...params };
|
|
807
|
-
|
|
807
|
+
// Coerce simple string-array fields: LLMs sometimes pass a plain string
|
|
808
|
+
// instead of a single-element array (#3585).
|
|
809
|
+
const wrapArray = (v: any): any[] =>
|
|
810
|
+
v == null ? [] : Array.isArray(v) ? v : [v];
|
|
811
|
+
coerced.provides = wrapArray(params.provides);
|
|
812
|
+
coerced.keyFiles = wrapArray(params.keyFiles);
|
|
813
|
+
coerced.keyDecisions = wrapArray(params.keyDecisions);
|
|
814
|
+
coerced.patternsEstablished = wrapArray(params.patternsEstablished);
|
|
815
|
+
coerced.observabilitySurfaces = wrapArray(params.observabilitySurfaces);
|
|
816
|
+
coerced.requirementsSurfaced = wrapArray(params.requirementsSurfaced);
|
|
817
|
+
coerced.drillDownPaths = wrapArray(params.drillDownPaths);
|
|
818
|
+
coerced.affects = wrapArray(params.affects);
|
|
819
|
+
coerced.filesModified = wrapArray(params.filesModified).map((f: any) => {
|
|
808
820
|
if (typeof f !== "string") return f;
|
|
809
821
|
const [path, description] = splitPair(f);
|
|
810
822
|
return { path, description };
|
|
811
823
|
});
|
|
812
|
-
coerced.requires = (params.requires
|
|
824
|
+
coerced.requires = wrapArray(params.requires).map((r: any) => {
|
|
813
825
|
if (typeof r !== "string") return r;
|
|
814
826
|
const [slice, provides] = splitPair(r);
|
|
815
827
|
return { slice, provides };
|
|
816
828
|
});
|
|
817
|
-
coerced.requirementsAdvanced = (params.requirementsAdvanced
|
|
829
|
+
coerced.requirementsAdvanced = wrapArray(params.requirementsAdvanced).map((r: any) => {
|
|
818
830
|
if (typeof r !== "string") return r;
|
|
819
831
|
const [id, how] = splitPair(r);
|
|
820
832
|
return { id, how };
|
|
821
833
|
});
|
|
822
|
-
coerced.requirementsValidated = (params.requirementsValidated
|
|
834
|
+
coerced.requirementsValidated = wrapArray(params.requirementsValidated).map((r: any) => {
|
|
823
835
|
if (typeof r !== "string") return r;
|
|
824
836
|
const [id, proof] = splitPair(r);
|
|
825
837
|
return { id, proof };
|
|
826
838
|
});
|
|
827
|
-
coerced.requirementsInvalidated = (params.requirementsInvalidated
|
|
839
|
+
coerced.requirementsInvalidated = wrapArray(params.requirementsInvalidated).map((r: any) => {
|
|
828
840
|
if (typeof r !== "string") return r;
|
|
829
841
|
const [id, what] = splitPair(r);
|
|
830
842
|
return { id, what };
|
|
@@ -884,14 +896,14 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
884
896
|
deviations: Type.Optional(Type.String({ description: "Deviations from the slice plan, or 'None.'" })),
|
|
885
897
|
knownLimitations: Type.Optional(Type.String({ description: "Known limitations or gaps, or 'None.'" })),
|
|
886
898
|
followUps: Type.Optional(Type.String({ description: "Follow-up work discovered during execution, or 'None.'" })),
|
|
887
|
-
keyFiles: Type.Optional(Type.Array(Type.String(), { description: "Key files created or modified" })),
|
|
888
|
-
keyDecisions: Type.Optional(Type.Array(Type.String(), { description: "Key decisions made during this slice" })),
|
|
889
|
-
patternsEstablished: Type.Optional(Type.Array(Type.String(), { description: "Patterns established by this slice" })),
|
|
890
|
-
observabilitySurfaces: Type.Optional(Type.Array(Type.String(), { description: "Observability surfaces added" })),
|
|
891
|
-
provides: Type.Optional(Type.Array(Type.String(), { description: "What this slice provides to downstream slices" })),
|
|
892
|
-
requirementsSurfaced: Type.Optional(Type.Array(Type.String(), { description: "New requirements surfaced" })),
|
|
893
|
-
drillDownPaths: Type.Optional(Type.Array(Type.String(), { description: "Paths to task summaries for drill-down" })),
|
|
894
|
-
affects: Type.Optional(Type.Array(Type.String(), { description: "Downstream slices affected" })),
|
|
899
|
+
keyFiles: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Key files created or modified" })),
|
|
900
|
+
keyDecisions: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Key decisions made during this slice" })),
|
|
901
|
+
patternsEstablished: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Patterns established by this slice" })),
|
|
902
|
+
observabilitySurfaces: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Observability surfaces added" })),
|
|
903
|
+
provides: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "What this slice provides to downstream slices" })),
|
|
904
|
+
requirementsSurfaced: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "New requirements surfaced" })),
|
|
905
|
+
drillDownPaths: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Paths to task summaries for drill-down" })),
|
|
906
|
+
affects: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Downstream slices affected" })),
|
|
895
907
|
requirementsAdvanced: Type.Optional(Type.Array(
|
|
896
908
|
Type.Union([
|
|
897
909
|
Type.Object({
|
|
@@ -157,13 +157,18 @@ export class GSDNotificationOverlay {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const content = this.buildContentLines(width);
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
|
|
160
|
+
const maxVisibleRows = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24) - 2;
|
|
161
|
+
const visibleContentRows = Math.min(content.length, maxVisibleRows);
|
|
163
162
|
const maxScroll = Math.max(0, content.length - visibleContentRows);
|
|
164
163
|
this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
|
|
165
164
|
const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
|
|
166
165
|
|
|
166
|
+
// Pad to consistent height so filter changes don't leave ghost artifacts
|
|
167
|
+
// (differential renderer can't clear old overlay positions)
|
|
168
|
+
while (visibleContent.length < maxVisibleRows) {
|
|
169
|
+
visibleContent.push("");
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
const lines = this.wrapInBox(visibleContent, width);
|
|
168
173
|
|
|
169
174
|
this.cachedWidth = width;
|
|
@@ -253,13 +258,13 @@ export class GSDNotificationOverlay {
|
|
|
253
258
|
const time = th.fg("dim", formatTimestamp(entry.ts));
|
|
254
259
|
const source = entry.source === "workflow-logger" ? th.fg("dim", " [engine]") : "";
|
|
255
260
|
|
|
256
|
-
//
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
// Measure actual prefix width to truncate message accurately
|
|
262
|
+
const prefix = `${coloredIcon} ${time}${source} `;
|
|
263
|
+
const prefixWidth = visibleWidth(prefix);
|
|
264
|
+
const msgMaxWidth = Math.max(10, contentWidth - prefixWidth);
|
|
265
|
+
const msg = truncateToWidth(entry.message, msgMaxWidth, "…");
|
|
261
266
|
|
|
262
|
-
lines.push(row(`${
|
|
267
|
+
lines.push(row(`${prefix}${msg}`));
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
return lines;
|
|
@@ -275,14 +275,19 @@ function _withLock<T>(basePath: string, fn: () => T): T {
|
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
// Only run the mutation if we actually own the lock
|
|
279
|
+
const ownsLock = fd !== null;
|
|
278
280
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
if (ownsLock && fd !== null) {
|
|
282
|
+
// Write our PID timestamp into the lock for stale detection
|
|
281
283
|
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
282
284
|
closeSync(fd);
|
|
283
285
|
}
|
|
284
286
|
return fn();
|
|
285
287
|
} finally {
|
|
286
|
-
|
|
288
|
+
// Only delete the lock if we created it — never remove another process's lock
|
|
289
|
+
if (ownsLock) {
|
|
290
|
+
try { unlinkSync(lockPath); } catch { /* best-effort cleanup */ }
|
|
291
|
+
}
|
|
287
292
|
}
|
|
288
293
|
}
|
|
@@ -124,6 +124,42 @@ describe("verificationEvidence sentinel coercion (#3565)", () => {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
// ─── wrapArray coercion unit tests (#3585) ──────────────────────────────
|
|
128
|
+
|
|
129
|
+
describe("wrapArray coercion for simple string-array fields (#3585)", () => {
|
|
130
|
+
/**
|
|
131
|
+
* The wrapArray coercion logic extracted from db-tools.ts sliceCompleteExecute.
|
|
132
|
+
* Duplicated here so we can unit-test it directly.
|
|
133
|
+
*/
|
|
134
|
+
function wrapArray(v: any): any[] {
|
|
135
|
+
return v == null ? [] : Array.isArray(v) ? v : [v];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
test("null returns empty array", () => {
|
|
139
|
+
assert.deepEqual(wrapArray(null), []);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("undefined returns empty array", () => {
|
|
143
|
+
assert.deepEqual(wrapArray(undefined), []);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("plain string wraps into single-element array", () => {
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
wrapArray("Validated Tech UI flows and Portal self-service flows"),
|
|
149
|
+
["Validated Tech UI flows and Portal self-service flows"],
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("array passes through unchanged", () => {
|
|
154
|
+
const arr = ["item1", "item2"];
|
|
155
|
+
assert.deepEqual(wrapArray(arr), arr);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("empty array passes through unchanged", () => {
|
|
159
|
+
assert.deepEqual(wrapArray([]), []);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
127
163
|
// ─── Handler integration with coerced params ─────────────────────────────
|
|
128
164
|
|
|
129
165
|
describe("handleCompleteSlice with coerced string arrays (#3565)", () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
|
|
@@ -246,4 +246,37 @@ describe("notification-store", () => {
|
|
|
246
246
|
assert.equal(getUnreadCount(), 0);
|
|
247
247
|
assert.equal(getLineCount(), 0);
|
|
248
248
|
});
|
|
249
|
+
|
|
250
|
+
test("markAllRead does not delete a foreign lock file", () => {
|
|
251
|
+
initNotificationStore(tmp);
|
|
252
|
+
appendNotification("msg1", "info");
|
|
253
|
+
|
|
254
|
+
// Simulate another process holding the lock
|
|
255
|
+
const lockPath = join(tmp, ".gsd", "notifications.lock");
|
|
256
|
+
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
257
|
+
|
|
258
|
+
// markAllRead should still work (best-effort) but not delete the foreign lock
|
|
259
|
+
markAllRead();
|
|
260
|
+
|
|
261
|
+
assert.ok(existsSync(lockPath), "foreign lock file should not be deleted");
|
|
262
|
+
|
|
263
|
+
// Clean up the lock so afterEach doesn't leave artifacts
|
|
264
|
+
rmSync(lockPath, { force: true });
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("clearNotifications does not delete a foreign lock file", () => {
|
|
268
|
+
initNotificationStore(tmp);
|
|
269
|
+
appendNotification("msg1", "info");
|
|
270
|
+
|
|
271
|
+
// Simulate another process holding the lock
|
|
272
|
+
const lockPath = join(tmp, ".gsd", "notifications.lock");
|
|
273
|
+
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
274
|
+
|
|
275
|
+
// clearNotifications should still work but not delete the foreign lock
|
|
276
|
+
clearNotifications();
|
|
277
|
+
|
|
278
|
+
assert.ok(existsSync(lockPath), "foreign lock file should not be deleted");
|
|
279
|
+
|
|
280
|
+
rmSync(lockPath, { force: true });
|
|
281
|
+
});
|
|
249
282
|
});
|
|
File without changes
|
|
File without changes
|