pi-harness-delegate 0.6.0 → 0.6.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.
- package/README.md +1 -1
- package/extensions/index.ts +32 -2
- package/extensions/templates.ts +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ You are a senior engineer delegated by the pi coding agent.
|
|
|
134
134
|
|
|
135
135
|
Custom templates are just files dropped in the above dirs — any registered name becomes a valid `mode`.
|
|
136
136
|
|
|
137
|
-
**Project trust:** project-local templates (`.pi/delegate/templates/`) load only when pi itself considers the current project trusted (`ctx.isProjectTrusted()`, backed by pi's own trust store outside the project — the same trust that gates other project-scoped behavior). Trust it via pi's own trust prompt (shown the first time you open an untrusted directory) or your `defaultProjectTrust` setting; `/delegate status` reports whether the current project is trusted and, if not, that project-local templates are being skipped. There is **no way to grant trust from inside the project** — no `.pi/trusted` file, no environment variable. Earlier versions supported both (`.pi/trusted` containing `1`, or `PI_TRUSTED=1`/`PI_DELEGATE_TRUSTED=1` in the environment); both were removed as a security fix — a repo could commit `.pi/trusted` and declare itself trusted, letting a cloned hostile repo's templates silently override a builtin (e.g. widening `review` from `readonly` to `edit` and attaching a `verify:` command that runs host-side). If you relied on either, switch to pi's trust prompt or `defaultProjectTrust`.
|
|
137
|
+
**Project trust:** project-local templates (`.pi/delegate/templates/`) load only when pi itself considers the current project trusted (`ctx.isProjectTrusted()`, backed by pi's own trust store outside the project — the same trust that gates other project-scoped behavior). Trust it via pi's own trust prompt (shown the first time you open an untrusted directory) or your `defaultProjectTrust` setting; `/delegate status` reports whether the current project is trusted and, if not, that project-local templates are being skipped. There is **no way to grant trust from inside the project** — no `.pi/trusted` file, no environment variable. Earlier versions supported both (`.pi/trusted` containing `1`, or `PI_TRUSTED=1`/`PI_DELEGATE_TRUSTED=1` in the environment); both were removed as a security fix — a repo could commit `.pi/trusted` and declare itself trusted, letting a cloned hostile repo's templates silently override a builtin (e.g. widening `review` from `readonly` to `edit` and attaching a `verify:` command that runs host-side). If you relied on either, switch to pi's trust prompt or `defaultProjectTrust`. **On upgrade this is otherwise silent** — a project override shares its name with the builtin it replaces, so the run just uses the builtin and looks fine — so a delegation in an untrusted project that actually has `.pi/delegate/templates/` now prints a one-time warning saying they were skipped, and points out a leftover `.pi/trusted` file if one is present (it no longer does anything and can be deleted).
|
|
138
138
|
|
|
139
139
|
## How the main session consumes the output
|
|
140
140
|
|
package/extensions/index.ts
CHANGED
|
@@ -86,7 +86,13 @@ import { NotifyBatcher } from './notify.ts';
|
|
|
86
86
|
import { type FeedEntry, progressWindow } from './progress.ts';
|
|
87
87
|
import { formatFanoutChip, multiProgressWindow, type RunRow } from './progress-multi.ts';
|
|
88
88
|
import { runHarness } from './runner.ts';
|
|
89
|
-
import {
|
|
89
|
+
import {
|
|
90
|
+
type DelegateTemplate,
|
|
91
|
+
describeSkippedProjectTemplates,
|
|
92
|
+
loadTemplates,
|
|
93
|
+
projectTemplatePresence,
|
|
94
|
+
resolveNativePermission,
|
|
95
|
+
} from './templates.ts';
|
|
90
96
|
import { mapClaudeUsage } from './usage.ts';
|
|
91
97
|
|
|
92
98
|
/** Render a possibly-unknown cost — `null` means the harness didn't report one, not a measured $0. */
|
|
@@ -188,6 +194,28 @@ function outputsDirFor(harness: string): string {
|
|
|
188
194
|
* project-local delegate templates load — see the trust-tier comment on `loadTemplates`. Fails
|
|
189
195
|
* closed (untrusted) if the host is old enough not to expose the method, or if it throws.
|
|
190
196
|
*/
|
|
197
|
+
/**
|
|
198
|
+
* Warn once per project when trusted-only content was silently skipped.
|
|
199
|
+
*
|
|
200
|
+
* Before 0.6.0 a committed `.pi/trusted` file (or `PI_TRUSTED=1`) granted trust; both were removed as
|
|
201
|
+
* a security fix. A user who relied on either loses their project-local templates on upgrade with no
|
|
202
|
+
* visible signal — an override shares its name with the builtin it replaces, so the run just uses the
|
|
203
|
+
* builtin and looks fine. `/delegate status` reports trust state, but nobody runs it unless something
|
|
204
|
+
* already looks wrong, so this fires on an actual delegation instead — and only when the project
|
|
205
|
+
* demonstrably has the content being skipped, so it never nags anyone unaffected.
|
|
206
|
+
*/
|
|
207
|
+
const warnedUntrustedProjects = new Set<string>();
|
|
208
|
+
|
|
209
|
+
function warnIfProjectTemplatesSkipped(ctx: ExtensionContext, trusted: boolean): void {
|
|
210
|
+
if (trusted || warnedUntrustedProjects.has(ctx.cwd)) return;
|
|
211
|
+
const lines = describeSkippedProjectTemplates(projectTemplatePresence(ctx.cwd));
|
|
212
|
+
if (lines.length === 0) return;
|
|
213
|
+
warnedUntrustedProjects.add(ctx.cwd);
|
|
214
|
+
const msg = lines.join('\n');
|
|
215
|
+
if (ctx.hasUI) ctx.ui.notify?.(msg, 'warning');
|
|
216
|
+
else process.stderr.write(`${msg}\n`);
|
|
217
|
+
}
|
|
218
|
+
|
|
191
219
|
function isProjectTrusted(ctx: ExtensionContext): boolean {
|
|
192
220
|
try {
|
|
193
221
|
return typeof ctx.isProjectTrusted === 'function' && ctx.isProjectTrusted() === true;
|
|
@@ -611,7 +639,9 @@ async function delegate(
|
|
|
611
639
|
throw new Error(
|
|
612
640
|
`unknown harness "${harnessName}". Available: ${HARNESS_NAMES.join(', ')} (aliases: ${Object.keys(ALIASES).join(', ')})`,
|
|
613
641
|
);
|
|
614
|
-
const
|
|
642
|
+
const projectTrusted = isProjectTrusted(ctx);
|
|
643
|
+
warnIfProjectTemplatesSkipped(ctx, projectTrusted);
|
|
644
|
+
const templates = loadTemplates(ctx.cwd, harnessName, projectTrusted);
|
|
615
645
|
const mode = opts.mode || config.defaultMode;
|
|
616
646
|
const template = templates.get(mode);
|
|
617
647
|
if (!template)
|
package/extensions/templates.ts
CHANGED
|
@@ -201,3 +201,56 @@ export function resolveNativePermission(
|
|
|
201
201
|
if (!nativePermission) return undefined;
|
|
202
202
|
return effectivePermission === templatePermission ? nativePermission : undefined;
|
|
203
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* What a project has on disk that only loads when the project is trusted.
|
|
207
|
+
*
|
|
208
|
+
* Used to warn a user whose project-local templates stopped loading after the 0.6.0 security fix
|
|
209
|
+
* (§19 of ROADMAP) — before it, a committed `.pi/trusted` file or `PI_TRUSTED=1` granted trust, and
|
|
210
|
+
* both were removed. The failure is otherwise invisible: an override shares its name with the
|
|
211
|
+
* builtin it replaces, so the run silently uses the builtin and produces plausible output.
|
|
212
|
+
*
|
|
213
|
+
* Pure filesystem inspection — no trust logic. The caller supplies the trust decision.
|
|
214
|
+
*/
|
|
215
|
+
export function projectTemplatePresence(cwd: string): {
|
|
216
|
+
/** Template dirs that exist and would load if the project were trusted. */
|
|
217
|
+
dirs: string[];
|
|
218
|
+
/** A leftover `.pi/trusted` file — strong evidence the user relied on the removed mechanism. */
|
|
219
|
+
staleTrustFile: boolean;
|
|
220
|
+
} {
|
|
221
|
+
const candidates = [projectTemplatesDir(cwd), legacyProjectTemplatesDir(cwd)];
|
|
222
|
+
const dirs: string[] = [];
|
|
223
|
+
for (const dir of candidates) {
|
|
224
|
+
try {
|
|
225
|
+
if (readdirSync(dir).some(f => f.endsWith('.md'))) dirs.push(dir);
|
|
226
|
+
} catch {
|
|
227
|
+
// absent or unreadable — nothing to warn about
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
let staleTrustFile = false;
|
|
231
|
+
try {
|
|
232
|
+
staleTrustFile = existsSync(join(cwd, '.pi', 'trusted'));
|
|
233
|
+
} catch {
|
|
234
|
+
staleTrustFile = false;
|
|
235
|
+
}
|
|
236
|
+
return { dirs, staleTrustFile };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** One-line notices for a project whose trusted-only content was skipped. Empty when nothing applies. */
|
|
240
|
+
export function describeSkippedProjectTemplates(presence: { dirs: string[]; staleTrustFile: boolean }): string[] {
|
|
241
|
+
if (presence.dirs.length === 0 && !presence.staleTrustFile) return [];
|
|
242
|
+
const out: string[] = [];
|
|
243
|
+
if (presence.dirs.length > 0) {
|
|
244
|
+
out.push(
|
|
245
|
+
`⚠ project-local templates were NOT loaded — this project is untrusted (${presence.dirs.join(', ')})`,
|
|
246
|
+
" trust it via pi's trust prompt or defaultProjectTrust; /delegate status shows trust state",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (presence.staleTrustFile) {
|
|
250
|
+
out.push(
|
|
251
|
+
' a leftover .pi/trusted file was found — it no longer grants trust (removed in 0.6.0 as a',
|
|
252
|
+
' security fix, since a repo could use it to trust itself) and can be deleted',
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-harness-delegate",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Delegate work to any harness (Claude Code, Muse, OpenCode, Amp) from the pi coding agent \u2014 code reviews, plans, implementation, security audits, docs, or your own custom templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.14",
|