@naiba/pi-durable-background-tasks 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -2,7 +2,15 @@
2
2
 
3
3
  Supervised background shell commands for Pi Durable. No delegated agents, model configuration, or implicit model calls.
4
4
 
5
- **Initial development version. Linux only; requires Python 3.9+ and Linux pidfd support.** Not yet published to npm.
5
+ **Initial development version. Linux only; requires Python 3.9+ and Linux pidfd support.**
6
+
7
+ ```sh
8
+ bun add @naiba/pi-durable-background-tasks
9
+ ```
10
+
11
+ Available on [npm](https://www.npmjs.com/package/@naiba/pi-durable-background-tasks), with GitHub Actions Trusted Publishing and provenance. Node.js 22+ is required; Bun loads the TypeScript source exports, while Node loads the compiled JavaScript.
12
+
13
+ This is an extension for **Pi Durable**, not an auto-discovered Pi coding-agent CLI plugin. The host must install the extension in its registry and own its lifecycle. It does not manage Claude Code or Codex background tasks.
6
14
 
7
15
  ## Usage
8
16
 
@@ -10,6 +18,7 @@ Supervised background shell commands for Pi Durable. No delegated agents, model
10
18
  import {BackgroundTasks} from "@naiba/pi-durable-background-tasks";
11
19
  import {backgroundTasksExtension} from "@naiba/pi-durable-background-tasks/pi";
12
20
 
21
+ // `registry` is your host's Pi Durable registry.
13
22
  const tasks = new BackgroundTasks("/persistent/session/background");
14
23
  registry.install(backgroundTasksExtension(tasks, "/workspace"));
15
24
  // Register your normal Pi beforeTool approval policy. This package does not bypass it.
@@ -19,7 +28,14 @@ await tasks.close();
19
28
  unsubscribe();
20
29
  ```
21
30
 
22
- Tools: `background_start`, `background_status`, `background_output`, `background_wait`, `background_stop`.
31
+ | Tool | Input | Purpose |
32
+ | --- | --- | --- |
33
+ | `background_start` | `command`, optional `name` | Start a session-owned shell command |
34
+ | `background_status` | `{}` | List commands and their recorded state |
35
+ | `background_output` | `id`, optional `offset`, `limit` | Read up to 64 KiB using byte offsets |
36
+ | `background_wait` | `id`, `timeout_ms` | Observe completion for a bounded interval |
37
+ | `background_stop` | `id` | Stop a command and await supervisor cleanup |
38
+
23
39
  Wait requires `timeout_ms` between 1 and 600000. Observation timeout/cancellation does not stop the command. Commands have no implicit execution deadline.
24
40
 
25
41
  The core API works without creating a model session. `start` requires a stable `requestId`; repeats return the original record and mismatching arguments are rejected. Recovered records never automatically relaunch a command. Use one service instance per directory and one writer process; concurrent writers are not supported.
@@ -34,6 +50,12 @@ Records and logs survive reopening; live process continuity across host restart
34
50
 
35
51
  Output reads are bounded to 64 KiB, with byte offsets. Logs are retained without automatic deletion; configure filesystem quotas at the host level. Commands run with inherited environment and host permissions. Do not expose this service directly to untrusted clients; isolate task directories by session and authorize every tool/HTTP action.
36
52
 
53
+ ## CloudCode integration
54
+
55
+ [CloudCode](https://github.com/naiba/cloudcode) consumes this npm package for Pi sessions. It provides a collapsed background-activity panel, on-demand logs, explicit stop controls, native Pi approval, and protection against releasing/deleting sessions with active commands. Closing a browser is not host shutdown and does not stop commands.
56
+
57
+ The package itself has no Web UI and does not automatically resume a model when a command completes. CloudCode's Claude Code/Codex ACP background activity adapters are not implemented by this package; their execution must remain native.
58
+
37
59
  ## Development
38
60
 
39
61
  ```sh
@@ -43,3 +65,14 @@ bun test
43
65
  ```
44
66
 
45
67
  Tests exercise real Linux processes rather than mocked cancellation. The build includes the Python supervisor beside the JavaScript distribution.
68
+
69
+ ## Publishing
70
+
71
+ Releases use `.github/workflows/publish.yml` on pushed `v*` tags, with npm Trusted Publishing (OIDC); no npm token secret is required. Configure this package's GitHub Actions trusted publisher on npm:
72
+
73
+ - Organization or user: `naiba`
74
+ - Repository: `pi-durable-background-tasks`
75
+ - Workflow filename: `publish.yml`
76
+ - Environment: leave empty (the workflow does not declare an environment)
77
+
78
+ Update `package.json` to a new stable version, refresh `bun.lock` if needed, commit and push, then push the matching tag (for example, `v0.1.2` for version `0.1.2`). The tag-triggered publish workflow first calls the reusable `test.yml` workflow on the same tag. Its publish job uses `needs: test`, so failed or cancelled tests prevent publication. Branch pushes and pull requests also run `test.yml`; tag pushes avoid a redundant standalone test run. After tests succeed, the publish job rejects mismatching or prerelease versions, rebuilds the package, then publishes publicly with provenance. Published versions are immutable: `0.1.0` and `0.1.1` already exist and must not be republished. Never move a published release tag. There is no manual-dispatch publish path.
package/dist/pi.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { defineExtension, defineTool } from "@earendil-works/pi-durable";
2
2
  import { Type } from "@earendil-works/pi-ai";
3
- const result = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
3
+ // Pi 1.2 consumes output; content silently hides task IDs and status from the model.
4
+ const result = (value) => ({ output: [{ type: "text", text: JSON.stringify(value) }] });
4
5
  export function backgroundTasksExtension(service, cwd) {
5
6
  return defineExtension({ name: "background-tasks", tools: [
6
- defineTool({ name: "background_start", description: "Start a supervised background shell command. Returns immediately; no model delegation. Commands do not survive host loss. Approval belongs to the host tool policy.", replay: "unsafe",
7
+ defineTool({ name: "background_start", description: "Start a supervised background shell command. Returns immediately; continue independent work before checking results. Do not immediately wait when other work is available; no model delegation. Commands do not survive host loss. Approval belongs to the host tool policy.", replay: "unsafe",
7
8
  parameters: Type.Object({ command: Type.String(), name: Type.Optional(Type.String()) }),
8
9
  execute: async (args, api) => result(service.start({ ...args, cwd, requestId: String(api.taskId) })) }),
9
10
  defineTool({ name: "background_status", description: "List background commands owned by this session.", replay: "safe", parameters: Type.Object({}), execute: async () => result(service.list()) }),
10
11
  defineTool({ name: "background_output", description: "Read bounded background output using byte offsets.", replay: "safe",
11
12
  parameters: Type.Object({ id: Type.String(), offset: Type.Optional(Type.Integer({ minimum: 0 })), limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 65536 })) }),
12
13
  execute: async (args) => result(await service.output(args.id, args.offset, args.limit)) }),
13
- defineTool({ name: "background_wait", description: "Wait for a background command. Explicit timeout ends observation only, not execution.", replay: "safe",
14
+ defineTool({ name: "background_wait", description: "Blocking observation of a background command. Use only when the next step requires its result and no independent work remains; otherwise continue working and check status later. Explicit timeout ends observation only, not execution.", replay: "safe",
14
15
  parameters: Type.Object({ id: Type.String(), timeout_ms: Type.Integer({ minimum: 1, maximum: 600000 }) }),
15
16
  execute: async (args, api, ctx) => result(await service.wait(args.id, args.timeout_ms, ctx.abortSignal)) }),
16
17
  defineTool({ name: "background_stop", description: "Stop a session-owned command and reap its descendants. Returns after supervisor cleanup.", replay: "unsafe",
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@naiba/pi-durable-background-tasks",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Background shell tools for Pi Durable; no model delegation",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/naiba/pi-durable-background-tasks.git"
9
+ "url": "git+https://github.com/naiba/pi-durable-background-tasks.git"
10
10
  },
11
11
  "exports": {
12
12
  ".": {
@@ -36,14 +36,14 @@
36
36
  "check": "tsc --noEmit"
37
37
  },
38
38
  "peerDependencies": {
39
- "@earendil-works/pi-durable": "^1.0.4",
40
- "@earendil-works/pi-ai": "^1.0.4"
39
+ "@earendil-works/pi-durable": "^1.2.0",
40
+ "@earendil-works/pi-ai": "^1.2.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@types/node": "^24.0.0",
44
- "@types/bun": "latest",
45
- "typescript": "~5.9.3",
46
- "@earendil-works/pi-durable": "1.0.4",
47
- "@earendil-works/pi-ai": "1.0.4"
43
+ "@types/node": "^26.6.5",
44
+ "@types/bun": "^1.4.3",
45
+ "typescript": "~7.0.2",
46
+ "@earendil-works/pi-durable": "1.2.0",
47
+ "@earendil-works/pi-ai": "1.2.0"
48
48
  }
49
49
  }
package/src/pi.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import {defineExtension,defineTool} from "@earendil-works/pi-durable";
2
2
  import {Type} from "@earendil-works/pi-ai";
3
3
  import {BackgroundTasks} from "./index.js";
4
- const result=(value:unknown)=>({content:[{type:"text" as const,text:JSON.stringify(value)}]});
4
+ // Pi 1.2 consumes output; content silently hides task IDs and status from the model.
5
+ const result=(value:unknown)=>({output:[{type:"text" as const,text:JSON.stringify(value)}]});
5
6
  export function backgroundTasksExtension(service:BackgroundTasks,cwd:string){
6
7
  return defineExtension({name:"background-tasks",tools:[
7
- defineTool({name:"background_start",description:"Start a supervised background shell command. Returns immediately; no model delegation. Commands do not survive host loss. Approval belongs to the host tool policy.",replay:"unsafe",
8
+ defineTool({name:"background_start",description:"Start a supervised background shell command. Returns immediately; continue independent work before checking results. Do not immediately wait when other work is available; no model delegation. Commands do not survive host loss. Approval belongs to the host tool policy.",replay:"unsafe",
8
9
  parameters:Type.Object({command:Type.String(),name:Type.Optional(Type.String())}),
9
10
  execute:async(args,api)=>result(service.start({...args,cwd,requestId:String(api.taskId)}))}),
10
11
  defineTool({name:"background_status",description:"List background commands owned by this session.",replay:"safe",parameters:Type.Object({}),execute:async()=>result(service.list())}),
11
12
  defineTool({name:"background_output",description:"Read bounded background output using byte offsets.",replay:"safe",
12
13
  parameters:Type.Object({id:Type.String(),offset:Type.Optional(Type.Integer({minimum:0})),limit:Type.Optional(Type.Integer({minimum:1,maximum:65536}))}),
13
14
  execute:async(args)=>result(await service.output(args.id,args.offset,args.limit))}),
14
- defineTool({name:"background_wait",description:"Wait for a background command. Explicit timeout ends observation only, not execution.",replay:"safe",
15
+ defineTool({name:"background_wait",description:"Blocking observation of a background command. Use only when the next step requires its result and no independent work remains; otherwise continue working and check status later. Explicit timeout ends observation only, not execution.",replay:"safe",
15
16
  parameters:Type.Object({id:Type.String(),timeout_ms:Type.Integer({minimum:1,maximum:600000})}),
16
17
  execute:async(args,api,ctx)=>result(await service.wait(args.id,args.timeout_ms,ctx.abortSignal))}),
17
18
  defineTool({name:"background_stop",description:"Stop a session-owned command and reap its descendants. Returns after supervisor cleanup.",replay:"unsafe",