pi-orchestrator-config 4.21.0 → 4.22.1

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 (45) hide show
  1. package/README.md +109 -37
  2. package/extensions/orchestrator/async-agents.ts +2 -3
  3. package/extensions/orchestrator/index.ts +2 -2
  4. package/extensions/orchestrator/project-settings.ts +6 -6
  5. package/extensions/orchestrator/review-learning.ts +4 -4
  6. package/extensions/orchestrator/rules.ts +11 -7
  7. package/extensions/orchestrator/status-bar.ts +2 -2
  8. package/extensions/orchestrator/subagent-tool.ts +2 -3
  9. package/extensions/orchestrator/trail.ts +745 -0
  10. package/extensions/orchestrator/utils.ts +44 -1
  11. package/extensions/pidash/discord-answers.ts +8 -0
  12. package/extensions/pidash/discord-attachments.ts +8 -0
  13. package/extensions/pidash/discord-board.ts +75 -0
  14. package/extensions/pidash/discord-bot.ts +1811 -0
  15. package/extensions/pidash/discord-commands.ts +90 -0
  16. package/extensions/pidash/discord-config.ts +93 -0
  17. package/extensions/pidash/discord-format.ts +24 -0
  18. package/extensions/pidash/discord-jobs-view.ts +74 -0
  19. package/extensions/pidash/discord-reactions.ts +76 -0
  20. package/extensions/pidash/discord-render.ts +8 -0
  21. package/extensions/pidash/discord-sessions-view.ts +8 -0
  22. package/extensions/pidash/discord-state.ts +8 -0
  23. package/extensions/pidash/pidash-ui/dist/assets/{index-DzT6_qJc.js → index-B7e6Zyb5.js} +1 -1
  24. package/extensions/pidash/pidash-ui/dist/index.html +1 -1
  25. package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +3 -3
  26. package/extensions/pidash/pidash.ts +41 -13
  27. package/extensions/shared/chat/chat-attachments.ts +192 -0
  28. package/extensions/shared/chat/chat-bridge.ts +420 -0
  29. package/extensions/shared/chat/chat-events.ts +124 -0
  30. package/extensions/shared/chat/chat-format.ts +136 -0
  31. package/extensions/shared/chat/chat-picker.ts +128 -0
  32. package/extensions/shared/chat/chat-prompts.ts +158 -0
  33. package/extensions/shared/chat/chat-sessions.ts +187 -0
  34. package/extensions/shared/chat/chat-state.ts +192 -0
  35. package/extensions/shared/chat/chat-types.ts +86 -0
  36. package/extensions/shared/create-runtime-provider.ts +1 -1
  37. package/extensions/shared/types.ts +7 -2
  38. package/package.json +6 -6
  39. package/prompts/release.md +3 -1
  40. package/scripts/pidash-discord.ts +6 -588
  41. package/scripts/pidash-server.ts +1 -0
  42. package/scripts/pidash-session-state.ts +38 -3
  43. package/scripts/{graft-allow-scripts.mjs → trail-allow-scripts.mjs} +2 -2
  44. package/settings-keys.json +8 -8
  45. package/extensions/orchestrator/graft.ts +0 -356
package/README.md CHANGED
@@ -26,7 +26,7 @@ Single extension that provides:
26
26
  | **Git status** | Live git status in status line with colored icons — updates after every tool call. Shows clickable `#N` when the branch has an open PR. Last-activity clock `⏱ HH:MM (Xm/Xh ago)` shows time since last response |
27
27
  | **Desktop notifications** | Notifies via `notify-send` on task completion, waiting for input, and action required |
28
28
  | **File preview** | Serves generated HTML/frontend files via HTTP for browser preview from container |
29
- | **Graft integration** | When `graft_enable=true`, main agents and subagents query the local Graft graph before raw project navigation on substantive prompts. Children only consume graphs. Stale graphs remain usable, retrieval failures fall back to raw tools, and main processes coordinate rebuilds with a cross-process lock. |
29
+ | **Trail-code integration** | On by default when the `trail` binary is installed. Main agents and subagents query the local code map before raw project navigation: the first project navigation per turn is gated behind a `trail_*` call, guidance is map-first (`trail_find_code` first, `trail_repo_map` once per session), and injected hits carry source. Worktrees resolve the map under `graft/` or `.trail/cache/`, inherit enablement from the main checkout, and seed from it instead of cold-building. Stale maps stay usable and failures fall open. |
30
30
  | **Pidash dashboard** | Live web dashboard — multi-session monitoring, browser messaging, model switching, live session name updates, reasoning token display |
31
31
  | **Pidiff viewer** | Per-project diff viewer with review comments — branch diffs, file tree, inline comments, git-based ignore rules |
32
32
  | **Dreaming** | Background memory consolidation — extracts memories from sessions, deduplicates, maintains topic-based memory |
@@ -179,7 +179,7 @@ The installer covers:
179
179
 
180
180
  - **Pi Packages** — pi-config, pi-vertex-claude, pi-web-access, myk-pi-tools, bun
181
181
  - **Python Tools** — prek
182
- - **npm Packages** — acpx, agent-browser, graft
182
+ - **npm Packages** — acpx, agent-browser, trail-code (`@trailhq/trail-code`)
183
183
  - **Browser Automation** — playwright + chromium
184
184
  - **Environment Setup** — gitignore configuration
185
185
 
@@ -425,28 +425,74 @@ Set `image_model` in `pi-config-settings.json` or use `PI_IMAGE_MODEL` env var.
425
425
 
426
426
  In containers, images are auto-served via HTTP for browser preview.
427
427
 
428
- ### Graft repository graph (optional)
428
+ ### Trail-code code map (on by default)
429
429
 
430
- Set `graft_enable` to `true` in `.pi/pi-config-settings.json` (or set
431
- `PI_GRAFT_ENABLE=true`) to enable local Graft repository graph context:
430
+ Trail-code is the local code map behind the `trail_*` tools. It is **on by
431
+ default** whenever the `trail` binary is on `PATH` — no setting required. In a
432
+ trusted git worktree, pi indexes the project and gives main agents and
433
+ subagents a queryable map, so the first project navigation of every substantive
434
+ turn is a `trail_*` query instead of a blind `grep`.
435
+
436
+ Install or upgrade the CLI:
437
+
438
+ ```bash
439
+ DO_NOT_TRACK=1 npm install -g @trailhq/trail-code@latest
440
+ uv run scripts/install.py # audited install used by the container
441
+ ```
442
+
443
+ The installer downloads `@trailhq/trail-code@latest` with scripts disabled into a
444
+ dedicated npm prefix, audits that isolated dependency tree for lifecycle scripts,
445
+ and strictly rebuilds it with only those packages approved. The completed prefix
446
+ is published under the global npm prefix and its `trail` binary linked globally,
447
+ so unrelated global packages are never evaluated. It verifies `trail --version`;
448
+ `DO_NOT_TRACK=1` disables Trail telemetry. Requirements: Node.js 22+, npm 12+,
449
+ and the node-gyp prerequisites (Python 3, `make`, G++). If the binary is missing
450
+ or the install fails, pi continues without the map and the `trail_*` tools are
451
+ simply absent.
452
+
453
+ **Kill switch.** `trail_enable` (env `PI_TRAIL_ENABLE`) defaults to `true`. Set it
454
+ to `false` in `.pi/pi-config-settings.json`, or globally in
455
+ `~/.pi/pi-config-settings.json`, to keep the integration fully inactive:
432
456
 
433
457
  ```json
434
458
  {
435
- "graft_enable": true
459
+ "trail_enable": false
436
460
  }
437
461
  ```
438
462
 
439
- Graft requires Node.js 22+, npm 12+, node-gyp prerequisites (Python 3, `make`, and
440
- G++), and the `graft` CLI. The container includes them; native installs can use
441
- `uv run scripts/install.py`. The installer downloads `@nanonets/graft@latest` with
442
- scripts disabled into a dedicated npm prefix, audits that isolated dependency tree
443
- for lifecycle scripts, and strictly rebuilds it with only those packages approved.
444
- The completed prefix is then published under the global npm prefix and its `graft`
445
- binary linked globally, so unrelated global packages are never evaluated. The
446
- installer verifies `graft --version`; `DO_NOT_TRACK=1` disables Graft telemetry.
447
- When enabled, Graft reads the trusted project and writes a regenerable `./graft/`
448
- cache. Delete that directory or set `graft_enable` to `false` to disable it. If the
449
- native CLI is unavailable or its installation fails, pi continues without Graft.
463
+ **Where the map lives.** Upstream owns the path: `./graft/` on older repos and
464
+ `.trail/cache/` once a repo is on trail (upstream renames a legacy map itself).
465
+ Both are regenerable and gitignored, and pi reads whichever is present. Never
466
+ commit them.
467
+
468
+ **Worktrees.** The map is resolved from the worktree root, and enablement is
469
+ inherited from the main checkout when the worktree has no `.pi` settings file
470
+ (`.pi/` is gitignored, so a fresh worktree would otherwise look disabled). A
471
+ fresh worktree seeds its own cache from the main checkout on the first query — a
472
+ copy plus branch-diff repair, never a cold rebuild — and never writes to the main
473
+ checkout's map. Expect roughly 48 MB per worktree in this repo.
474
+
475
+ **Layers.** This is not memory. Pi memory (`.pi/memory/`, dreaming, enforced
476
+ entries) is what pi carries between sessions. Trail's own layer —
477
+ `.trail/learnings/`, `trail note`, the pre-ship `trail check` — is upstream's and
478
+ **we do not use it**. We only read the code map.
479
+
480
+ ### Migrating from Graft
481
+
482
+ The integration was renamed from Graft to Trail-code. The break is intentional:
483
+
484
+ - Only `@trailhq/trail-code` is installed and only `trail` is invoked. The `graft`
485
+ binary is no longer installed and there is no fallback — if you only have
486
+ `@nanonets/graft`, install trail to get the map tools back.
487
+ - `graft_enable` / `PI_GRAFT_ENABLE` are gone, with no alias and no migration. An
488
+ existing `graft_enable: true` is a dead key, which is harmless: the feature is
489
+ default-on when the binary exists.
490
+ - Tools are `trail_find_code`, `trail_find_all`, `trail_file_api`,
491
+ `trail_trace_calls`, `trail_repo_map`, `trail_refresh`, `trail_blast_radius`.
492
+ - `log_graft` becomes `log_trail` (env `PI_LOG_TRAIL`); the old `~/.pi/logs/graft`
493
+ directory is neither read nor migrated.
494
+ - `graft/` survives as a _path_ until upstream renames it: it is the map
495
+ directory, not the product.
450
496
 
451
497
  ### Cache Miss Notices
452
498
 
@@ -469,20 +515,20 @@ Run pi inside a disposable container for **filesystem isolation** — the agent
469
515
  - **Consistent tooling** — All required tools pre-installed in a single image
470
516
  - **Disposable** — Container is destroyed after each session (`--rm`)
471
517
 
472
- **Graft (optional `graft_enable`):** The image installs the latest
473
- `@nanonets/graft` with `DO_NOT_TRACK=1`. It downloads with scripts disabled, derives
518
+ **Trail-code (`trail_enable`, on by default):** The image installs the latest
519
+ `@trailhq/trail-code` with `DO_NOT_TRACK=1`. It downloads with scripts disabled, derives
474
520
  an exact script allowlist by auditing the installed package's reachable dependency
475
521
  metadata in a dedicated npm prefix, then strictly rebuilds that isolated tree with
476
522
  those approvals. Only after the rebuild succeeds is the completed prefix moved under
477
- the global npm prefix and its `graft` binary linked globally. This prevents unrelated
523
+ the global npm prefix and its `trail` binary linked globally. This prevents unrelated
478
524
  global packages from entering npm's strict preflight. The allowlist is command-scoped
479
525
  and changes with `@latest`; other npm commands keep npm 12's install-script
480
526
  protections. The image includes Python 3,
481
- `make`, and the GCC/G++ toolchain required by node-gyp for Graft and other native
527
+ `make`, and the GCC/G++ toolchain required by node-gyp for Trail-code and other native
482
528
  npm packages; these remain available for reliable package rebuilds. The build then
483
- verifies `graft --version`. Enable Graft in trusted project or global pi-config
484
- settings; it reads the project locally and stores its regenerable graph in `./graft/`. Set
485
- `graft_enable` to `false` and delete that directory to disable and remove the cache.
529
+ verifies `trail --version`. The map is written to the mounted project's `./graft/` or
530
+ `.trail/cache/` directory; set `trail_enable` to `false` and delete that directory to
531
+ disable and remove the cache.
486
532
  Native installations require Node.js 22+, npm 12+, Python 3, `make`, and G++; the
487
533
  installer uses the same audited strict installation and failure remains non-fatal.
488
534
 
@@ -789,7 +835,7 @@ PI_PIDIFF_ENABLE=false pi
789
835
  | `acpx` | Agent proxy for remote models |
790
836
  | `kubectl` / `oc` | Kubernetes and OpenShift CLI |
791
837
  | `agent-browser` | Browser automation CLI (navigate, click, screenshot, forms) |
792
- | `graft` | Local repository code graph for opt-in `graft_enable`; graph cache is stored in the mounted project’s `./graft/` directory |
838
+ | `trail` | Local code map for `trail_enable` (on by default); the regenerable cache lives in the mounted project's `./graft/` or `.trail/cache/` directory |
793
839
  | `procps` | Process utilities (ps, top, pgrep, pkill) |
794
840
  | `passwd` | `usermod`/`groupmod` — init remaps user `node` to `PI_HOST_UID`/`PI_HOST_GID` |
795
841
  | `docker` / `podman` | Container CLIs (used via `docker-safe` read-only wrapper) |
@@ -814,7 +860,7 @@ PI_PIDIFF_ENABLE=false pi
814
860
  | `myk-pi-tools pr claude-md` | Fetch `CLAUDE.md` and `AGENTS.md` content for a PR's repository |
815
861
  | `myk-pi-tools release create` | Create a GitHub release with changelog |
816
862
  | `myk-pi-tools release plan` | Scoped-release plan: which packages changed since their last publish (per-package registry-anchored diff windows). Detection only — no proposed versions. Read-only; requires `release-map.toml` at the repo root — without it, reports lockstep mode and changes nothing (issue #965) |
817
- | `myk-pi-tools release bump-scoped` | Bump ONLY the affected packages' version files. Versions are ALWAYS explicit: `--set NAME=VERSION` per affected package (the versions the LLM/user decided in the `/release` flow) or a global `--version X.Y.Z` — the plan never proposes versions. Each package versions from its own registry history (no global repo-tag guard — no forced jumps for long-skipped packages); tags are per-package `<name>@<version>`. Versions are validated (strict x.y.z, no leading zeros, strictly greater than the registry-published version). Errored packages, undetected version files, or partially-updatable manifests abort before or at the first bad write. Requires `release-map.toml` — without it, exit 1 (use `bump-version` for lockstep) (issue #965) |
863
+ | `myk-pi-tools release bump-scoped` | Bump ONLY the affected packages' version files. Versions are ALWAYS explicit: `--set NAME=VERSION` per affected package (the versions the LLM/user decided in the `/release` flow) or a global `--version X.Y.Z` — the plan never proposes versions. Each package versions from its own registry history (no global repo-tag guard — no forced jumps for long-skipped packages); tags are per-package `<name>@<version>`. Versions are validated (strict x.y.z, no leading zeros, strictly greater than the registry-published version). Errored packages, undetected version files, or partially-updatable manifests abort before or at the first bad write. Requires `release-map.toml` — without it, exit 1 (use `bump-version` for lockstep) (issue #965). When the repo has a `uv.lock` the bump refreshes it with `uv lock` and reports it under its own `lockfile` key — `updated[]` keeps only version files; a missing or failing `uv` reports `refreshed: false` instead of failing the release. |
818
864
  | `myk-pi-tools db` | Review database query commands |
819
865
  | `myk-pi-tools reviews` | Review handling commands (fetch, poll, address, post, store, store-session) |
820
866
  | `myk-pi-tools reviews poll` | Poll a PR for review comments until approved or actionable — `--source qodo` reads findings through the `qodo` CLI (structured, with spans and authoritative open/closed status) when the binary is installed and authenticated, and falls back to the GitHub sticky-comment scrape otherwise. Read-only: replies are always posted as GitHub comments via `reviews address` |
@@ -968,7 +1014,8 @@ Control your pi sessions from your phone via Discord DMs.
968
1014
  - Go to "Bot" section → click "Reset Token" → copy the token
969
1015
  - Enable "Message Content Intent" under "Privileged Gateway Intents"
970
1016
  - Go to "OAuth2 > URL Generator" → select scope `bot` → select permissions:
971
- `Send Messages`, `Read Message History`, `Add Reactions`, `View Channels`
1017
+ `Send Messages`, `Read Message History`, `Add Reactions`, `View Channels`,
1018
+ and for per-session threads: `Create Public Threads`, `Send Messages in Threads`
972
1019
  - Open the generated URL → add bot to your server
973
1020
 
974
1021
  2. **Configure:**
@@ -978,11 +1025,21 @@ Control your pi sessions from your phone via Discord DMs.
978
1025
  cat > ~/.pi/discord.env << EOF
979
1026
  DISCORD_BOT_TOKEN=your-token-here
980
1027
  DISCORD_ALLOWED_USERS=your-discord-user-id
1028
+ # Optional: open access without an allowlist (off by default)
1029
+ # DISCORD_ALLOW_ALL=true
1030
+ # Optional: how long a pending agent question waits (default 10m, min 30s)
1031
+ # DISCORD_PROMPT_TTL_MS=600000
1032
+ # Optional: forum channel id for /board (one post per session)
1033
+ # DISCORD_FORUM_CHANNEL_ID=
981
1034
  EOF
982
1035
  ```
983
1036
 
984
1037
  Find your Discord user ID: Settings > Advanced > Developer Mode ON, then right-click yourself > Copy User ID.
985
1038
 
1039
+ > The bot refuses to start without `DISCORD_ALLOWED_USERS` — a token alone must
1040
+ > not mean "anyone who DMs the bot drives your agent". Set
1041
+ > `DISCORD_ALLOW_ALL=true` only if you really want open access.
1042
+
986
1043
  3. **Restart pidash:**
987
1044
  The Discord bot runs inside the pidash server daemon. Restart it to pick up the config:
988
1045
 
@@ -992,15 +1049,30 @@ Control your pi sessions from your phone via Discord DMs.
992
1049
 
993
1050
  ### Discord Commands
994
1051
 
995
- | Command | Description |
996
- | ----------- | ----------------------------------- |
997
- | `!sessions` | List active pi sessions |
998
- | `!watch N` | Watch/switch to session N |
999
- | `!status` | Show current session info |
1000
- | `!abort` | Abort current operation |
1001
- | `!help` | Show commands |
1002
- | Any text | Send as prompt to watched session |
1003
- | `/command` | Forward slash command to pi session |
1052
+ | Command | Description |
1053
+ | -------------------- | -------------------------------------------------- |
1054
+ | `/sessions` | Paginated, ephemeral list with watch buttons |
1055
+ | `/watch session:<n>` | Watch a session by name (autocomplete) |
1056
+ | `/status` | Watched session: model, branch, state, cwd |
1057
+ | `/stop` | Interrupt the running turn |
1058
+ | `/compact` | Compact the watched session's context |
1059
+ | `/model [value]` | Show or switch the model |
1060
+ | `/thinking [value]` | Show or set the thinking level |
1061
+ | `/tasks` `/coms` `/memory` `/mcp` `/diff` | Inspect the watched session |
1062
+ | `/file path:<rel>` | Send a file from the session cwd (8MB max) |
1063
+ | `/notify on\|off` | DM when a turn finishes in an unwatched session |
1064
+ | Any text (DM/thread) | Send as a prompt to the watched session |
1065
+ | `/cancel` | Dismiss a pending agent question |
1066
+
1067
+ Agent questions arrive with **Approve/Deny** buttons or a dropdown; you can also
1068
+ reply `yes`/`no`, an option number, or the option text. In a server, watching a
1069
+ session opens a **thread** for it (DMs are used as a fallback).
1070
+
1071
+ The bot fails closed: without `DISCORD_ALLOWED_USERS` it refuses to start unless
1072
+ you set `DISCORD_ALLOW_ALL=true`. Commands, reaction shortcuts (⏹ 🔁 🗑), the
1073
+ forum session board and every environment variable are documented in
1074
+ [docs/discord-bot.md](docs/discord-bot.md)
1075
+ for the full surface.
1004
1076
 
1005
1077
  ### How It Works
1006
1078
 
@@ -1021,7 +1093,7 @@ it also runs as a pre-commit hook when TypeScript files change.
1021
1093
 
1022
1094
  ## Prerequisites
1023
1095
 
1024
- - [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.99.0**). Pidash uses Pi's `ui_prompt_start` and `ui_prompt_end` events to show prompt wait separately from agent work.
1096
+ - [pi](https://github.com/badlogic/pi-mono) (minimum version: **1.4.0**). Pidash uses Pi's `ui_prompt_start` and `ui_prompt_end` events to show prompt wait separately from agent work.
1025
1097
  - `gh` CLI (for GitHub operations)
1026
1098
  - `uv` (for Python execution)
1027
1099
  - `myk-pi-tools` (optional, for `/pr-review` and `/release`)
@@ -17,7 +17,7 @@ const require = createRequire(import.meta.url);
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
18
  import type { AgentConfig, AgentDiscoveryResult } from "./agents.js";
19
19
  import { resolveAgentModelProvider } from "./resolve-agent-model.js";
20
- import { withGraftTools } from "./graft.js";
20
+ import { trailToolsArgs } from "./trail.js";
21
21
  import { getPiInvocation, getProjectTmpDir, parseProcStartTime, djb2Hash } from "./utils.js";
22
22
  import { addReviewerPending, recordReviewerResult, countFindings, readReviewState } from "./pi-config-review-state.js";
23
23
  import {
@@ -1097,8 +1097,7 @@ export function registerAsyncAgents(
1097
1097
  piArgs.push("--session-id", deterministicSessionId);
1098
1098
  if (effectiveModel) piArgs.push("--model", effectiveModel);
1099
1099
  if (effectiveProvider) piArgs.push("--provider", effectiveProvider);
1100
- const tools = withGraftTools(agent.tools, getSetting(cwd, "graft_enable"));
1101
- if (tools?.length) piArgs.push("--tools", tools.join(","));
1100
+ piArgs.push(...trailToolsArgs(agent.tools, cwd));
1102
1101
 
1103
1102
  if (agent.systemPrompt?.trim()) {
1104
1103
  const promptPath = path.join(workerDir, "system-prompt.md");
@@ -37,7 +37,7 @@ import { registerSettingsTui } from "./settings-tui.js";
37
37
  import { buildSituationReport } from "./situation-report.js";
38
38
  import { listTopics } from "./memory-tree.js";
39
39
  import { registerMarkdownTransformer } from "./markdown-transformer.js";
40
- import { registerGraft } from "./graft.js";
40
+ import { registerTrail } from "./trail.js";
41
41
  import { registerReviewLearning } from "./review-learning.js";
42
42
  import { ensureGitSshTimeout, isRunningInContainer, terminalNotify } from "./utils.js";
43
43
  import { createLogger } from "../shared/logger.js";
@@ -129,7 +129,7 @@ export default function (pi: ExtensionAPI) {
129
129
  const { spawnAsyncAgent, killAsyncAgent, getAsyncJobs } = registerAsyncAgents(pi, terminalNotify);
130
130
  registerSubagentTool(pi, spawnAsyncAgent, killAsyncAgent);
131
131
  registerProjectSettings(pi);
132
- registerGraft(pi);
132
+ registerTrail(pi);
133
133
  registerEnforcement(pi, IN_CONTAINER);
134
134
  registerReviewUI(pi);
135
135
  registerRules(pi, getAsyncJobs);
@@ -46,8 +46,8 @@ interface ProjectSettings {
46
46
  cli_agents?: string | string[];
47
47
  pidash_enable?: boolean;
48
48
  pidiff_enable?: boolean;
49
- /** Enable local Graft repository graph integration. Default: false. */
50
- graft_enable?: boolean;
49
+ /** Enable local Trail code map integration. Default: true. */
50
+ trail_enable?: boolean;
51
51
  pidash_port?: number;
52
52
  task_auto_clear_enabled?: boolean;
53
53
  task_auto_clear_minutes?: number;
@@ -123,7 +123,7 @@ const PROJECT_SETTINGS_KEYS: (keyof ProjectSettings)[] = [
123
123
  "cli_agents",
124
124
  "pidash_enable",
125
125
  "pidiff_enable",
126
- "graft_enable",
126
+ "trail_enable",
127
127
  "pidash_port",
128
128
  "task_auto_clear_enabled",
129
129
  "task_auto_clear_minutes",
@@ -185,7 +185,7 @@ export function parseSettingsFile(filePath: string): ProjectSettings {
185
185
  if (typeof raw.orchestrator_edit_write_block === "boolean") result.orchestrator_edit_write_block = raw.orchestrator_edit_write_block;
186
186
  if (typeof raw.pidash_enable === "boolean") result.pidash_enable = raw.pidash_enable;
187
187
  if (typeof raw.pidiff_enable === "boolean") result.pidiff_enable = raw.pidiff_enable;
188
- if (typeof raw.graft_enable === "boolean") result.graft_enable = raw.graft_enable;
188
+ if (typeof raw.trail_enable === "boolean") result.trail_enable = raw.trail_enable;
189
189
  if (typeof raw.pidash_port === "number" && Number.isInteger(raw.pidash_port) && raw.pidash_port > 0 && raw.pidash_port <= 65535) {
190
190
  result.pidash_port = raw.pidash_port;
191
191
  }
@@ -238,7 +238,7 @@ export function parseSettingsFile(filePath: string): ProjectSettings {
238
238
  const SPECIAL_CASE_KEYS = new Set<string>([
239
239
  "commit_trailer", "allow_push_to_protected_branches", "use_worktrees",
240
240
  "dream_interval_hours", "dco", "comment_signature", "review_loop_enforcement",
241
- "orchestrator_edit_write_block", "pidash_enable", "pidiff_enable", "graft_enable", "pidash_port",
241
+ "orchestrator_edit_write_block", "pidash_enable", "pidiff_enable", "trail_enable", "pidash_port",
242
242
  "image_model", "internal_operations_provider", "internal_operations_model",
243
243
  "review_loop_max_cycles", "acpx_agents", "cli_agents", "agent_provider",
244
244
  "agent_model", "agent_overrides",
@@ -485,7 +485,7 @@ export function getSetting(cwd: string, key: "acpx_agents"): string[];
485
485
  export function getSetting(cwd: string, key: "cli_agents"): string[];
486
486
  export function getSetting(cwd: string, key: "pidash_enable"): boolean;
487
487
  export function getSetting(cwd: string, key: "pidiff_enable"): boolean;
488
- export function getSetting(cwd: string, key: "graft_enable"): boolean;
488
+ export function getSetting(cwd: string, key: "trail_enable"): boolean;
489
489
  export function getSetting(cwd: string, key: "pidash_port"): number;
490
490
  export function getSetting(cwd: string, key: "task_auto_clear_enabled"): boolean;
491
491
  export function getSetting(cwd: string, key: "task_auto_clear_minutes"): number;
@@ -1391,12 +1391,12 @@ export function createReviewLearning(options: ReviewLearningOptions = {}) {
1391
1391
  }
1392
1392
  });
1393
1393
 
1394
- // Surface 1 — before_agent_start injection (graft-gate pattern).
1394
+ // Surface 1 — before_agent_start injection (trail-gate pattern).
1395
1395
  // Purely mechanical path extraction (prompt tokens + branch diff), sync
1396
1396
  // DB/git with timeouts, no model calls. Diff paths are repo-relative —
1397
1397
  // normalized against the worktree ROOT so nested sessions still match
1398
1398
  // the DB's repo-relative paths. Must be fast and never throw — other
1399
- // before_agent_start handlers (graft gate, rules) must not stall.
1399
+ // before_agent_start handlers (trail gate, rules) must not stall.
1400
1400
  pi.on("before_agent_start", (event: any, ctx: any) => {
1401
1401
  try {
1402
1402
  const cwd = ctx?.cwd;
@@ -1415,7 +1415,7 @@ export function createReviewLearning(options: ReviewLearningOptions = {}) {
1415
1415
  recall.fullBlockInjected = true;
1416
1416
  log.info("recall_injected_prompt", { paths: findings.length, chars: block.length });
1417
1417
  // Prefer a custom prompt section (rules.ts pattern — preserves cached
1418
- // prompt prefixes); fall back to the graft return shape when a prior
1418
+ // prompt prefixes); fall back to a replacement prompt when a prior
1419
1419
  // handler forced the whole prompt or sections are unavailable.
1420
1420
  const opts = event?.systemPromptOptions;
1421
1421
  const sections = opts?.sections;
@@ -1430,7 +1430,7 @@ export function createReviewLearning(options: ReviewLearningOptions = {}) {
1430
1430
  }
1431
1431
  });
1432
1432
 
1433
- // Surface 2 — first-write tool_result hint (graft blastHint pattern):
1433
+ // Surface 2 — first-write tool_result hint (trail blastHint pattern):
1434
1434
  // catches paths discovered mid-run that prompt extraction missed. Appends
1435
1435
  // once per path per session; a path is checked once per session whether
1436
1436
  // or not it has findings (a no-findings path must not re-query the DB on
@@ -236,19 +236,23 @@ export function registerRules(
236
236
  }
237
237
  }
238
238
 
239
+ // 1.4.0's BeforeAgentStartEvent is a union: `prompt` exists only on the user
240
+ // variant, so narrow once here and use this for every downstream lookup.
241
+ const prompt = event.messageType === "user" ? event.prompt : undefined;
242
+
239
243
  // Project memories — situation report (scored, token-budgeted) injected BEFORE rules
240
244
  // systemPrompt.length is chars; estimateMemoryBudget converts internally
241
245
  const budget = estimateMemoryBudget(event.systemPrompt?.length ?? 0, undefined, ctx.cwd);
242
246
  const queryClass =
243
- !isSubagent && event.prompt && !isSocialCloser(event.prompt)
244
- ? classifyQueryClass(event.prompt)
247
+ !isSubagent && prompt && !isSocialCloser(prompt)
248
+ ? classifyQueryClass(prompt)
245
249
  : "general";
246
250
  const memories = loadMemoriesWithScoring(ctx.cwd, isSubagent, budget, queryClass);
247
251
 
248
252
  // Auto-inject contextually relevant memories via vector search (~2.5ms, in-process)
249
253
  // Session-scoped/expired entries are filtered out first (Qodo #19, PR #916)
250
254
  // — a session rule must not leak into a later session's context.
251
- const shouldSearch = !isSubagent && !!event.prompt && !isSocialCloser(event.prompt);
255
+ const shouldSearch = !isSubagent && !!prompt && !isSocialCloser(prompt);
252
256
  let contextMemories = "";
253
257
  if (shouldSearch) {
254
258
  try {
@@ -271,7 +275,7 @@ export function registerRules(
271
275
  ...entries.filter((e) => !boostCats.has(e.category)),
272
276
  ]
273
277
  : entries;
274
- const results = await vectorSearch(ctx.cwd, event.prompt, searchPool, topK);
278
+ const results = await vectorSearch(ctx.cwd, prompt, searchPool, topK);
275
279
  const injectThreshold = Number(getSetting(ctx.cwd, "memory_inject_similarity")) || 0.65;
276
280
  const relevant = results.filter(r => r.similarity > injectThreshold);
277
281
  if (relevant.length > 0) {
@@ -281,7 +285,7 @@ export function registerRules(
281
285
  relevant.map(r => `- [${r.category}] ${r.text} (similarity: ${r.similarity.toFixed(3)})`).join("\n") +
282
286
  "\n\n";
283
287
  // Retrieval telemetry — track what was injected
284
- logMemoryInjection(ctx.cwd, event.prompt, relevant, queryClass);
288
+ logMemoryInjection(ctx.cwd, prompt, relevant, queryClass);
285
289
  lastInjectedMemories = relevant;
286
290
  log.info("memory_injected", relevant.length);
287
291
  }
@@ -294,7 +298,7 @@ export function registerRules(
294
298
  if (shouldSearch) {
295
299
  try {
296
300
  const { searchSessions } = await import("./session-search.js");
297
- const sessionResults = searchSessions(ctx.cwd, event.prompt, 3);
301
+ const sessionResults = searchSessions(ctx.cwd, prompt, 3);
298
302
  if (sessionResults.length > 0) {
299
303
  log.info("context_injected", "session_history");
300
304
  sessionContext = "\n# Relevant Past Sessions\n\n" +
@@ -307,7 +311,7 @@ export function registerRules(
307
311
  try {
308
312
  getTelemetryLog(ctx.cwd).append({
309
313
  event: "session-inject",
310
- prompt: event.prompt.slice(0, 200),
314
+ prompt: prompt?.slice(0, 200),
311
315
  sessionCount: sessionResults.length,
312
316
  });
313
317
  } catch (e: any) { log.debug("session telemetry write failed:", e?.message?.slice(0, 100)); }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Stale-ctx safety (reload crash postmortem, PR #950): ctx.ui getters call
11
11
  * assertActive() and THROW after a runtime reload/replacement — an async
12
- * continuation (e.g. a graft build resolving across a reload) or a
12
+ * continuation (e.g. a trail build resolving across a reload) or a
13
13
  * session_shutdown handler touching a captured ctx must never crash the
14
14
  * process over a footer update. Guarded with isLiveExtensionCtx + try/catch.
15
15
  */
@@ -26,7 +26,7 @@ const SLOTS = {
26
26
  crons: "3-crons",
27
27
  dream: "3b-dream",
28
28
  git: "4-git",
29
- graft: "4b-graft",
29
+ trail: "4b-trail",
30
30
  review: "5-review",
31
31
  } as const;
32
32
 
@@ -47,7 +47,7 @@ import { getSetting } from "./project-settings.js";
47
47
  import { checkSyncLimit } from "./sync-limit.js";
48
48
  import { substituteSettingsPlaceholders } from "./rule-placeholders.js";
49
49
  import { resolveAgentModelProvider } from "./resolve-agent-model.js";
50
- import { withGraftTools } from "./graft.js";
50
+ import { trailToolsArgs } from "./trail.js";
51
51
  import { parseModelOverride, mergeModelOverride } from "./parse-model-override.js";
52
52
  import { clockHHMM, getPiInvocation, getProjectTmpDir, djb2Hash } from "./utils.js";
53
53
 
@@ -424,8 +424,7 @@ export async function runSingleAgent(
424
424
  const { model: effectiveModel, provider: effectiveProvider } = resolveAgentModelProvider(agentName, agent, parentModelId, parentProvider, cwd, explicit);
425
425
  if (effectiveModel) args.push("--model", effectiveModel);
426
426
  if (effectiveProvider) args.push("--provider", effectiveProvider);
427
- const tools = withGraftTools(agent.tools, getSetting(cwd, "graft_enable"));
428
- if (tools?.length) args.push("--tools", tools.join(","));
427
+ args.push(...trailToolsArgs(agent.tools, cwd));
429
428
 
430
429
  let tmpDir: string | null = null;
431
430
  let tmpFile: string | null = null;