@saifymatteo/pi-extensions 0.1.3 → 0.1.5
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/CHANGELOG.md +28 -0
- package/package.json +2 -1
- package/packages/pi-bg-bash/AGENTS.md +29 -0
- package/packages/pi-bg-bash/CHANGELOG.md +25 -0
- package/packages/pi-bg-bash/package.json +2 -1
- package/packages/pi-bg-bash/src/index.ts +189 -99
- package/packages/pi-bg-bash/src/lib/output.ts +31 -0
- package/packages/pi-bg-bash/src/lib/panel.ts +155 -0
- package/packages/pi-bg-bash/src/lib/process-control.ts +221 -0
- package/packages/pi-bg-bash/src/lib/windows-console.ts +169 -0
- package/packages/pi-bg-bash/test/job-controls.test.mjs +347 -0
- package/packages/pi-bg-bash/test/native-process-control.test.mjs +126 -0
- package/packages/pi-bg-bash/test/output.test.mjs +28 -0
- package/packages/pi-bg-bash/test/panel.test.mjs +146 -0
- package/packages/pi-bg-bash/test/process-control.test.mjs +300 -0
- package/packages/pi-bg-bash/test/windows-console.test.mjs +221 -0
- package/packages/pi-claude-usage/CHANGELOG.md +18 -0
- package/packages/pi-claude-usage/package.json +2 -1
- package/packages/pi-clipboard-image/CHANGELOG.md +18 -0
- package/packages/pi-clipboard-image/package.json +2 -1
- package/packages/pi-codeblock-borders/CHANGELOG.md +18 -0
- package/packages/pi-codeblock-borders/package.json +2 -1
- package/packages/pi-codegraph/CHANGELOG.md +25 -0
- package/packages/pi-codegraph/package.json +2 -1
- package/packages/pi-codegraph/src/index.ts +67 -27
- package/packages/pi-compact-tools/CHANGELOG.md +18 -0
- package/packages/pi-compact-tools/package.json +2 -1
- package/packages/pi-decisions/CHANGELOG.md +18 -0
- package/packages/pi-decisions/package.json +2 -1
- package/packages/pi-load-pi-dotenv/CHANGELOG.md +18 -0
- package/packages/pi-load-pi-dotenv/package.json +2 -1
- package/packages/pi-model-go/CHANGELOG.md +18 -0
- package/packages/pi-model-go/package.json +2 -1
- package/packages/pi-prestart-working/CHANGELOG.md +18 -0
- package/packages/pi-prestart-working/package.json +2 -1
- package/packages/pi-root-guard/CHANGELOG.md +18 -0
- package/packages/pi-root-guard/package.json +2 -1
- package/packages/pi-session-finder/CHANGELOG.md +18 -0
- package/packages/pi-session-finder/package.json +2 -1
- package/packages/pi-session-id/CHANGELOG.md +18 -0
- package/packages/pi-session-id/package.json +2 -1
- package/packages/pi-session-nav/CHANGELOG.md +18 -0
- package/packages/pi-session-nav/package.json +2 -1
- package/packages/pi-session-rename/CHANGELOG.md +18 -0
- package/packages/pi-session-rename/package.json +2 -1
- package/packages/pi-session-retention/AGENTS.md +15 -0
- package/packages/pi-session-retention/CHANGELOG.md +25 -0
- package/packages/pi-session-retention/package.json +2 -1
- package/packages/pi-session-retention/src/index.ts +43 -4
- package/packages/pi-session-retention/test/startup.test.mjs +229 -0
- package/packages/pi-skill-usage-log/CHANGELOG.md +18 -0
- package/packages/pi-skill-usage-log/package.json +2 -1
- package/packages/pi-tool-usage-log/CHANGELOG.md +18 -0
- package/packages/pi-tool-usage-log/package.json +2 -1
- package/packages/pi-tts/CHANGELOG.md +18 -0
- package/packages/pi-tts/package.json +2 -1
- package/packages/pi-user-image-previews/CHANGELOG.md +18 -0
- package/packages/pi-user-image-previews/package.json +2 -1
- package/packages/pi-vision/CHANGELOG.md +18 -0
- package/packages/pi-vision/package.json +2 -1
- package/packages/pi-wait/CHANGELOG.md +18 -0
- package/packages/pi-wait/package.json +2 -1
- package/packages/pi-zvec/CHANGELOG.md +25 -0
- package/packages/pi-zvec/package.json +2 -1
- package/packages/pi-zvec/src/index.ts +81 -39
|
@@ -121,6 +121,9 @@ async function trashSearchText(trash: string, batch: TrashBatch): Promise<{ sear
|
|
|
121
121
|
export default function sessionRetention(pi: ExtensionAPI, options: { guard?: () => Promise<void>; now?: () => number } = {}) {
|
|
122
122
|
const now = options.now ?? Date.now;
|
|
123
123
|
let started = false;
|
|
124
|
+
let pendingAutomatic = false;
|
|
125
|
+
let automaticTimer: ReturnType<typeof setTimeout> | undefined;
|
|
126
|
+
let automatic: Promise<void> | undefined;
|
|
124
127
|
let maintaining = false;
|
|
125
128
|
let lifecycle = new AbortController();
|
|
126
129
|
let operation: Promise<unknown> | undefined;
|
|
@@ -159,10 +162,18 @@ export default function sessionRetention(pi: ExtensionAPI, options: { guard?: ()
|
|
|
159
162
|
};
|
|
160
163
|
pi.on("session_before_switch", preventSwitch);
|
|
161
164
|
pi.on("session_before_fork", preventSwitch);
|
|
165
|
+
function cancelAutomatic() {
|
|
166
|
+
pendingAutomatic = false;
|
|
167
|
+
if (automaticTimer !== undefined) clearTimeout(automaticTimer);
|
|
168
|
+
automaticTimer = undefined;
|
|
169
|
+
}
|
|
162
170
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
171
|
+
cancelAutomatic();
|
|
163
172
|
status(ctx, undefined);
|
|
164
173
|
lifecycle.abort();
|
|
165
174
|
closeDialog?.();
|
|
175
|
+
// Drain the background handler as well as file work before replacing its context.
|
|
176
|
+
await automatic?.catch(() => {});
|
|
166
177
|
// Never release a lock while a file operation can still be using it.
|
|
167
178
|
await operation?.catch(() => {});
|
|
168
179
|
started = false;
|
|
@@ -199,7 +210,13 @@ export default function sessionRetention(pi: ExtensionAPI, options: { guard?: ()
|
|
|
199
210
|
].join("\n");
|
|
200
211
|
if (previewOnly) { status(ctx, "Session cleanup: preview — no changes"); await ctx.ui.confirm("Preview only — nothing will be changed", summary, { signal }); return; }
|
|
201
212
|
if (!state.approved || state.paused) {
|
|
202
|
-
if (!manual
|
|
213
|
+
if (!manual) {
|
|
214
|
+
// Background discovery must not replace the editor with an approval dialog.
|
|
215
|
+
if (count || expired.length) ctx.ui.notify("Session cleanup is available. Run /session-cleanup to review and enable daily automatic cleanup.", "info");
|
|
216
|
+
state.lastRun = time;
|
|
217
|
+
await saveState(paths.trash, state);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
203
220
|
status(ctx, "Session cleanup: awaiting approval…");
|
|
204
221
|
const approved = await ctx.ui.confirm("Enable daily session cleanup?", `${summary}\n\nApprove this cleanup and future automatic cleanup at startup (at most daily)?`, { signal });
|
|
205
222
|
signal.throwIfAborted();
|
|
@@ -228,13 +245,33 @@ export default function sessionRetention(pi: ExtensionAPI, options: { guard?: ()
|
|
|
228
245
|
});
|
|
229
246
|
}
|
|
230
247
|
|
|
231
|
-
|
|
248
|
+
function scheduleAutomatic(ctx: ExtensionContext) {
|
|
249
|
+
if (!pendingAutomatic || automaticTimer !== undefined || automatic || maintaining) return;
|
|
250
|
+
const signal = lifecycle.signal;
|
|
251
|
+
if (signal.aborted) return;
|
|
252
|
+
// Yield before starting maintenance; startup never awaits this task.
|
|
253
|
+
automaticTimer = setTimeout(() => {
|
|
254
|
+
automaticTimer = undefined;
|
|
255
|
+
if (signal.aborted || !pendingAutomatic || !ctx.isIdle() || maintaining) return;
|
|
256
|
+
pendingAutomatic = false;
|
|
257
|
+
automatic = cleanup(ctx, false)
|
|
258
|
+
.catch((error) => {
|
|
259
|
+
if (!signal.aborted) {
|
|
260
|
+
try { ctx.ui.notify(message(error), "warning"); } catch { /* UI failures must not reject a detached task. */ }
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
.finally(() => { automatic = undefined; });
|
|
264
|
+
}, 0);
|
|
265
|
+
automaticTimer.unref();
|
|
266
|
+
}
|
|
267
|
+
pi.on("session_start", (_event, ctx) => {
|
|
232
268
|
if (lifecycle.signal.aborted) lifecycle = new AbortController();
|
|
233
269
|
if (started || ctx.mode !== "tui" || !ctx.sessionManager.getSessionFile()) return;
|
|
234
270
|
started = true;
|
|
235
|
-
|
|
236
|
-
|
|
271
|
+
pendingAutomatic = true;
|
|
272
|
+
scheduleAutomatic(ctx);
|
|
237
273
|
});
|
|
274
|
+
pi.on("agent_settled", (_event, ctx) => { scheduleAutomatic(ctx); });
|
|
238
275
|
|
|
239
276
|
pi.registerCommand("session-cleanup", {
|
|
240
277
|
description: "Clean inactive session families [preview|pause]",
|
|
@@ -250,6 +287,7 @@ export default function sessionRetention(pi: ExtensionAPI, options: { guard?: ()
|
|
|
250
287
|
const signal = lifecycle.signal;
|
|
251
288
|
const action = args.trim();
|
|
252
289
|
if (!["", "preview", "pause"].includes(action)) return ctx.ui.notify("Usage: /session-cleanup [preview|pause]", "info");
|
|
290
|
+
cancelAutomatic();
|
|
253
291
|
try {
|
|
254
292
|
if (action === "pause") {
|
|
255
293
|
const paths = storePaths(ctx);
|
|
@@ -265,6 +303,7 @@ export default function sessionRetention(pi: ExtensionAPI, options: { guard?: ()
|
|
|
265
303
|
description: "Search Trash by names, first prompts, IDs or paths; restore a whole family",
|
|
266
304
|
handler: async (args, ctx) => {
|
|
267
305
|
if (ctx.mode !== "tui") return ctx.ui.notify("Session Trash requires interactive mode.", "warning");
|
|
306
|
+
cancelAutomatic();
|
|
268
307
|
const signal = lifecycle.signal;
|
|
269
308
|
try {
|
|
270
309
|
await ctx.waitForIdle();
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, readFile, access, rm, realpath, utimes } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { setImmediate } from 'node:timers/promises';
|
|
7
|
+
import { loadExtension } from '../../../test/load-extension.mjs';
|
|
8
|
+
import * as core from '../src/core.ts';
|
|
9
|
+
|
|
10
|
+
const deferred = () => {
|
|
11
|
+
let resolve;
|
|
12
|
+
const promise = new Promise(done => { resolve = done; });
|
|
13
|
+
return { promise, resolve };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
async function harness(t, { old = false, state, holdScan = false, scanError = false } = {}) {
|
|
17
|
+
const dir = await mkdtemp(join(await realpath(tmpdir()), 'pi-retention-startup-'));
|
|
18
|
+
const root = join(dir, 'sessions');
|
|
19
|
+
const file = join(root, 'session.jsonl');
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const stamp = old ? now - core.RETENTION_MS - 60_000 : now;
|
|
22
|
+
await mkdir(root);
|
|
23
|
+
await writeFile(file, JSON.stringify({ type: 'session', id: 'fixture', timestamp: new Date(stamp).toISOString() }) + '\n');
|
|
24
|
+
await utimes(file, new Date(stamp), new Date(stamp));
|
|
25
|
+
const entered = deferred(), release = deferred(), finished = deferred(), notified = deferred();
|
|
26
|
+
const timers = new Map(), hooks = {}, commands = {}, notifications = [], confirmations = [];
|
|
27
|
+
let scans = 0, probes = 0, nextTimer = 0;
|
|
28
|
+
const extension = loadExtension(new URL('../src/index.ts', import.meta.url), {
|
|
29
|
+
'./core.ts': {
|
|
30
|
+
...core,
|
|
31
|
+
RetentionStore: class extends core.RetentionStore {
|
|
32
|
+
async scan(options) {
|
|
33
|
+
scans++;
|
|
34
|
+
entered.resolve();
|
|
35
|
+
if (holdScan) await release.promise;
|
|
36
|
+
if (scanError) throw new Error('fixture scan failed');
|
|
37
|
+
return super.scan(options);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}, {
|
|
42
|
+
performance,
|
|
43
|
+
setTimeout: callback => { const id = { id: ++nextTimer, unref() {} }; timers.set(id, callback); return id; },
|
|
44
|
+
clearTimeout: id => { timers.delete(id); },
|
|
45
|
+
});
|
|
46
|
+
extension.default({
|
|
47
|
+
on: (name, handler) => { hooks[name] = handler; },
|
|
48
|
+
registerCommand: (name, command) => { commands[name] = command; },
|
|
49
|
+
}, { now: () => now, guard: async () => { probes++; } });
|
|
50
|
+
const ctx = {
|
|
51
|
+
mode: 'tui',
|
|
52
|
+
idle: true,
|
|
53
|
+
isIdle() { return this.idle; },
|
|
54
|
+
sessionManager: { getSessionDir: () => root, getCwd: () => dir, getSessionFile: () => join(root, 'active.jsonl') },
|
|
55
|
+
waitForIdle: async () => {},
|
|
56
|
+
ui: {
|
|
57
|
+
custom: async () => undefined,
|
|
58
|
+
setStatus: (_key, text) => { if (text === undefined) finished.resolve(); },
|
|
59
|
+
notify: (text, level) => { notifications.push({ text, level }); notified.resolve(); },
|
|
60
|
+
confirm: async (...args) => { confirmations.push(args); return true; },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const { trash } = extension.storePaths(ctx);
|
|
64
|
+
if (state) {
|
|
65
|
+
await mkdir(trash, { recursive: true });
|
|
66
|
+
await writeFile(join(trash, 'settings.json'), JSON.stringify({ approved: true, paused: false, lastRun: 0, ...state }));
|
|
67
|
+
}
|
|
68
|
+
t.after(async () => {
|
|
69
|
+
release.resolve();
|
|
70
|
+
await hooks.session_shutdown({}, ctx);
|
|
71
|
+
await rm(dir, { recursive: true, force: true });
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
ctx, hooks, commands, timers, notifications, confirmations, file, trash, now, entered, release, finished, notified,
|
|
75
|
+
get scans() { return scans; },
|
|
76
|
+
get probes() { return probes; },
|
|
77
|
+
runTimer() {
|
|
78
|
+
assert.equal(timers.size, 1);
|
|
79
|
+
const [id, callback] = timers.entries().next().value;
|
|
80
|
+
timers.delete(id);
|
|
81
|
+
callback();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test('startup returns before scanning; shutdown drains background work before releasing its lock', async t => {
|
|
87
|
+
const h = await harness(t, { holdScan: true });
|
|
88
|
+
assert.equal(h.hooks.session_start({}, h.ctx), undefined);
|
|
89
|
+
assert.equal(h.scans, 0);
|
|
90
|
+
assert.equal(h.timers.size, 1);
|
|
91
|
+
h.hooks.session_start({}, h.ctx);
|
|
92
|
+
assert.equal(h.timers.size, 1);
|
|
93
|
+
h.runTimer();
|
|
94
|
+
await h.entered.promise;
|
|
95
|
+
assert.equal(h.hooks.session_before_switch({}, h.ctx).cancel, true);
|
|
96
|
+
assert.equal(h.hooks.session_before_fork({}, h.ctx).cancel, true);
|
|
97
|
+
let stopped = false;
|
|
98
|
+
const shutdown = h.hooks.session_shutdown({}, h.ctx).then(() => { stopped = true; });
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
assert.equal(stopped, false);
|
|
101
|
+
await access(join(h.trash, '.maintenance.lock'));
|
|
102
|
+
h.release.resolve();
|
|
103
|
+
await shutdown;
|
|
104
|
+
await assert.rejects(access(join(h.trash, '.maintenance.lock')), { code: 'ENOENT' });
|
|
105
|
+
assert.equal(h.notifications.some(item => item.level === 'warning' && /aborted/i.test(item.text)), false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('busy startup defers until agent_settled and never schedules duplicate scans', async t => {
|
|
109
|
+
const h = await harness(t);
|
|
110
|
+
h.ctx.idle = false;
|
|
111
|
+
h.hooks.session_start({}, h.ctx);
|
|
112
|
+
h.runTimer();
|
|
113
|
+
assert.equal(h.scans, 0);
|
|
114
|
+
assert.equal(h.timers.size, 0);
|
|
115
|
+
h.ctx.idle = true;
|
|
116
|
+
h.hooks.agent_settled({}, h.ctx);
|
|
117
|
+
h.hooks.agent_settled({}, h.ctx);
|
|
118
|
+
h.runTimer();
|
|
119
|
+
await h.finished.promise;
|
|
120
|
+
assert.equal(h.scans, 1);
|
|
121
|
+
h.hooks.agent_settled({}, h.ctx);
|
|
122
|
+
assert.equal(h.timers.size, 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('automatic discovery notifies instead of opening approval; explicit cleanup can approve', async t => {
|
|
126
|
+
const h = await harness(t, { old: true });
|
|
127
|
+
h.hooks.session_start({}, h.ctx);
|
|
128
|
+
h.runTimer();
|
|
129
|
+
await h.finished.promise;
|
|
130
|
+
assert.equal(h.confirmations.length, 0);
|
|
131
|
+
assert.ok(h.notifications.some(item => /\/session-cleanup/.test(item.text)));
|
|
132
|
+
await access(h.file);
|
|
133
|
+
assert.equal(h.probes, 0);
|
|
134
|
+
const state = JSON.parse(await readFile(join(h.trash, 'settings.json'), 'utf8'));
|
|
135
|
+
assert.equal(state.approved, false);
|
|
136
|
+
assert.equal(state.lastRun, h.now);
|
|
137
|
+
await h.commands['session-cleanup'].handler('', h.ctx);
|
|
138
|
+
assert.equal(h.confirmations.length, 1);
|
|
139
|
+
assert.deepEqual(h.notifications.filter(item => item.level === 'error'), []);
|
|
140
|
+
assert.ok(h.probes > 0);
|
|
141
|
+
await assert.rejects(access(h.file), { code: 'ENOENT' });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('previously approved automatic cleanup still moves eligible sessions without a dialog', async t => {
|
|
145
|
+
const h = await harness(t, { old: true, state: {} });
|
|
146
|
+
h.hooks.session_start({}, h.ctx);
|
|
147
|
+
h.runTimer();
|
|
148
|
+
await h.finished.promise;
|
|
149
|
+
assert.equal(h.confirmations.length, 0);
|
|
150
|
+
assert.deepEqual(h.notifications.filter(item => item.level === 'warning'), []);
|
|
151
|
+
assert.ok(h.probes > 0);
|
|
152
|
+
await assert.rejects(access(h.file), { code: 'ENOENT' });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('daily and paused settings still skip scans in the background', async t => {
|
|
156
|
+
for (const state of [{ lastRun: Date.now() }, { paused: true }]) {
|
|
157
|
+
await t.test(JSON.stringify(state), async t => {
|
|
158
|
+
const h = await harness(t, { state });
|
|
159
|
+
h.hooks.session_start({}, h.ctx);
|
|
160
|
+
h.runTimer();
|
|
161
|
+
await h.finished.promise;
|
|
162
|
+
assert.equal(h.scans, 0);
|
|
163
|
+
assert.equal(h.probes, 0);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('shutdown cancels queued maintenance and a new session can schedule again', async t => {
|
|
169
|
+
const h = await harness(t);
|
|
170
|
+
h.hooks.session_start({}, h.ctx);
|
|
171
|
+
await h.hooks.session_shutdown({}, h.ctx);
|
|
172
|
+
assert.equal(h.timers.size, 0);
|
|
173
|
+
assert.equal(h.scans, 0);
|
|
174
|
+
h.hooks.session_start({}, h.ctx);
|
|
175
|
+
h.runTimer();
|
|
176
|
+
await h.entered.promise;
|
|
177
|
+
await h.hooks.session_shutdown({}, h.ctx);
|
|
178
|
+
assert.equal(h.scans, 1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('explicit maintenance cancels queued automatic work', async t => {
|
|
182
|
+
for (const [command, args] of [['session-cleanup', 'pause'], ['session-trash', '']]) {
|
|
183
|
+
await t.test(command, async t => {
|
|
184
|
+
const h = await harness(t);
|
|
185
|
+
h.hooks.session_start({}, h.ctx);
|
|
186
|
+
await h.commands[command].handler(args, h.ctx);
|
|
187
|
+
assert.equal(h.timers.size, 0);
|
|
188
|
+
h.hooks.agent_settled({}, h.ctx);
|
|
189
|
+
assert.equal(h.timers.size, 0);
|
|
190
|
+
assert.equal(h.scans, 0);
|
|
191
|
+
assert.deepEqual(h.notifications.filter(item => item.level === 'error'), []);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('non-TUI modes and sessions without a file schedule no maintenance', async t => {
|
|
197
|
+
const h = await harness(t);
|
|
198
|
+
for (const mode of ['print', 'json', 'rpc']) {
|
|
199
|
+
h.ctx.mode = mode;
|
|
200
|
+
h.hooks.session_start({}, h.ctx);
|
|
201
|
+
assert.equal(h.timers.size, 0);
|
|
202
|
+
}
|
|
203
|
+
h.ctx.mode = 'tui';
|
|
204
|
+
h.ctx.sessionManager.getSessionFile = () => undefined;
|
|
205
|
+
h.hooks.session_start({}, h.ctx);
|
|
206
|
+
assert.equal(h.timers.size, 0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('background failures are reported and release the maintenance lock', async t => {
|
|
210
|
+
const h = await harness(t, { scanError: true });
|
|
211
|
+
h.hooks.session_start({}, h.ctx);
|
|
212
|
+
h.runTimer();
|
|
213
|
+
await h.notified.promise;
|
|
214
|
+
await h.hooks.session_shutdown({}, h.ctx);
|
|
215
|
+
assert.ok(h.notifications.some(item => /fixture scan failed/.test(item.text)));
|
|
216
|
+
await assert.rejects(access(join(h.trash, '.maintenance.lock')), { code: 'ENOENT' });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('a failed UI notification cannot cause an unhandled background rejection', async t => {
|
|
220
|
+
const h = await harness(t, { scanError: true });
|
|
221
|
+
const notify = h.ctx.ui.notify;
|
|
222
|
+
h.ctx.ui.notify = (...args) => { notify(...args); throw new Error('fixture UI failed'); };
|
|
223
|
+
h.hooks.session_start({}, h.ctx);
|
|
224
|
+
h.runTimer();
|
|
225
|
+
await h.notified.promise;
|
|
226
|
+
// Give detached promise rejections a turn to surface in the test runner.
|
|
227
|
+
await setImmediate();
|
|
228
|
+
await assert.rejects(access(join(h.trash, '.maintenance.lock')), { code: 'ENOENT' });
|
|
229
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-skill-usage-log",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
12
|
"README.md",
|
|
13
|
+
"CHANGELOG.md",
|
|
13
14
|
"NOTICE.md",
|
|
14
15
|
"LICENSE"
|
|
15
16
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-tool-usage-log",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
12
|
"README.md",
|
|
13
|
+
"CHANGELOG.md",
|
|
13
14
|
"NOTICE.md",
|
|
14
15
|
"LICENSE"
|
|
15
16
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-tts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"src",
|
|
12
12
|
"examples",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"NOTICE.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-user-image-previews",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Adds collapsible UI-only previews for newly submitted image attachments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"src",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"NOTICE.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as both an extension and a skill package in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-vision",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"src",
|
|
13
13
|
"examples",
|
|
14
14
|
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
15
16
|
"NOTICE.md",
|
|
16
17
|
"LICENSE",
|
|
17
18
|
"skills"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.4]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.3]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
18
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-wait",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"src",
|
|
12
12
|
"examples",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"NOTICE.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release notes are recorded from 0.1.3 onward.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.5]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Include package release notes in Pi 1.5.0's Extension Updates.
|
|
12
|
+
|
|
13
|
+
## [0.1.4]
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Defer CLI checks, configuration, and index initialization until after session startup to avoid blocking Pi.
|
|
18
|
+
- Cancel stale initialization when the active session changes.
|
|
19
|
+
|
|
20
|
+
## [0.1.3]
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Classify the package as an extension in the Pi package gallery.
|
|
25
|
+
- Show the package screenshot as its gallery preview.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saifymatteo/pi-zvec",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"src",
|
|
12
12
|
"examples",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"NOTICE.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|