pi-orchestrator-config 4.21.0 → 4.22.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 +108 -36
- package/extensions/orchestrator/async-agents.ts +2 -3
- package/extensions/orchestrator/index.ts +2 -2
- package/extensions/orchestrator/project-settings.ts +6 -6
- package/extensions/orchestrator/review-learning.ts +4 -4
- package/extensions/orchestrator/rules.ts +11 -7
- package/extensions/orchestrator/status-bar.ts +2 -2
- package/extensions/orchestrator/subagent-tool.ts +2 -3
- package/extensions/orchestrator/trail.ts +745 -0
- package/extensions/orchestrator/utils.ts +44 -1
- package/extensions/pidash/discord-answers.ts +8 -0
- package/extensions/pidash/discord-attachments.ts +8 -0
- package/extensions/pidash/discord-board.ts +75 -0
- package/extensions/pidash/discord-bot.ts +1811 -0
- package/extensions/pidash/discord-commands.ts +90 -0
- package/extensions/pidash/discord-config.ts +93 -0
- package/extensions/pidash/discord-format.ts +24 -0
- package/extensions/pidash/discord-jobs-view.ts +74 -0
- package/extensions/pidash/discord-reactions.ts +76 -0
- package/extensions/pidash/discord-render.ts +8 -0
- package/extensions/pidash/discord-sessions-view.ts +8 -0
- package/extensions/pidash/discord-state.ts +8 -0
- package/extensions/pidash/pidash-ui/dist/assets/{index-DzT6_qJc.js → index-B7e6Zyb5.js} +1 -1
- package/extensions/pidash/pidash-ui/dist/index.html +1 -1
- package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +3 -3
- package/extensions/pidash/pidash.ts +41 -13
- package/extensions/shared/chat/chat-attachments.ts +192 -0
- package/extensions/shared/chat/chat-bridge.ts +420 -0
- package/extensions/shared/chat/chat-events.ts +124 -0
- package/extensions/shared/chat/chat-format.ts +136 -0
- package/extensions/shared/chat/chat-picker.ts +128 -0
- package/extensions/shared/chat/chat-prompts.ts +158 -0
- package/extensions/shared/chat/chat-sessions.ts +187 -0
- package/extensions/shared/chat/chat-state.ts +192 -0
- package/extensions/shared/chat/chat-types.ts +86 -0
- package/extensions/shared/create-runtime-provider.ts +1 -1
- package/extensions/shared/types.ts +7 -2
- package/package.json +6 -6
- package/scripts/pidash-discord.ts +6 -588
- package/scripts/pidash-server.ts +1 -0
- package/scripts/pidash-session-state.ts +38 -3
- package/scripts/{graft-allow-scripts.mjs → trail-allow-scripts.mjs} +2 -2
- package/settings-keys.json +8 -8
- 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
|
-
| **
|
|
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,
|
|
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
|
-
###
|
|
428
|
+
### Trail-code code map (on by default)
|
|
429
429
|
|
|
430
|
-
|
|
431
|
-
`
|
|
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
|
-
"
|
|
459
|
+
"trail_enable": false
|
|
436
460
|
}
|
|
437
461
|
```
|
|
438
462
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
The
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
**
|
|
473
|
-
`@
|
|
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 `
|
|
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
|
|
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 `
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
| `
|
|
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) |
|
|
@@ -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
|
|
996
|
-
|
|
|
997
|
-
|
|
|
998
|
-
|
|
|
999
|
-
|
|
|
1000
|
-
|
|
|
1001
|
-
|
|
|
1002
|
-
|
|
|
1003
|
-
| `/
|
|
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: **
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
50
|
-
|
|
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
|
-
"
|
|
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.
|
|
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", "
|
|
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: "
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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 &&
|
|
244
|
-
? classifyQueryClass(
|
|
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 && !!
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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;
|