pi-orchestrator-config 4.2.1 → 4.3.2
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 +52 -61
- package/agents/git-expert.md +5 -1
- package/agents/github-expert.md +4 -1
- package/extensions/acpx-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/agents/claude.ts +3 -2
- package/extensions/cli-provider/agents/cursor.ts +3 -5
- package/extensions/cli-provider/agents/gemini.ts +3 -2
- package/extensions/cli-provider/discover.ts +7 -7
- package/extensions/cli-provider/index.ts +5 -0
- package/extensions/cli-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/session-reaper.ts +5 -7
- package/extensions/cli-provider/sessions.ts +4 -0
- package/extensions/cli-provider/shared/discover-cache.ts +17 -176
- package/extensions/coms/coms-p2p.ts +1719 -423
- package/extensions/coms/coms-shared.ts +261 -39
- package/extensions/coms/coms-wrapper.ts +100 -62
- package/extensions/coms/index.ts +8 -4
- package/extensions/coms/mixed-turn.ts +26 -0
- package/extensions/coms/probe-socket.ts +42 -0
- package/extensions/coms/themeMap.ts +0 -1
- package/extensions/image-gen/image-gen.ts +19 -1
- package/extensions/orchestrator/async-agents.ts +509 -85
- package/extensions/orchestrator/cron.ts +14 -8
- package/extensions/orchestrator/dreaming.ts +31 -24
- package/extensions/orchestrator/enforcement-helpers.ts +459 -17
- package/extensions/orchestrator/enforcement-rules.ts +5 -4
- package/extensions/orchestrator/enforcement.ts +79 -25
- package/extensions/orchestrator/extended-autocomplete.ts +7 -0
- package/extensions/orchestrator/git-helpers.ts +2 -2
- package/extensions/orchestrator/index.ts +23 -10
- package/extensions/orchestrator/markdown-transformer.ts +313 -0
- package/extensions/orchestrator/memory-embeddings.ts +36 -34
- package/extensions/orchestrator/memory-provenance.ts +29 -25
- package/extensions/orchestrator/memory-scoring.ts +23 -18
- package/extensions/orchestrator/memory-tools.ts +10 -3
- package/extensions/orchestrator/overlay-dashboard.ts +17 -5
- package/extensions/orchestrator/pi-config-review-state.ts +44 -33
- package/extensions/orchestrator/project-settings.ts +220 -21
- package/extensions/orchestrator/review-porcelain.ts +4 -0
- package/extensions/orchestrator/review-ui.ts +11 -5
- package/extensions/orchestrator/rule-placeholders.ts +384 -1
- package/extensions/orchestrator/rules.ts +80 -132
- package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
- package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
- package/extensions/orchestrator/settings-tui.ts +636 -0
- package/extensions/orchestrator/state-jsonl.ts +431 -0
- package/extensions/orchestrator/subagent-tool.ts +105 -50
- package/extensions/orchestrator/sync-limit.ts +23 -0
- package/extensions/orchestrator/task-lifecycle.ts +77 -67
- package/extensions/orchestrator/utils.ts +8 -5
- package/extensions/pidash/pidash.ts +193 -156
- package/extensions/pidiff/pidiff.ts +64 -38
- package/extensions/pidiff/spawn-lock.ts +62 -0
- package/extensions/pitasks/index.ts +430 -0
- package/extensions/pitasks/reminders.ts +37 -0
- package/extensions/pitasks/task-store.ts +467 -0
- package/extensions/pitasks/task-tools.ts +375 -0
- package/extensions/pitasks/task-widget.ts +181 -0
- package/extensions/pitasks/types.ts +38 -0
- package/extensions/providers/acpx-driver.ts +12 -2
- package/extensions/providers/built-in-drivers.ts +6 -6
- package/extensions/providers/claude-driver.ts +1 -1
- package/extensions/providers/cursor-cli-driver.ts +1 -1
- package/extensions/providers/gemini-driver.ts +1 -1
- package/extensions/providers/index.ts +144 -26
- package/extensions/providers/initialized-guard.ts +27 -0
- package/extensions/providers/restore-default-model.ts +596 -0
- package/extensions/providers/session-shutdown.ts +39 -0
- package/extensions/shared/coms-active.ts +23 -0
- package/extensions/shared/create-runtime-provider.ts +2 -0
- package/extensions/shared/daemon-manager.ts +0 -8
- package/extensions/shared/file-logger.ts +132 -25
- package/extensions/shared/logger.ts +50 -0
- package/extensions/shared/models-dev.ts +421 -0
- package/extensions/shared/oneshot.ts +116 -0
- package/extensions/shared/resolve-binary.ts +191 -0
- package/extensions/shared/types.ts +3 -0
- package/package.json +7 -2
- package/prompts/coderabbit-rate-limit.md +2 -0
- package/rules/00-orchestrator-core.md +3 -0
- package/rules/10-agent-routing.md +1 -1
- package/rules/20-code-review-loop.md +38 -16
- package/rules/30-prompt-templates.md +4 -0
- package/rules/35-memory.md +3 -3
- package/rules/40-critical-rules.md +3 -0
- package/rules/55-coms-protocol.md +84 -18
- package/rules/60-task-tracking.md +2 -0
- package/settings-keys.json +481 -20
- package/templates/coms-feature-manager-prompt.md +4 -6
- package/extensions/coms/coms-net-server.ts +0 -1642
- package/extensions/coms/coms-net-wrapper.ts +0 -521
- package/extensions/coms/coms-net.ts +0 -1589
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* review state tracking and gitignore checks.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createLogger } from "../shared/logger.js";
|
|
8
9
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import { Type } from "typebox";
|
|
@@ -34,6 +35,9 @@ import {
|
|
|
34
35
|
checkTempFileEnforcement,
|
|
35
36
|
hasGitAddBulk,
|
|
36
37
|
hasGitAddForce,
|
|
38
|
+
isRealGitCommitOrPush,
|
|
39
|
+
injectSignoff,
|
|
40
|
+
injectGhBodySignature,
|
|
37
41
|
isReadOnlyStatement,
|
|
38
42
|
isRmInProjectTmp,
|
|
39
43
|
normalizeForRepeatCheck,
|
|
@@ -41,9 +45,16 @@ import {
|
|
|
41
45
|
stripHeredocBodies,
|
|
42
46
|
escapeForDoubleQuote,
|
|
43
47
|
escapeForSingleQuote,
|
|
48
|
+
commandHasTrailerByName,
|
|
44
49
|
isTestRunnerCommand,
|
|
50
|
+
isBumpVersionBranch,
|
|
51
|
+
getCachedBranch,
|
|
45
52
|
} from "./enforcement-helpers.js";
|
|
46
53
|
|
|
54
|
+
const log = createLogger("enforcement");
|
|
55
|
+
|
|
56
|
+
export { isBumpVersionBranch, getCachedBranch };
|
|
57
|
+
|
|
47
58
|
type EnforcementResult = { block: true; reason: string } | { autofix: true; modifiedCommand: string; reason: string } | undefined;
|
|
48
59
|
|
|
49
60
|
// Repeat detection via temp file — immune to module reload / closure issues
|
|
@@ -72,7 +83,7 @@ function writeRepeatState(state: { lastCmd: string; count: number }): void {
|
|
|
72
83
|
if (!REPEAT_FILE) return;
|
|
73
84
|
try {
|
|
74
85
|
writeFileSync(REPEAT_FILE, JSON.stringify(state));
|
|
75
|
-
} catch (e: any) {
|
|
86
|
+
} catch (e: any) { log.debug("enforcement: write repeat state failed:", e?.message || e); }
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
|
|
@@ -82,7 +93,7 @@ async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd
|
|
|
82
93
|
const trailerSetting = getSetting(gitCwd, "commit_trailer");
|
|
83
94
|
if (typeof trailerSetting !== "string") return undefined;
|
|
84
95
|
|
|
85
|
-
const modelId = (process.env.PI_MODEL || (ctx as any).model?.id || "unknown").replace(/[^a-zA-Z0-9._:@/ -]/g, "").trim() || "unknown";
|
|
96
|
+
const modelId = (process.env.PI_PRIMARY_MODEL || process.env.PI_MODEL || (ctx as any).model?.id || "unknown").replace(/[^a-zA-Z0-9._:@/ -]/g, "").trim() || "unknown";
|
|
86
97
|
|
|
87
98
|
const piIdentity = `PI (${modelId}) <noreply@pi.dev>`;
|
|
88
99
|
let trailerName: string;
|
|
@@ -122,10 +133,6 @@ async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd
|
|
|
122
133
|
trailerName = trailerSetting;
|
|
123
134
|
}
|
|
124
135
|
|
|
125
|
-
// Raw trailer line for duplicate detection (what the commit message should contain)
|
|
126
|
-
const rawTrailerLine = `${trailerName}: ${piIdentity}`;
|
|
127
|
-
if (command.includes(rawTrailerLine)) return undefined;
|
|
128
|
-
|
|
129
136
|
// Pattern A: echo "..." | git commit -F -
|
|
130
137
|
const pipeIdx = command.lastIndexOf("|");
|
|
131
138
|
if (pipeIdx !== -1 && /git\s+commit\s+.*-F\s*-/.test(command.slice(pipeIdx))) {
|
|
@@ -137,6 +144,14 @@ async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd
|
|
|
137
144
|
if (lastQuoteIdx > 0) {
|
|
138
145
|
// Determine quote context for correct escaping
|
|
139
146
|
const quoteChar = echoPart[lastQuoteIdx];
|
|
147
|
+
// Scope dedup to the echo/message-construction portion (before the pipe), which
|
|
148
|
+
// excludes the git args. Do NOT attempt to slice out the exact quoted substring
|
|
149
|
+
// by backward-scanning for a quote — escaped quotes (\") inside the message would
|
|
150
|
+
// misparse the boundary and cause a missed trailer -> duplicate injection.
|
|
151
|
+
if (commandHasTrailerByName(echoPart, trailerName)) {
|
|
152
|
+
log.debug("commit_trailer already present in echo message (by name) — skipping injection", trailerName);
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
140
155
|
const escaped = quoteChar === "'"
|
|
141
156
|
? `${escapeForSingleQuote(trailerName)}: ${piIdentity}`
|
|
142
157
|
: `${escapeForDoubleQuote(trailerName)}: ${piIdentity}`;
|
|
@@ -149,6 +164,14 @@ async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd
|
|
|
149
164
|
const mFlagMatch = command.match(/git\s+commit\s+.*-m\s+(["'])([\s\S]*?)\1/);
|
|
150
165
|
if (mFlagMatch) {
|
|
151
166
|
const quoteChar = mFlagMatch[1];
|
|
167
|
+
// Scope duplicate-by-name detection to the -m message payload only
|
|
168
|
+
// (mFlagMatch[2]) — not the whole command — so an unrelated arg/env var
|
|
169
|
+
// containing the trailer token does not wrongly skip injection.
|
|
170
|
+
const payload = mFlagMatch[2];
|
|
171
|
+
if (commandHasTrailerByName(payload, trailerName)) {
|
|
172
|
+
log.debug("commit_trailer already present in message (by name) — skipping injection", trailerName);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
152
175
|
const escaped = quoteChar === "'"
|
|
153
176
|
? `${escapeForSingleQuote(trailerName)}: ${piIdentity}`
|
|
154
177
|
: `${escapeForDoubleQuote(trailerName)}: ${piIdentity}`;
|
|
@@ -268,12 +291,17 @@ async function checkGitProtection(command: string, event: any, ctx: any, gitCwd:
|
|
|
268
291
|
const trailerResult = await handleCommitTrailer(command, event, ctx, gitCwd);
|
|
269
292
|
if (trailerResult) return trailerResult;
|
|
270
293
|
|
|
271
|
-
// DCO enforcement — inject --signoff
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
294
|
+
// DCO enforcement — inject --signoff into the REAL git commit invocation only.
|
|
295
|
+
// injectSignoff scopes to the actual invocation (after the last pipe for
|
|
296
|
+
// `echo ... | git commit -F -`, or the command-position-anchored `git commit`),
|
|
297
|
+
// so a `git commit` mention inside the echo/message body is never corrupted.
|
|
298
|
+
// The already-signed (--signoff/-s) guards live inside injectSignoff.
|
|
299
|
+
if (getSetting(gitCwd, "dco")) {
|
|
300
|
+
const signed = injectSignoff(event.input.command);
|
|
301
|
+
if (signed !== event.input.command) {
|
|
302
|
+
log.debug("DCO: injected --signoff into real git commit invocation");
|
|
303
|
+
event.input.command = signed;
|
|
304
|
+
}
|
|
277
305
|
}
|
|
278
306
|
}
|
|
279
307
|
|
|
@@ -361,7 +389,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
361
389
|
cachedTrailerSelection = null;
|
|
362
390
|
// Set comment signature env var for CLI tools to read
|
|
363
391
|
if (getSetting(ctx.cwd, "comment_signature")) {
|
|
364
|
-
const modelId = (process.env.PI_MODEL || (ctx as any).model?.id || "unknown").replace(/[^a-zA-Z0-9._:@/ -]/g, "").trim() || "unknown";
|
|
392
|
+
const modelId = (process.env.PI_PRIMARY_MODEL || process.env.PI_MODEL || (ctx as any).model?.id || "unknown").replace(/[^a-zA-Z0-9._:@/ -]/g, "").trim() || "unknown";
|
|
365
393
|
process.env.PI_COMMENT_SIGNATURE = `Assisted-by: PI (${modelId})`;
|
|
366
394
|
} else {
|
|
367
395
|
delete process.env.PI_COMMENT_SIGNATURE;
|
|
@@ -383,6 +411,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
383
411
|
const matches = matchToolCall(blockEntries, toolName, input);
|
|
384
412
|
if (matches.length > 0) {
|
|
385
413
|
const rule = matches[0].rule;
|
|
414
|
+
log.info("enforcement_block", toolName, rule.text);
|
|
386
415
|
return { block: true, reason: `⛔ ENFORCEMENT [${rule.entry.class}]: ${rule.text}` };
|
|
387
416
|
}
|
|
388
417
|
}
|
|
@@ -395,7 +424,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
395
424
|
const toolUsed = isToolCallEventType("edit", event) ? "edit" : "write";
|
|
396
425
|
return {
|
|
397
426
|
block: true,
|
|
398
|
-
reason: `⛔ Orchestrator cannot use ${toolUsed} directly. Delegate to
|
|
427
|
+
reason: `⛔ Orchestrator cannot use ${toolUsed} directly. Delegate to the appropriate specialist subagent via the subagent tool.`,
|
|
399
428
|
};
|
|
400
429
|
}
|
|
401
430
|
}
|
|
@@ -406,7 +435,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
406
435
|
if (filePath && resolve(ctx.cwd, filePath) === statePath(ctx.cwd)) {
|
|
407
436
|
return {
|
|
408
437
|
block: true,
|
|
409
|
-
reason: "\u26d4 Direct modification of pi-config-review-state.
|
|
438
|
+
reason: "\u26d4 Direct modification of pi-config-review-state.jsonl blocked. The review state is managed by the enforcement system.",
|
|
410
439
|
};
|
|
411
440
|
}
|
|
412
441
|
}
|
|
@@ -421,7 +450,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
421
450
|
if (getSetting(ctx.cwd, "review_loop_enforcement") && cmdLower.includes("pi-config-review-state")) {
|
|
422
451
|
return {
|
|
423
452
|
block: true,
|
|
424
|
-
reason: "\u26d4 Bash command targeting pi-config-review-state blocked. The review state is managed by the enforcement system.",
|
|
453
|
+
reason: "\u26d4 Bash command targeting pi-config-review-state blocked. The review state is managed exclusively by the enforcement system.",
|
|
425
454
|
};
|
|
426
455
|
}
|
|
427
456
|
|
|
@@ -486,14 +515,14 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
486
515
|
// On acpx, do not push async (unsupported); foreground waits are allowed.
|
|
487
516
|
const hasLoop = /\b(while|for|until)\b/.test(command);
|
|
488
517
|
const sleepMatch = command.match(/\bsleep\s+(\d+)/);
|
|
489
|
-
if (parentSupportsAsyncLlm && hasLoop && sleepMatch && parseInt(sleepMatch[1], 10) >
|
|
518
|
+
if (parentSupportsAsyncLlm && hasLoop && sleepMatch && parseInt(sleepMatch[1], 10) > getSetting(ctx.cwd, "enforcement_loop_sleep_threshold_s")) {
|
|
490
519
|
return {
|
|
491
520
|
block: true,
|
|
492
521
|
reason: `⚠️ Polling loop with sleep ${sleepMatch[1]}s blocked. Use subagent with async: true for polling/monitoring tasks instead of blocking the session.`,
|
|
493
522
|
};
|
|
494
523
|
}
|
|
495
524
|
|
|
496
|
-
if (parentSupportsAsyncLlm && !hasLoop && sleepMatch && parseInt(sleepMatch[1], 10) >
|
|
525
|
+
if (parentSupportsAsyncLlm && !hasLoop && sleepMatch && parseInt(sleepMatch[1], 10) > getSetting(ctx.cwd, "enforcement_sleep_threshold_s")) {
|
|
497
526
|
return {
|
|
498
527
|
block: true,
|
|
499
528
|
reason: `⚠️ sleep ${sleepMatch[1]}s blocked — too long. Use subagent with async: true instead of blocking the session.`,
|
|
@@ -503,9 +532,28 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
503
532
|
const remoteCheck = checkRemoteExecBlock(cmdLower);
|
|
504
533
|
if (remoteCheck) return remoteCheck;
|
|
505
534
|
|
|
506
|
-
//
|
|
535
|
+
// comment_signature: inject Assisted-by footer into gh pr/issue create|comment|edit bodies
|
|
536
|
+
if (getSetting(ctx.cwd, "comment_signature")) {
|
|
537
|
+
const modelId = (
|
|
538
|
+
process.env.PI_PRIMARY_MODEL ||
|
|
539
|
+
process.env.PI_MODEL ||
|
|
540
|
+
(ctx as any).model?.id ||
|
|
541
|
+
"unknown"
|
|
542
|
+
).replace(/[^a-zA-Z0-9._:@/ -]/g, "").trim() || "unknown";
|
|
543
|
+
const signature = process.env.PI_COMMENT_SIGNATURE || `Assisted-by: PI (${modelId})`;
|
|
544
|
+
const injected = injectGhBodySignature(event.input.command, signature);
|
|
545
|
+
if (injected !== event.input.command) {
|
|
546
|
+
log.info("comment_signature injected into gh body command", signature);
|
|
547
|
+
event.input.command = injected;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Enforce git commit/push only via git-expert agent.
|
|
552
|
+
// Use isRealGitCommitOrPush (heredoc-stripped + command-position aware) so that
|
|
553
|
+
// file CONTENT / string args / paths that merely MENTION "git commit" do not trip
|
|
554
|
+
// the guard (BUG #3). Only genuine git commit/push invocations are blocked.
|
|
507
555
|
if (process.env.PI_AGENT_NAME !== "git-expert") {
|
|
508
|
-
if (
|
|
556
|
+
if (isRealGitCommitOrPush(command)) {
|
|
509
557
|
return {
|
|
510
558
|
block: true,
|
|
511
559
|
reason: "⛔ git commit/push blocked. Use git-expert agent for commit and push operations.",
|
|
@@ -517,7 +565,9 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
517
565
|
// Use resolveEffectiveCwd to detect the worktree from cd commands (e.g., cd .worktrees/issue-622 && git commit)
|
|
518
566
|
if (process.env.PI_AGENT_NAME === "git-expert" && hasGitSub(command, "commit")) {
|
|
519
567
|
const commitCwd = resolveEffectiveCwd(command, ctx.cwd);
|
|
520
|
-
|
|
568
|
+
// Skip review enforcement on version bump branches (release workflow only)
|
|
569
|
+
const commitBranch = getCurrentBranch(commitCwd);
|
|
570
|
+
if (!isBumpVersionBranch(commitBranch) && getSetting(commitCwd, "review_loop_enforcement") && !isCommitAllowed(commitCwd)) {
|
|
521
571
|
const state = readReviewState(commitCwd);
|
|
522
572
|
const testInfo = state.status === "clean" && !state.tests_passed ? " Tests have not passed yet — run tests before committing." : "";
|
|
523
573
|
const reviewAdvice = state.status !== "clean" ? " Fix findings and re-run all reviewers until 0 comments." : "";
|
|
@@ -555,9 +605,10 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
555
605
|
try {
|
|
556
606
|
entries = loadEnforcedEntries(ctx.cwd);
|
|
557
607
|
} catch (e: any) {
|
|
558
|
-
|
|
608
|
+
log.debug("enforcement: loadEnforcedEntries failed:", e?.message?.slice(0, 100));
|
|
559
609
|
return;
|
|
560
610
|
}
|
|
611
|
+
log.info("enforcement_loaded", entries.length);
|
|
561
612
|
if (entries.length === 0) return;
|
|
562
613
|
|
|
563
614
|
|
|
@@ -570,7 +621,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
570
621
|
const details = (event as any).details;
|
|
571
622
|
const results = Array.isArray(details?.results) ? details.results : [];
|
|
572
623
|
if (!Array.isArray(details?.results) && details?.results) {
|
|
573
|
-
|
|
624
|
+
log.debug(`enforcement: subagent details.results is ${typeof details.results} (expected array), skipping bash extraction`);
|
|
574
625
|
}
|
|
575
626
|
const nonBlockEntries = entries.filter(e => e.action !== "block");
|
|
576
627
|
const seen = new Set<string>();
|
|
@@ -649,6 +700,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
649
700
|
}
|
|
650
701
|
|
|
651
702
|
if (rule.action === "warn") {
|
|
703
|
+
log.warn("enforcement_warn", toolName, rule.text);
|
|
652
704
|
appendMessages.push({
|
|
653
705
|
type: "text",
|
|
654
706
|
text: `\n⚠️ ENFORCEMENT WARNING: ${rule.text}`,
|
|
@@ -690,6 +742,8 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
690
742
|
} catch {
|
|
691
743
|
effectiveCwd = ctx.cwd; // fallback to session cwd
|
|
692
744
|
}
|
|
745
|
+
// Skip review tracking on version bump branches (release workflow only)
|
|
746
|
+
if (isBumpVersionBranch(getCachedBranch(effectiveCwd))) return;
|
|
693
747
|
const relativePath = path.relative(effectiveCwd, absPath);
|
|
694
748
|
|
|
695
749
|
// Skip gitignored files (build artifacts, .pi/tmp/, node_modules, etc.)
|
|
@@ -708,7 +762,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
708
762
|
|
|
709
763
|
// Retry markNeedsReview on lock failure — missing this would leave state
|
|
710
764
|
// as 'clean' and allow commits to slip through enforcement.
|
|
711
|
-
// Uses effectiveCwd so each worktree gets its own pi-config-review-state.
|
|
765
|
+
// Uses effectiveCwd so each worktree gets its own pi-config-review-state.jsonl.
|
|
712
766
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
713
767
|
try { markNeedsReview(effectiveCwd); break; } catch {
|
|
714
768
|
if (attempt === 2) {
|
|
@@ -721,7 +775,7 @@ export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): vo
|
|
|
721
775
|
|
|
722
776
|
// ── Test command detection (auto-mark tests_passed in review state) ──
|
|
723
777
|
// Runs in ALL processes (orchestrator + subagents) to catch test runs from any agent.
|
|
724
|
-
// When a test command exits successfully, marks tests_passed=true in pi-config-review-state.
|
|
778
|
+
// When a test command exits successfully, marks tests_passed=true in pi-config-review-state.jsonl.
|
|
725
779
|
// When it fails, marks tests_passed=false. Any subsequent file edit resets tests_passed
|
|
726
780
|
// via markNeedsReview(), so stale results are impossible.
|
|
727
781
|
// NOTE: Code reviewers are excluded — they may run tests to verify behavior but their
|
|
@@ -316,6 +316,12 @@ function registerCompletions(
|
|
|
316
316
|
}
|
|
317
317
|
return null;
|
|
318
318
|
},
|
|
319
|
+
"pi-config-settings": (prefix: string) => {
|
|
320
|
+
return filter([
|
|
321
|
+
{ value: "project", label: "project", description: "Edit project-level settings" },
|
|
322
|
+
{ value: "global", label: "global", description: "Edit global settings" },
|
|
323
|
+
], prefix);
|
|
324
|
+
},
|
|
319
325
|
|
|
320
326
|
};
|
|
321
327
|
|
|
@@ -348,6 +354,7 @@ function setupPromptTemplateInterceptor(
|
|
|
348
354
|
const promptTemplateCommands = new Set([
|
|
349
355
|
"external-ai", "pr-review", "issue-review", "coderabbit-rate-limit",
|
|
350
356
|
"review-local", "release", "review-handler", "cron", "create-skill", "create-coms-feature-manager",
|
|
357
|
+
"pi-config-settings",
|
|
351
358
|
]);
|
|
352
359
|
|
|
353
360
|
// /external-ai-models-refresh command — clears cache and re-fetches
|
|
@@ -20,9 +20,9 @@ export function runGit(
|
|
|
20
20
|
GCM_INTERACTIVE: "Never",
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
-
return { stdout: stdout.
|
|
23
|
+
return { stdout: stdout.trimEnd(), code: 0 };
|
|
24
24
|
} catch (e: any) {
|
|
25
|
-
return { stdout: (e.stdout || "").
|
|
25
|
+
return { stdout: (e.stdout || "").trimEnd(), code: e.status || 1 };
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -34,24 +34,34 @@ import { registerPreferenceExtractor } from "./preference-extractor.js";
|
|
|
34
34
|
import { registerMemoryTools } from "./memory-tools.js";
|
|
35
35
|
import { registerReviewUI } from "./review-ui.js";
|
|
36
36
|
import { registerSessionSearch } from "./session-search.js";
|
|
37
|
+
import { registerSettingsTui } from "./settings-tui.js";
|
|
38
|
+
import { registerMarkdownTransformer } from "./markdown-transformer.js";
|
|
37
39
|
import { ensureGitSshTimeout, isRunningInContainer, terminalNotify } from "./utils.js";
|
|
40
|
+
import { createLogger } from "../shared/logger.js";
|
|
41
|
+
import { setGlobalSessionId } from "../shared/file-logger.js";
|
|
38
42
|
|
|
39
43
|
const IN_CONTAINER = isRunningInContainer();
|
|
40
44
|
ensureGitSshTimeout();
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
export const commandHandlerRegistry = new Map<string, (args: string, ctx: any) => Promise<void>>();
|
|
44
|
-
|
|
45
|
-
// Latest ExtensionCommandContext captured from any command handler.
|
|
46
|
-
// Commands provide the full context (switchSession, newSession, etc.)
|
|
47
|
-
// while session_start only provides basic ExtensionContext.
|
|
48
|
-
export let latestCommandCtx: any = null;
|
|
46
|
+
const log = createLogger("orchestrator");
|
|
49
47
|
|
|
50
48
|
export default function (pi: ExtensionAPI) {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// Set global session ID FIRST so all fileLog callers get per-session files.
|
|
50
|
+
pi.on("session_start", (_event, ctx) => {
|
|
51
|
+
const reason = (_event as any)?.reason ?? "unknown";
|
|
52
|
+
const sid = ctx.sessionManager?.getSessionId?.();
|
|
53
|
+
log.info("session_start", reason, sid ?? "?");
|
|
54
|
+
if (sid) setGlobalSessionId(sid);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ── Pidash command handler bridge ──────────────────────────────────
|
|
58
|
+
// TODO: Remove once pidash uses PiClient/RemoteSession (#732) — native
|
|
59
|
+
// command dispatch via RemoteSession.submit() eliminates the need for
|
|
60
|
+
// this event-based handler forwarding.
|
|
54
61
|
const originalRegisterCommand = pi.registerCommand.bind(pi);
|
|
62
|
+
let latestCommandCtx: any = null;
|
|
63
|
+
const commandHandlerRegistry = new Map<string, Function>();
|
|
64
|
+
|
|
55
65
|
pi.registerCommand = (name: string, options: any) => {
|
|
56
66
|
if (options?.handler) {
|
|
57
67
|
const origHandler = options.handler;
|
|
@@ -69,6 +79,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
79
|
|
|
70
80
|
// Replay all registered command handlers when pidash requests them
|
|
71
81
|
// (handles extension load order — pidash may load after orchestrator)
|
|
82
|
+
// TODO: Remove once pidash uses PiClient/RemoteSession (#732)
|
|
72
83
|
pi.events.on("pidash:request-commands", () => {
|
|
73
84
|
for (const [name, handler] of commandHandlerRegistry) {
|
|
74
85
|
pi.events.emit("pidash:register-command", { name, handler });
|
|
@@ -98,6 +109,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
98
109
|
registerPreferenceExtractor(pi);
|
|
99
110
|
registerMemoryTools(pi);
|
|
100
111
|
registerSessionSearch(pi);
|
|
112
|
+
registerSettingsTui(pi);
|
|
113
|
+
registerMarkdownTransformer(pi);
|
|
101
114
|
|
|
102
115
|
// ── list_models tool — LLM-callable model discovery ──
|
|
103
116
|
pi.registerTool({
|