@pify/shell-background 0.1.1 → 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.
@@ -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 delivered unasked.
318
- job.delivered = true;
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.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 {