@pify/shell-background 0.1.0 → 0.1.2
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/README.md +6 -2
- package/extensions/shell-background.ts +21 -2
- package/package.json +1 -1
- package/src/spawn.ts +5 -0
package/README.md
CHANGED
|
@@ -56,9 +56,13 @@ Put these in `.pi/shell-background.json` (project) or `<agentDir>/shell-backgrou
|
|
|
56
56
|
|
|
57
57
|
`autoBackgroundMs` is how long a foreground command may run before it auto-backgrounds; set it to `0` to disable auto-background (explicit `background: true` still works). `PIFY_SHELL_BG_MS` overrides it for one run or in CI. `tailBytes` bounds how much of a job's log a status result shows. Bad values fall back to the defaults with a warning rather than taking the tool down.
|
|
58
58
|
|
|
59
|
-
## Coexistence
|
|
59
|
+
## Coexistence with @pify/pretty
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Both this package and `@pify/pretty` re-register `bash`: this one to change its *execution* (async), pretty to change its *rendering* (compact, syntax-highlit). pi's `registerTool` is last-write-wins and gives an extension no way to read or wrap another's registered tool, so the two cannot be merged — whichever loads last wins the whole `bash` tool.
|
|
62
|
+
|
|
63
|
+
**Load `@pify/shell-background` after `@pify/pretty`.** Async bash is the reason to install this package, so it should own execution; pretty keeps rendering every other tool (`read`, `edit`, `grep`, `write`, `ls`, `find`) — only its `bash`-specific rendering yields, and you still get pi's default bash view here. If pretty loads last instead, *this package's async execution is lost* and bash reverts to blocking — the outcome to avoid.
|
|
64
|
+
|
|
65
|
+
(A future pi API to compose registered tools would let both apply at once; today none exists.)
|
|
62
66
|
|
|
63
67
|
## License
|
|
64
68
|
|
|
@@ -47,6 +47,7 @@ import { DEFAULT_SETTINGS, resolveSettings, type ShellBgSettings } from "../src/
|
|
|
47
47
|
import { backgroundedResult, deliveryMessage, DELIVERY_TYPE } from "../src/pending.ts";
|
|
48
48
|
import { formatResult, formatList, header } from "../src/format.ts";
|
|
49
49
|
import { buildWidgetLines } from "../src/widget.ts";
|
|
50
|
+
import { isFinished } from "../src/types.ts";
|
|
50
51
|
import type { Job } from "../src/types.ts";
|
|
51
52
|
|
|
52
53
|
type UiContext = ExtensionContext;
|
|
@@ -236,6 +237,19 @@ export default function shellBackground(pi: ExtensionAPI) {
|
|
|
236
237
|
|
|
237
238
|
if (outcome === "auto") {
|
|
238
239
|
scheduleDelivery(job, settle);
|
|
240
|
+
// A timeout the caller set still applies once the command is in the
|
|
241
|
+
// background: schedule the kill for the time it has left so the deadline
|
|
242
|
+
// the model expects is honoured rather than silently dropped. This timer
|
|
243
|
+
// deliberately outlives the `finally` below, so it is not in `timers`.
|
|
244
|
+
if (params.timeout && params.timeout > 0) {
|
|
245
|
+
const remaining = params.timeout * 1000 - (Date.now() - job.startedAt);
|
|
246
|
+
const killAt = setTimeout(() => {
|
|
247
|
+
if (job.status !== "running") return;
|
|
248
|
+
job.killedByUs = true;
|
|
249
|
+
killTree(job.pid);
|
|
250
|
+
}, Math.max(0, remaining));
|
|
251
|
+
killAt.unref?.();
|
|
252
|
+
}
|
|
239
253
|
const r = backgroundedResult({
|
|
240
254
|
id: job.id,
|
|
241
255
|
command,
|
|
@@ -314,8 +328,13 @@ export default function shellBackground(pi: ExtensionAPI) {
|
|
|
314
328
|
const known = registry.all().map((j) => j.id).join(", ") || "(none)";
|
|
315
329
|
return { content: [{ type: "text", text: `No job "${id}". Known: ${known}` }], details: {}, isError: true };
|
|
316
330
|
}
|
|
317
|
-
// Reading marks it collected so it will not also be
|
|
318
|
-
|
|
331
|
+
// Reading a finished job marks it collected so it will not also be
|
|
332
|
+
// delivered unasked. A still-running poll must never do this: setting
|
|
333
|
+
// delivered here would permanently cancel the promised auto-delivery.
|
|
334
|
+
if (isFinished(job)) {
|
|
335
|
+
job.delivered = true;
|
|
336
|
+
registry.persist(job);
|
|
337
|
+
}
|
|
319
338
|
return {
|
|
320
339
|
content: [{ type: "text", text: formatResult(job, settings.tailBytes) }],
|
|
321
340
|
details: { id: job.id, status: job.status, exitCode: job.exitCode },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pify/shell-background",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Long-running bash goes async: background: true launches detached, and any command still running after 30s auto-backgrounds and delivers its result when it finishes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/spawn.ts
CHANGED
|
@@ -41,6 +41,11 @@ export function spawnToFile(
|
|
|
41
41
|
): Spawned {
|
|
42
42
|
// Append so a re-attach or racing read never clips output already written.
|
|
43
43
|
const out = createWriteStream(logPath, { flags: "a" });
|
|
44
|
+
// A write stream with no 'error' listener turns any disk error (ENOSPC,
|
|
45
|
+
// EACCES) or a stray write-after-end into an uncaught exception that takes the
|
|
46
|
+
// whole pi host down. Losing a log line is survivable; crashing the host is
|
|
47
|
+
// not — so swallow it here.
|
|
48
|
+
out.on("error", () => {});
|
|
44
49
|
|
|
45
50
|
let child;
|
|
46
51
|
try {
|