@tekmidian/pai 0.37.0 → 0.39.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.
Files changed (85) hide show
  1. package/dist/{auto-route-DM7GhJ8y.mjs → auto-route-BlWOWT4z.mjs} +2 -2
  2. package/dist/{auto-route-DM7GhJ8y.mjs.map → auto-route-BlWOWT4z.mjs.map} +1 -1
  3. package/dist/{providers-sXcK5bDZ.mjs → chain-CTtHligI.mjs} +1040 -782
  4. package/dist/chain-CTtHligI.mjs.map +1 -0
  5. package/dist/cli/index.mjs +4 -3
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/cli/program.mjs +4 -3
  8. package/dist/{clusters-Do4tEGyc.mjs → clusters-wlK0w41E.mjs} +1 -1
  9. package/dist/{clusters-Do4tEGyc.mjs.map → clusters-wlK0w41E.mjs.map} +1 -1
  10. package/dist/daemon/index.mjs +7 -7
  11. package/dist/{daemon-CGg1VCbA.mjs → daemon-CTkN_Vn8.mjs} +25 -25
  12. package/dist/{daemon-CGg1VCbA.mjs.map → daemon-CTkN_Vn8.mjs.map} +1 -1
  13. package/dist/{daemon-DsGGiIJM.mjs → daemon-CmHHmKpg.mjs} +7 -7
  14. package/dist/daemon-mcp/index.mjs +207 -34
  15. package/dist/daemon-mcp/index.mjs.map +1 -1
  16. package/dist/{detector-CMap-9vw.mjs → detector-Bwk_4Pk4.mjs} +1 -1
  17. package/dist/{detector-CMap-9vw.mjs.map → detector-Bwk_4Pk4.mjs.map} +1 -1
  18. package/dist/detector-DO730Zq0.mjs +5 -0
  19. package/dist/{factory-DD2T33C9.mjs → factory-A9x-T2Kg.mjs} +5 -5
  20. package/dist/{factory-DD2T33C9.mjs.map → factory-A9x-T2Kg.mjs.map} +1 -1
  21. package/dist/factory-BD-np0Vd.mjs +3 -0
  22. package/dist/hooks/route-agents-to-worker.mjs +112 -20
  23. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  24. package/dist/hooks/worker-proxy.mjs +110 -18
  25. package/dist/hooks/worker-proxy.mjs.map +2 -2
  26. package/dist/hooks/worker-status-line.mjs +176 -23
  27. package/dist/hooks/worker-status-line.mjs.map +4 -4
  28. package/dist/{indexer-backend-Cox9BCo-.mjs → indexer-backend-TG64CCQC.mjs} +1 -1
  29. package/dist/{indexer-backend-Cox9BCo-.mjs.map → indexer-backend-TG64CCQC.mjs.map} +1 -1
  30. package/dist/{latent-ideas-BC1oINZ-.mjs → latent-ideas-B7wq75Pt.mjs} +2 -2
  31. package/dist/{latent-ideas-BC1oINZ-.mjs.map → latent-ideas-B7wq75Pt.mjs.map} +1 -1
  32. package/dist/{link-boost-QFLrJwD6.mjs → link-boost-HkG7JWZR.mjs} +1 -1
  33. package/dist/{link-boost-QFLrJwD6.mjs.map → link-boost-HkG7JWZR.mjs.map} +1 -1
  34. package/dist/{neighborhood-D9MJ1c8f.mjs → neighborhood-lThN-MaQ.mjs} +1 -1
  35. package/dist/{neighborhood-D9MJ1c8f.mjs.map → neighborhood-lThN-MaQ.mjs.map} +1 -1
  36. package/dist/{note-context-d1wT_-GA.mjs → note-context-b6k0mAKi.mjs} +1 -1
  37. package/dist/{note-context-d1wT_-GA.mjs.map → note-context-b6k0mAKi.mjs.map} +1 -1
  38. package/dist/planner-BDI7bE9B.mjs +243 -0
  39. package/dist/planner-BDI7bE9B.mjs.map +1 -0
  40. package/dist/{postgres--BjPtLa0.mjs → postgres-mW1n7Vi1.mjs} +1 -1
  41. package/dist/{postgres--BjPtLa0.mjs.map → postgres-mW1n7Vi1.mjs.map} +1 -1
  42. package/dist/{program-CEIHn_Ma.mjs → program-DXBwJV7h.mjs} +274 -65
  43. package/dist/program-DXBwJV7h.mjs.map +1 -0
  44. package/dist/providers-FYkZjn_C.mjs +1405 -0
  45. package/dist/providers-FYkZjn_C.mjs.map +1 -0
  46. package/dist/{query-feedback-BUJxgw5B.mjs → query-feedback-BV4CcxqS.mjs} +1 -1
  47. package/dist/{query-feedback-BUJxgw5B.mjs.map → query-feedback-BV4CcxqS.mjs.map} +1 -1
  48. package/dist/query-feedback-DSVyHtrG.mjs +3 -0
  49. package/dist/router-Bk77E7hj.mjs +3 -0
  50. package/dist/{router-DK_sLsUL.mjs → router-DcHKnEPa.mjs} +1 -1
  51. package/dist/{router-DK_sLsUL.mjs.map → router-DcHKnEPa.mjs.map} +1 -1
  52. package/dist/skills/Worker/SKILL.md +34 -12
  53. package/dist/{sources-Bi7--33T.mjs → sources-kLnQsNrW.mjs} +1 -1
  54. package/dist/{sources-Bi7--33T.mjs.map → sources-kLnQsNrW.mjs.map} +1 -1
  55. package/dist/{sqlite-DtaL1glm.mjs → sqlite-BenGr3UP.mjs} +1 -1
  56. package/dist/{sqlite-DtaL1glm.mjs.map → sqlite-BenGr3UP.mjs.map} +1 -1
  57. package/dist/{state-BY2L6-vX.mjs → state-CAeyOdfq.mjs} +1 -1
  58. package/dist/{state-BY2L6-vX.mjs.map → state-CAeyOdfq.mjs.map} +1 -1
  59. package/dist/{state-8Hm9E4tW.mjs → state-Ca9F_sZc.mjs} +1 -1
  60. package/dist/{themes-BN0a2duq.mjs → themes-BI4GMRP9.mjs} +1 -1
  61. package/dist/{themes-BN0a2duq.mjs.map → themes-BI4GMRP9.mjs.map} +1 -1
  62. package/dist/{tools-CGPqpU3A.mjs → tools-Bp7hj6OW.mjs} +1 -1
  63. package/dist/{tools-y2bJpKom.mjs → tools-DGcB3o_S.mjs} +13 -13
  64. package/dist/{tools-y2bJpKom.mjs.map → tools-DGcB3o_S.mjs.map} +1 -1
  65. package/dist/{trace-h23JCcFD.mjs → trace-bobARFEX.mjs} +1 -1
  66. package/dist/{trace-h23JCcFD.mjs.map → trace-bobARFEX.mjs.map} +1 -1
  67. package/dist/{vault-indexer-DgsPjMgs.mjs → vault-indexer-C3OfXTNF.mjs} +1 -1
  68. package/dist/{vault-indexer-DgsPjMgs.mjs.map → vault-indexer-C3OfXTNF.mjs.map} +1 -1
  69. package/dist/{work-queue-worker-gsKd2LJa.mjs → work-queue-worker-DW8lz-Oo.mjs} +3 -3
  70. package/dist/{work-queue-worker-Dva_v_pI.mjs → work-queue-worker-R7UGIag_.mjs} +3 -3
  71. package/dist/{work-queue-worker-Dva_v_pI.mjs.map → work-queue-worker-R7UGIag_.mjs.map} +1 -1
  72. package/dist/{zettelkasten-vo7psPdT.mjs → zettelkasten-m5QPtb-L.mjs} +3 -3
  73. package/dist/{zettelkasten-vo7psPdT.mjs.map → zettelkasten-m5QPtb-L.mjs.map} +1 -1
  74. package/docs/commands/README.md +12 -7
  75. package/docs/commands/worker.md +110 -17
  76. package/docs/worker.md +244 -25
  77. package/package.json +1 -1
  78. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +2 -2
  79. package/dist/detector-DtLExmHN.mjs +0 -5
  80. package/dist/factory-BXzqRYVZ.mjs +0 -3
  81. package/dist/program-CEIHn_Ma.mjs.map +0 -1
  82. package/dist/providers-sXcK5bDZ.mjs.map +0 -1
  83. package/dist/query-feedback-DhyLOe5S.mjs +0 -3
  84. package/dist/router-1zi8jiNF.mjs +0 -3
  85. /package/dist/{main-resolver-IhZo4pI0.mjs → main-resolver-D6IImXvF.mjs} +0 -0
@@ -24,6 +24,10 @@ pai worker <subcommand> [options]
24
24
  | [`pai worker pane [id]`](#pai-worker-pane-id) | Open the follow pane for a worker (or one shared pane for this session) |
25
25
  | [`pai worker log [what]`](#pai-worker-log-what) | all = ledger, tail = last ledger lines, <id> = raw event stream, none = list |
26
26
  | [`pai worker say <id> <text>`](#pai-worker-say-id-text) | Send one message to a running worker (forwarded to its open stdin) |
27
+ | [`pai worker handoff <json>`](#pai-worker-handoff-json) | From inside a worker: append a handoff to the parent's inbox and (when it runs) say it to the parent. |
28
+ | [`pai worker merge <id>`](#pai-worker-merge-id) | Merge a worker's worktree branch (worker/<id>) into the original checkout, then remove the worktree |
29
+ | [`pai worker discard <id>`](#pai-worker-discard-id) | Drop a worker's worktree and branch, keeping nothing |
30
+ | [`pai worker controls <id> <who>`](#pai-worker-controls-id-who) | Hand the desktop controls (clickr) to a worker or take them back. |
27
31
  | [`pai worker resume <id> <text>`](#pai-worker-resume-id-text) | Continue a finished worker on the same provider: claude --resume <session> |
28
32
  | [`pai worker proxy [stop]`](#pai-worker-proxy-stop) | The local Anthropic↔OpenAI proxy (loopback only); started on demand by `run`, |
29
33
  | [`pai worker mcp [list]`](#pai-worker-mcp-list) | MCP servers workers may load via --mcp / roles, and the configured sets |
@@ -32,7 +36,7 @@ pai worker <subcommand> [options]
32
36
  | [`pai worker off`](#pai-worker-off) | Stop routing: Agent tool runs on Anthropic again |
33
37
  | [`pai worker install`](#pai-worker-install) | Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup |
34
38
  | [`pai worker providers`](#pai-worker-providers) | Providers: list (default), add, remove, use, enable, disable, test |
35
- | [`pai worker roles`](#pai-worker-roles) | Roles: which provider serves implement / research / spotcheck |
39
+ | [`pai worker classes`](#pai-worker-classes) | Classes: which provider serves draft / implement / review / |
36
40
 
37
41
  ### pai worker run [args...]
38
42
 
@@ -40,6 +44,8 @@ Run one claude-code worker through the configured provider.
40
44
 
41
45
  Unknown options are passed to claude verbatim (e.g. -p, --allowedTools);
42
46
  --output-format/--verbose are handled here.
47
+ --chain draft,implement[,review] runs a spec-first pipeline;
48
+ --agent <name> runs an agent definition from ~/.claude/agents.
43
49
 
44
50
  **Arguments**
45
51
 
@@ -52,11 +58,16 @@ Unknown options are passed to claude verbatim (e.g. -p, --allowedTools);
52
58
  | Option | Description | Default |
53
59
  |--------|-------------|---------|
54
60
  | `--provider <name>` | Provider to run on (default: active, else routing order) | |
55
- | `--role <role>` | Use the provider of this role (implement, research, spotcheck, ) | |
61
+ | `--class <name>` | Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image) | |
62
+ | `--role <name>` | Alias of --class (roles were renamed to classes) | |
63
+ | `--chain <stages>` | Comma-separated stage classes, e.g. draft,implement or draft,implement,review | |
64
+ | `--agent <name>` | Run the agent definition ~/.claude/agents/<name>.md on a worker | |
56
65
  | `--model <model>` | Override the provider's model for this run | |
57
66
  | `--label <text>` | Short task label shown in ps / follow / status line | |
58
67
  | `--mcp <names>` | MCP servers/sets this worker may use (comma-separated; see `pai worker mcp`) | |
59
68
  | `--no-pane` | Do not open a follow pane for this worker | |
69
+ | `--worktree` | Run in a git worktree on branch worker/<id> (default for implement/complex/plan in a git repo) | |
70
+ | `--no-worktree` | Run in place, no worktree | |
60
71
 
61
72
 
62
73
  ### pai worker ps
@@ -124,7 +135,7 @@ Open the follow pane for a worker (or one shared pane for this session)
124
135
 
125
136
  | Option | Description | Default |
126
137
  |--------|-------------|---------|
127
- | `--check` | Only report whether the pane is open, plus the profile file's path and font | |
138
+ | `--check` | Only report whether the pane is open, plus the profile file's path, font, and the hosting window's bounds | |
128
139
 
129
140
 
130
141
  ### pai worker log [what]
@@ -150,6 +161,56 @@ Send one message to a running worker (forwarded to its open stdin)
150
161
  | `<text>` | required |
151
162
 
152
163
 
164
+ ### pai worker handoff <json>
165
+
166
+ From inside a worker: append a handoff to the parent's inbox and (when it runs) say it to the parent.
167
+
168
+ Payload: {"kind":"proposal|question|blocker","text":"…","data":{…}} — from/to come from the environment.
169
+
170
+ **Arguments**
171
+
172
+ | Argument | Kind |
173
+ |----------|------|
174
+ | `<json>` | required |
175
+
176
+
177
+ ### pai worker merge <id>
178
+
179
+ Merge a worker's worktree branch (worker/<id>) into the original checkout, then remove the worktree
180
+
181
+ **Arguments**
182
+
183
+ | Argument | Kind |
184
+ |----------|------|
185
+ | `<id>` | required |
186
+
187
+
188
+ ### pai worker discard <id>
189
+
190
+ Drop a worker's worktree and branch, keeping nothing
191
+
192
+ **Arguments**
193
+
194
+ | Argument | Kind |
195
+ |----------|------|
196
+ | `<id>` | required |
197
+
198
+
199
+ ### pai worker controls <id> <who>
200
+
201
+ Hand the desktop controls (clickr) to a worker or take them back.
202
+
203
+ <who> is `you` (the worker may actuate) or `me` (the operator keeps them);
204
+ inside a worker's pane, typing "your controls" does the same.
205
+
206
+ **Arguments**
207
+
208
+ | Argument | Kind |
209
+ |----------|------|
210
+ | `<id>` | required |
211
+ | `<who>` | required |
212
+
213
+
153
214
  ### pai worker resume <id> <text>
154
215
 
155
216
  Continue a finished worker on the same provider: claude --resume <session>
@@ -235,7 +296,7 @@ Providers: list (default), add, remove, use, enable, disable, test
235
296
 
236
297
  ### pai worker providers add <name>
237
298
 
238
- Add a provider; the first one also turns workers on and seeds roles.
299
+ Add a provider; the first one also turns workers on and seeds classes.
239
300
 
240
301
  Example: pai worker providers add glm --base-url https://…/anthropic \
241
302
  --key-file ~/.config/zai/api_key --model glm-5.3 --fast-model glm-5.3-flash
@@ -263,11 +324,31 @@ Codex (ChatGPT plan): --engine codex — runs through the Codex CLI.
263
324
  | `--engine <engine>` | claude (default) or codex — codex runs `codex exec --json` | |
264
325
  | `--context-window <tokens>` | Context window for the meter (default 200000; init event overrides) | |
265
326
  | `--quota-probe <url>` | URL whose JSON first number is the quota percent (0-100) | |
327
+ | `--cost-tier <1-5>` | Cost tier 1 (cheapest) … 5 (most expensive; default 3) | |
328
+ | `--tags <tags>` | Capability tags, comma-separated (from: code, vision, image-gen, long-context, fast, reasoning) | `` |
329
+
330
+
331
+ ### pai worker providers update <name>
332
+
333
+ Change cost tier and tags of a provider (routing constraints use these)
334
+
335
+ **Arguments**
336
+
337
+ | Argument | Kind |
338
+ |----------|------|
339
+ | `<name>` | required |
340
+
341
+ **Options**
342
+
343
+ | Option | Description | Default |
344
+ |--------|-------------|---------|
345
+ | `--cost-tier <1-5>` | Cost tier 1 (cheapest) … 5 (most expensive) | |
346
+ | `--tags <tags>` | Capability tags, comma-separated (from: code, vision, image-gen, long-context, fast, reasoning); --tags '' clears | `` |
266
347
 
267
348
 
268
349
  ### pai worker providers remove <name>
269
350
 
270
- Remove a provider and any roles pointing at it
351
+ Remove a provider and any classes pointing at it
271
352
 
272
353
  **Arguments**
273
354
 
@@ -278,7 +359,7 @@ Remove a provider and any roles pointing at it
278
359
 
279
360
  ### pai worker providers use <name>
280
361
 
281
- Make this provider the active one for runs without --provider/--role
362
+ Make this provider the active one for runs without --provider/--class
282
363
 
283
364
  **Arguments**
284
365
 
@@ -320,37 +401,49 @@ One-word pong probe through a provider (default: the active one)
320
401
  | `[name]` | optional |
321
402
 
322
403
 
323
- ### pai worker roles
404
+ ### pai worker classes
405
+
406
+ Classes: which provider serves draft / implement / review / …
324
407
 
325
- Roles: which provider serves implement / research / spotcheck
326
408
 
409
+ ### pai worker classes list
327
410
 
328
- ### pai worker roles list
411
+ List classes and their targets (default action)
329
412
 
330
- List roles and their providers (default action)
331
413
 
414
+ ### pai worker classes set <class> [target]
332
415
 
333
- ### pai worker roles set <role> <provider[/alias]>
416
+ Point a class at a provider (or provider/fast), or give only constraints:
334
417
 
335
- Point a role at a provider, optionally its fast model (e.g. glm/fast)
418
+ classes set research --max-cost-tier 2 --require-tags long-context,reasoning
336
419
 
337
420
  **Arguments**
338
421
 
339
422
  | Argument | Kind |
340
423
  |----------|------|
341
- | `<role>` | required |
342
- | `<provider[/alias]>` | required |
424
+ | `<class>` | required |
425
+ | `[target]` | optional |
426
+
427
+ **Options**
428
+
429
+ | Option | Description | Default |
430
+ |--------|-------------|---------|
431
+ | `--provider <name>` | Pin the class to this provider (object form) | |
432
+ | `--mcp <names>` | MCP servers/sets for runs of this class (comma-separated) | |
433
+ | `--max-cost-tier <1-5>` | Auto-routing considers only providers up to this cost tier | |
434
+ | `--require-tags <tags>` | Auto-routing needs these tags (comma-separated) | |
435
+ | `--order <providers>` | Per-class routing order overriding workers.routing.order (comma-separated) | |
343
436
 
344
437
 
345
- ### pai worker roles unset <role>
438
+ ### pai worker classes unset <class>
346
439
 
347
- Remove a role (runs then use the active provider)
440
+ Remove a class (runs then use the active provider)
348
441
 
349
442
  **Arguments**
350
443
 
351
444
  | Argument | Kind |
352
445
  |----------|------|
353
- | `<role>` | required |
446
+ | `<class>` | required |
354
447
 
355
448
 
356
449
  ## See also
package/docs/worker.md CHANGED
@@ -32,22 +32,26 @@ main session (Anthropic) workers (configured provider)
32
32
  "keyFile": "~/.config/zai/api_key",
33
33
  "models": { "default": "glm-5.3", "fast": "glm-5.3-flash" },
34
34
  "env": { "API_TIMEOUT_MS": "3000000" },
35
- "contextWindow": 200000
35
+ "contextWindow": 200000,
36
+ "costTier": 2,
37
+ "tags": ["code", "long-context"]
36
38
  },
37
39
  "oai": {
38
40
  "protocol": "openai",
39
41
  "upstreamUrl": "https://api.openai.com/v1",
40
42
  "keyFile": "~/.config/pai/keys/oai",
41
- "models": { "default": "gpt-5.2", "fast": "gpt-5.2-mini" }
43
+ "models": { "default": "gpt-5.2", "fast": "gpt-5.2-mini" },
44
+ "costTier": 4,
45
+ "tags": ["reasoning", "vision"]
42
46
  },
43
47
  "codexprov": {
44
48
  "engine": "codex",
45
49
  "models": { "default": "gpt-5.2-codex" }
46
50
  }
47
51
  },
48
- "roles": {
52
+ "classes": {
49
53
  "implement": "glm",
50
- "research": "glm",
54
+ "research": { "maxCostTier": 2, "requireTags": ["long-context"] },
51
55
  "spotcheck": "glm/fast",
52
56
  "docs": { "provider": "glm", "mcp": ["office"] }
53
57
  },
@@ -70,8 +74,12 @@ main session (Anthropic) workers (configured provider)
70
74
  Code (see below).
71
75
  - `contextWindow` overrides the context-meter window when the endpoint's
72
76
  init event does not announce one (default 200 000).
73
- - A role target is `"provider[/model]"` or an object with `provider` and a
74
- `mcp` allowlist applied on top of `--mcp`.
77
+ - `costTier` (1 cheapest 5 most expensive, default 3) and `tags` (from:
78
+ `code`, `vision`, `image-gen`, `long-context`, `fast`, `reasoning`) describe
79
+ a provider; classes use them to constrain routing (next section).
80
+ - A class target is `"provider[/model]"` or an object with `provider` and a
81
+ `mcp` allowlist applied on top of `--mcp`, or an object with only routing
82
+ constraints (`maxCostTier`, `requireTags`, `order`).
75
83
 
76
84
  Or add one from the CLI:
77
85
 
@@ -80,19 +88,64 @@ pai worker providers add glm \
80
88
  --base-url https://api.z.ai/api/anthropic \
81
89
  --key-file ~/.config/zai/api_key \
82
90
  --model glm-5.3 --fast-model glm-5.3-flash \
83
- --env API_TIMEOUT_MS=3000000
91
+ --env API_TIMEOUT_MS=3000000 \
92
+ --cost-tier 2 --tags code,long-context
84
93
  pai worker providers add oai \
85
94
  --upstream-url https://api.openai.com/v1 \
86
95
  --key-file ~/.config/pai/keys/oai --model gpt-5.2
96
+ pai worker providers update glm --cost-tier 1 # tiers/tags change later
87
97
  ```
88
98
 
89
99
  The first provider also sets `enabled: true`, makes itself active and seeds
90
- the three roles. Then:
100
+ the nine classes. Then:
91
101
 
92
102
  ```
93
103
  pai worker install # Agent hook in settings.json + glm* shims + cleanup
94
104
  ```
95
105
 
106
+ ## Task classes
107
+
108
+ Roles were renamed to **classes** — task classes pick the provider for a kind
109
+ of work. The nine standard classes: `draft`, `plan`, `implement`, `review`,
110
+ `research`, `spotcheck`, `simple`, `complex`, `image` (any other name can be
111
+ defined too). Configs with the old `roles` key keep parsing; the key migrates
112
+ to `classes` on the first write.
113
+
114
+ ```
115
+ pai worker classes # list
116
+ pai worker classes set implement glm # pin a provider
117
+ pai worker classes set spotcheck glm/fast # …its fast model
118
+ pai worker classes set research --max-cost-tier 2 --require-tags long-context
119
+ pai worker classes unset research
120
+ ```
121
+
122
+ Every provider carries a **cost tier** (1 cheapest … 5 most expensive,
123
+ default 3) and **tags** (`code`, `vision`, `image-gen`, `long-context`,
124
+ `fast`, `reasoning`). A class resolves its provider as:
125
+
126
+ 1. `--provider` (explicit flag) wins;
127
+ 2. else the class mapping when it pins a provider;
128
+ 3. else auto-routing (see below) restricted to providers within the class's
129
+ `maxCostTier` and carrying all its `requireTags`;
130
+ 4. nothing qualifies → the run fails with a message listing why every
131
+ provider was excluded.
132
+
133
+ `classes.<name>.order` overrides `routing.order` for that class. Cooldown and
134
+ quota logic is unchanged.
135
+
136
+ ### Preferences from chat
137
+
138
+ The Worker skill maps phrases onto the MCP tools — the answer is one or two
139
+ lines, and the user is never told to edit a file:
140
+
141
+ - "use X for image generation" / "route research to kimi" →
142
+ `worker_classes set <class>=<provider>`
143
+ - "prefer the flash model for simple tasks" / "cheap only for drafts" →
144
+ `worker_classes set simple|draft=<provider>/fast` (or `max_cost_tier`)
145
+ - "reviews should use a reasoning model" → `worker_classes set review` with
146
+ `require_tags: ["reasoning"]`
147
+ - "what handles reviews" / "show the routing table" → `worker_classes list`
148
+
96
149
  ## The proxy (OpenAI-protocol providers)
97
150
 
98
151
  A provider with `protocol: "openai"` cannot be talked to by Claude Code
@@ -135,6 +188,8 @@ so it is unavailable for codex workers (their thread id is kept, but
135
188
  pai worker run --label "fix black buttons" -p '<task spec>' \
136
189
  --allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json \
137
190
  --mcp office
191
+ pai worker run --chain draft,implement -p '<brief>' # spec-first (below)
192
+ pai worker run --agent engineer -p '<task>' # agent library (below)
138
193
  pai worker ps # this session's workers
139
194
  pai worker follow [id] # live transcript (type to talk to it)
140
195
  pai worker replay <id> # transcript of one worker
@@ -145,10 +200,46 @@ pai worker proxy [--port N|stop] # the translating proxy, by hand
145
200
  pai worker log [all|tail|<id>] # raw streams + routing ledger
146
201
  ```
147
202
 
148
- Roles pick the provider for a task class: `--role implement|research|spotcheck`.
149
- `--no-pane` suppresses the iTerm follow pane; `--provider <name>` bypasses
150
- roles entirely. If you bring your own `--append-system-prompt`, the worker
151
- contract below is added alongside it, not instead.
203
+ Classes pick the provider for a task class: `--class implement|research|spotcheck|…`
204
+ (`--role` still works as its alias). `--no-pane` suppresses the iTerm follow
205
+ pane; `--provider <name>` bypasses classes entirely. If you bring your own
206
+ `--append-system-prompt`, the worker contract below is added alongside it, not
207
+ instead.
208
+
209
+ ## Chains (draft → implement → review)
210
+
211
+ ```
212
+ pai worker run --chain draft,implement -p '<brief>'
213
+ pai worker run --chain draft,implement,review -p '<brief>' # + review pass
214
+ ```
215
+
216
+ - The **draft** class turns the brief into a full spec file under
217
+ `<logDir>/specs/<chain id>.md` — goal, constraints, files likely touched,
218
+ acceptance checks, verification commands. It reads the repository first and
219
+ implements nothing.
220
+ - **implement** (or any other stage class) runs with that spec as its prompt
221
+ and the original brief attached.
222
+ - **review** reads the spec and the working-tree diff and produces the
223
+ structured report.
224
+ - Each stage is its own worker: own id, own pane, `parent` set to the chain
225
+ id — `ps` shows the chain as a tree.
226
+ - A stage that fails stops the chain (the exit code is the first failing
227
+ stage's); a draft that produces no spec stops it with a message telling the
228
+ caller to write the spec and re-run without the draft stage.
229
+ - `--class` alongside `--chain` overrides the class of every stage; `--label`
230
+ names the chain (stages render as `<label> · <stage>`).
231
+
232
+ ## Agent definitions as workers
233
+
234
+ `pai worker run --agent <name>` loads `~/.claude/agents/<name>.md` and runs it
235
+ on a worker: the front matter's `model` maps to a class (haiku→simple,
236
+ sonnet→implement, opus→complex — `--class` overrides), `tools` becomes
237
+ `--allowedTools`, and the body is passed via `--append-system-prompt` (your
238
+ own flags on the command line still win). The label defaults to
239
+ `<agent>: <first 50 chars of prompt>`.
240
+
241
+ The agent library therefore runs on workers, not on the orchestrator's
242
+ Anthropic account — same hooks, same classes, same `ps`/`follow`/`replay`.
152
243
 
153
244
  The old habits keep working: `glm`, `glm-run`, `glm-ps`, `glm-log` are shims
154
245
  to the pai commands (`pai worker install` moves any previous versions to
@@ -156,13 +247,19 @@ to the pai commands (`pai worker install` moves any previous versions to
156
247
 
157
248
  ## Routing
158
249
 
159
- A run resolves its provider as: `--provider` > `--role` > `active`.
250
+ A run resolves its provider as: `--provider` > `--class` > `active`.
160
251
 
161
- With `active: "auto"`, providers are tried in `routing.order`, skipping:
252
+ With `active: "auto"`, providers are tried in `routing.order` (or the class's
253
+ own `order`), skipping:
162
254
 
163
255
  - disabled providers,
164
256
  - providers in a cooldown (set for `cooldownMinutes` after a quota failure),
165
- - providers whose `quotaProbe` URL reports ≥ `quotaSkipAt` (default 95).
257
+ - providers whose `quotaProbe` URL reports ≥ `quotaSkipAt` (default 95),
258
+ - providers above the class's `maxCostTier` or missing one of its
259
+ `requireTags`.
260
+
261
+ Nothing qualifying fails the run with the exclusion reason of every provider
262
+ in the order.
166
263
 
167
264
  A quota failure before the first tool call is re-run on the next provider and
168
265
  logged as `WORKER-REROUTE`. `pai worker providers enable <name>` clears a
@@ -213,9 +310,116 @@ stdin closes two seconds later unless another message arrives — after that
213
310
 
214
311
  `pai worker resume <id> "<text>"` continues the same Claude session (the id
215
312
  recorded from the init event) on the same provider, labelled `↩ <original>`,
216
- and prints a fresh worker id with `--print-id`. A pane running `follow` reads
217
- its own stdin the same way: type to say while it runs, or to resume after it
218
- finished.
313
+ and prints a fresh worker id with `--print-id`.
314
+
315
+ A `follow <id>` pane on a TTY is a chat, not a tail: the transcript lives in
316
+ a scroll region that ends two rows above the pane's bottom, the last two rows
317
+ are fixed — the prompt row (`› `, full readline editing: arrows, backspace,
318
+ Ctrl-A/E, Ctrl-U) and the ticker row — and every transcript line is inserted
319
+ above them with a save-cursor / restore-cursor write, so the cursor never
320
+ leaves the prompt. Enter sends the line: said to the worker while it runs,
321
+ `resume`d into the same session once it has finished (the pane follows the
322
+ fresh run id and keeps the chat). The sent line is echoed into the transcript
323
+ as a `»` row with its time gutter, exactly once — the mirrored `operator`
324
+ event is swallowed. `/help` lists the commands:
325
+
326
+ | key | action |
327
+ | --- | --- |
328
+ | `/quit` | close the pane |
329
+ | `/resume <text>` | resume the finished worker with `<text>` |
330
+ | `/status` | one-line worker status |
331
+ | anything else | a message — said, or resumed |
332
+
333
+ Ctrl-C on an empty prompt leaves the pane, on a draft it clears the prompt;
334
+ Ctrl-D leaves. The auto-exit countdown never fires while the prompt holds
335
+ unsent text. Without a TTY (piped output) the pane keeps the plain scrolling
336
+ behaviour — no prompt row, no ticker, stdin still the operator channel.
337
+
338
+ ### Sub-workers and handoffs
339
+
340
+ Any worker may start its own workers: the runner exports `PAI_WORKER_ID` in
341
+ every worker's environment, and a `pai worker run` launched from inside one
342
+ records `parent` in its status — so the forest is visible in `ps` (children
343
+ indented under their parent, `├`/`└` connectors), the status line (`↳` under
344
+ the parent) and each child gets its own follow pane. Handoffs travel **up
345
+ only**, from a child to its parent:
346
+
347
+ ```
348
+ pai worker handoff '{"kind":"proposal","text":"run this on a cheap provider","data":{…}}'
349
+ ```
350
+
351
+ (or the MCP tool `worker_handoff`; kinds `proposal`, `question`, `blocker` —
352
+ `result` is sent automatically when a child finishes). The handoff is appended
353
+ to `<logDir>/<parent>.inbox.jsonl` (durable, ordered) and, when the parent is
354
+ running, also delivered as an operator message `[handoff from <child id>]`. The
355
+ parent sees it in its pane (`◆ from <id> · kind: text`, magenta), `ps` and the
356
+ status line show `◆N` for an inbox with N handoffs, and `replay`/`follow`
357
+ merge them into the transcript by timestamp. There is no sideways channel:
358
+ siblings never see each other, everything goes up.
359
+
360
+ Two caps keep the tree bounded (`workers.tree`):
361
+
362
+ - `maxDepth` (default 2) — how deep sub-workers may nest; a launch one level
363
+ past the cap fails with a message that suggests a handoff instead,
364
+ - `maxChildren` (default 4) — how many children of one parent may run at the
365
+ same time (finished children do not count).
366
+
367
+ Chain stages and planner sub-tasks carry a parent too, but a parent without a
368
+ status file (a chain id) is not a worker and is never capped by depth.
369
+
370
+ ### Worktrees and merge
371
+
372
+ A run whose class writes files (`implement`, `complex`, `plan`) in a git repo,
373
+ with a prompt that is not read-only, gets **its own git worktree** by default:
374
+ `<logDir>/worktrees/<id>` on branch `worker/<id>` from the current HEAD. The
375
+ worker commits its work on that branch (the no-commit rule applies to the main
376
+ branch only — the appended system prompt says so); when git refuses (no
377
+ commits yet, detached setup) the run degrades to in place with a note on
378
+ stderr and in the ledger.
379
+
380
+ ```
381
+ pai worker merge <id> # git merge --no-ff worker/<id> + remove the worktree
382
+ pai worker discard <id> # remove worktree and branch, keep nothing
383
+ ```
384
+
385
+ `ps` marks a worker with an unmerged branch `⎇<commits>` (yellow); the status
386
+ file records `branch`, `commits` and `worktreeDir`. Chains give a worktree to
387
+ the implement stage only; `--worktree` forces one on, `--no-worktree` opts
388
+ out.
389
+
390
+ ### The planner class
391
+
392
+ `--class plan` runs a small orchestration, not one worker:
393
+
394
+ 1. a planner worker reads the repository and writes
395
+ `<logDir>/plans/<planner id>.json` — sub-tasks (`title`, `brief`, `class`,
396
+ `files`, `acceptance`), 5–50 of them, fewer only when the goal names a
397
+ smaller count;
398
+ 2. the runner validates the plan and spawns the sub-tasks as children of the
399
+ planner, at most `workers.tree.maxChildren` at a time;
400
+ 3. each child's structured report arrives in the planner's inbox as a
401
+ `kind: "result"` handoff;
402
+ 4. the run finishes with a summary report (`n/m sub-tasks ok`) and the
403
+ `pai worker merge` lines for any unmerged branches.
404
+
405
+ The planner's prompt carries the prompt rules that make plans executable:
406
+ domain-specific instructions only (real files, real commands), constraints
407
+ over step lists, explicit quantity ranges, no checkbox style.
408
+
409
+ ### Clickr controls (desktop set)
410
+
411
+ The default `mcpSets` ship one set: `desktop = ["clickr"]`. A worker launched
412
+ `--mcp desktop` receives the clickr MCP server — screen control for GUI work
413
+ — and follows the same control handover as a session:
414
+
415
+ ```
416
+ pai worker controls <id> you # hand control of the desktop to the worker
417
+ pai worker controls <id> me # take it back
418
+ ```
419
+
420
+ `controls` runs the `clickr controls you|me` CLI and the worker's screenshot
421
+ and input tools honour it. Control starts with the operator: a worker cannot
422
+ drive the desktop until it is handed over.
219
423
 
220
424
  ### Context meter
221
425
 
@@ -239,7 +443,7 @@ pai worker run --mcp office … # a set, or names: --mcp memory,github
239
443
  `--mcp` takes server names and/or `mcpSets` names (comma-separated,
240
444
  repeatable); the filtered config is written from `~/.claude.json`'s
241
445
  `mcpServers` to `<logDir>/<id>.mcp.json` and passed with
242
- `--strict-mcp-config --mcp-config`. Role targets may add `"mcp": ["office"]`
446
+ `--strict-mcp-config --mcp-config`. Class targets may add `"mcp": ["office"]`
243
447
  on top. An unknown name fails fast, listing what exists;
244
448
  `pai worker mcp list` shows servers and sets. A caller-provided
245
449
  `--mcp-config` always wins; MCP is chosen at launch, not mid-run.
@@ -263,6 +467,17 @@ separator when the day changes) and, on a TTY, a liveness line
263
467
  erased before the next event. `replay` shows a finished transcript with the
264
468
  same gutter.
265
469
 
470
+ The pane wraps rows itself at the terminal width (re-read on resize, so a
471
+ narrower pane re-wraps what arrives after the resize): breaks on whitespace
472
+ where it can, hard-wraps a long token otherwise, never splits an ANSI escape
473
+ (it measures printable columns, not string length), and carries diff colours
474
+ onto every continuation row. Each continuation row carries a blank-time
475
+ gutter with the `│` bar kept — the bar runs unbroken down the pane and no
476
+ content ever lands left of it. In the chat layout the transcript scrolls
477
+ inside an ANSI scroll region (`ESC[1;rows-2r`, reset on exit and re-set on
478
+ resize) so the prompt and ticker rows stay fixed; piped output keeps the
479
+ terminal's own wrapping instead.
480
+
266
481
  The pane command is `exec pai worker follow …` so the pane holds exactly one
267
482
  process — signals reach the follow directly, and when the worker finishes
268
483
  the pane counts down its `auto-exit` (default 60 s, `pane.autoExitSecs`).
@@ -288,12 +503,16 @@ background instead. Decisions are ledgered (`DENIED-ANTHROPIC-AGENT`,
288
503
 
289
504
  ## MCP tools
290
505
 
291
- `worker_status`, `worker_providers` (list/add/remove/use/enable/disable/test),
292
- `worker_roles`, `worker_toggle`, `worker_ps`, `worker_replay`,
293
- `worker_say` (message a running worker), `worker_resume` (continue a
294
- finished one) the same library the CLI calls. `worker_providers add`
295
- accepts a raw `key`, parks it in `~/.config/pai/keys/<name>` (mode 0600) and
296
- stores only the path.
506
+ `worker_status`, `worker_providers`
507
+ (list/add/update/remove/use/enable/disable/test `update` changes
508
+ `cost_tier`/`tags`), `worker_classes` (list/set/unset), `worker_run` (start a
509
+ worker or chain from chat, returns the id immediately), `worker_toggle`,
510
+ `worker_ps`, `worker_replay`, `worker_say` (message a running worker),
511
+ `worker_resume` (continue a finished one), `worker_handoff` (from inside a
512
+ worker: send a proposal/question/blocker up to its parent) — the same library
513
+ the CLI calls.
514
+ `worker_providers add` accepts a raw `key`, parks it in
515
+ `~/.config/pai/keys/<name>` (mode 0600) and stores only the path.
297
516
 
298
517
  ## Status line
299
518
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekmidian/pai",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "PAI Knowledge OS — Personal AI Infrastructure with federated memory and project management",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -112,10 +112,10 @@ async function main(): Promise<void> {
112
112
  deny(
113
113
  "Agent tool is disabled: subagents run on the configured worker provider, not Anthropic. " +
114
114
  "Delegate with Bash instead, in the background:\n\n" +
115
- `pai worker run --label "${label || "task"}" --role research -p '<full, self-contained task spec>' ` +
115
+ `pai worker run --label "${label || "task"}" --class research -p '<full, self-contained task spec>' ` +
116
116
  "--allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json\n\n" +
117
117
  "- Run it with run_in_background: true and always with a timeout.\n" +
118
- "- Use --role spotcheck (or implement) as the task demands.\n" +
118
+ "- Use --class spotcheck (or implement) as the task demands.\n" +
119
119
  "- Web research: add WebSearch,WebFetch to --allowedTools.\n" +
120
120
  "- The answer is in the `result` field of the JSON it prints. Review the diff yourself.\n" +
121
121
  "- pai worker ps lists running workers; pai worker follow <id> shows one live.\n" +
@@ -1,5 +0,0 @@
1
- import "./embeddings-DOLZnT1X.mjs";
2
- import "./search-Rpk1cSBC.mjs";
3
- import { t as detectTopicShift } from "./detector-CMap-9vw.mjs";
4
-
5
- export { detectTopicShift };
@@ -1,3 +0,0 @@
1
- import { t as createStorageBackend } from "./factory-DD2T33C9.mjs";
2
-
3
- export { createStorageBackend };