pi-orchestrator-config 4.1.5
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 +808 -0
- package/agents/api-documenter.md +32 -0
- package/agents/bash-expert.md +53 -0
- package/agents/code-reviewer-docs.md +145 -0
- package/agents/code-reviewer-guidelines.md +97 -0
- package/agents/code-reviewer-quality.md +142 -0
- package/agents/code-reviewer-security.md +107 -0
- package/agents/code-reviewer-spec.md +164 -0
- package/agents/debugger.md +63 -0
- package/agents/docker-expert.md +40 -0
- package/agents/docs-fetcher.md +48 -0
- package/agents/git-expert.md +63 -0
- package/agents/github-expert.md +83 -0
- package/agents/go-expert.md +62 -0
- package/agents/issue-reviewer-feasibility.md +60 -0
- package/agents/issue-reviewer-scope.md +63 -0
- package/agents/issue-reviewer-spec.md +77 -0
- package/agents/java-expert.md +31 -0
- package/agents/jenkins-expert.md +38 -0
- package/agents/kubernetes-expert.md +40 -0
- package/agents/planner.md +42 -0
- package/agents/python-expert.md +61 -0
- package/agents/reviewer.md +26 -0
- package/agents/scout.md +40 -0
- package/agents/security-auditor.md +254 -0
- package/agents/technical-documentation-writer.md +37 -0
- package/agents/test-automator.md +55 -0
- package/agents/test-runner.md +47 -0
- package/agents/ts-expert.md +23 -0
- package/agents/worker.md +30 -0
- package/extensions/acpx-provider/index.ts +104 -0
- package/extensions/acpx-provider/load-runtime.ts +99 -0
- package/extensions/acpx-provider/runtime-models.ts +40 -0
- package/extensions/cli-provider/agents/claude.ts +128 -0
- package/extensions/cli-provider/agents/cursor.ts +84 -0
- package/extensions/cli-provider/agents/gemini.ts +100 -0
- package/extensions/cli-provider/discover.ts +77 -0
- package/extensions/cli-provider/index.ts +57 -0
- package/extensions/cli-provider/parsers.ts +304 -0
- package/extensions/cli-provider/providers.ts +58 -0
- package/extensions/cli-provider/runner.ts +225 -0
- package/extensions/cli-provider/runtime-models.ts +32 -0
- package/extensions/cli-provider/session-reaper.ts +122 -0
- package/extensions/cli-provider/sessions.ts +533 -0
- package/extensions/cli-provider/shared/discover-cache.ts +235 -0
- package/extensions/cli-provider/types.ts +48 -0
- package/extensions/coms/coms-net-server.ts +1642 -0
- package/extensions/coms/coms-net-wrapper.ts +521 -0
- package/extensions/coms/coms-net.ts +1589 -0
- package/extensions/coms/coms-p2p.ts +1879 -0
- package/extensions/coms/coms-shared.ts +510 -0
- package/extensions/coms/coms-wrapper.ts +138 -0
- package/extensions/coms/index.ts +20 -0
- package/extensions/coms/themeMap.ts +147 -0
- package/extensions/image-gen/image-gen.ts +332 -0
- package/extensions/image-gen/index.ts +14 -0
- package/extensions/orchestrator/agents.ts +134 -0
- package/extensions/orchestrator/ask-user.ts +293 -0
- package/extensions/orchestrator/async-agents.ts +1037 -0
- package/extensions/orchestrator/async-capability.ts +129 -0
- package/extensions/orchestrator/async-runner.ts +324 -0
- package/extensions/orchestrator/async-status-parse.ts +31 -0
- package/extensions/orchestrator/async-status-ui.ts +201 -0
- package/extensions/orchestrator/async-wait.ts +36 -0
- package/extensions/orchestrator/btw.ts +210 -0
- package/extensions/orchestrator/cron-status-format.ts +126 -0
- package/extensions/orchestrator/cron-status-ui.ts +132 -0
- package/extensions/orchestrator/cron.ts +730 -0
- package/extensions/orchestrator/dreaming.ts +355 -0
- package/extensions/orchestrator/enforcement-helpers.ts +530 -0
- package/extensions/orchestrator/enforcement-rules.ts +329 -0
- package/extensions/orchestrator/enforcement.ts +828 -0
- package/extensions/orchestrator/extended-autocomplete.ts +593 -0
- package/extensions/orchestrator/git-helpers.ts +420 -0
- package/extensions/orchestrator/github-autocomplete.ts +250 -0
- package/extensions/orchestrator/icons.ts +17 -0
- package/extensions/orchestrator/index.ts +100 -0
- package/extensions/orchestrator/memory-embeddings.ts +281 -0
- package/extensions/orchestrator/memory-promotion.ts +449 -0
- package/extensions/orchestrator/memory-provenance.ts +80 -0
- package/extensions/orchestrator/memory-query-class.ts +90 -0
- package/extensions/orchestrator/memory-scoring.ts +434 -0
- package/extensions/orchestrator/memory-tools.ts +883 -0
- package/extensions/orchestrator/memory-tree.ts +212 -0
- package/extensions/orchestrator/nvim.ts +169 -0
- package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
- package/extensions/orchestrator/overlay-dashboard.ts +512 -0
- package/extensions/orchestrator/pi-config-review-state.ts +303 -0
- package/extensions/orchestrator/preference-extractor.ts +106 -0
- package/extensions/orchestrator/project-settings.ts +468 -0
- package/extensions/orchestrator/promotion-queue.ts +311 -0
- package/extensions/orchestrator/resolve-agent-model.ts +32 -0
- package/extensions/orchestrator/review-ui.ts +204 -0
- package/extensions/orchestrator/rule-placeholders.ts +12 -0
- package/extensions/orchestrator/rules.ts +481 -0
- package/extensions/orchestrator/session-search.ts +243 -0
- package/extensions/orchestrator/session-validation.ts +374 -0
- package/extensions/orchestrator/situation-report.ts +370 -0
- package/extensions/orchestrator/status-bar.ts +31 -0
- package/extensions/orchestrator/status-line.ts +192 -0
- package/extensions/orchestrator/status.ts +110 -0
- package/extensions/orchestrator/subagent-tool.ts +1748 -0
- package/extensions/orchestrator/task-lifecycle.ts +88 -0
- package/extensions/orchestrator/utils.ts +227 -0
- package/extensions/pidash/index.ts +15 -0
- package/extensions/pidash/pidash.ts +941 -0
- package/extensions/pidiff/index.ts +13 -0
- package/extensions/pidiff/pidiff.ts +369 -0
- package/extensions/providers/acpx-driver.ts +479 -0
- package/extensions/providers/built-in-drivers.ts +52 -0
- package/extensions/providers/claude-driver.ts +319 -0
- package/extensions/providers/cursor-acpx-driver.ts +398 -0
- package/extensions/providers/cursor-cli-driver.ts +484 -0
- package/extensions/providers/gemini-driver.ts +302 -0
- package/extensions/providers/index.ts +513 -0
- package/extensions/shared/build-system-prompt.ts +57 -0
- package/extensions/shared/create-runtime-provider.ts +190 -0
- package/extensions/shared/daemon-manager.ts +248 -0
- package/extensions/shared/file-logger.ts +115 -0
- package/extensions/shared/managed-refresh.ts +211 -0
- package/extensions/shared/provider-driver.ts +295 -0
- package/extensions/shared/provider-errors.ts +305 -0
- package/extensions/shared/provider-registry.ts +388 -0
- package/extensions/shared/stream-builder.ts +275 -0
- package/extensions/shared/types.ts +20 -0
- package/extensions/shared/ui/README.md +10 -0
- package/extensions/shared/ui/badge.tsx +52 -0
- package/extensions/shared/ui/button.tsx +58 -0
- package/extensions/shared/ui/collapsible.tsx +21 -0
- package/extensions/shared/ui/dropdown-menu.tsx +266 -0
- package/extensions/shared/ui/input.tsx +20 -0
- package/extensions/shared/ui/popover.tsx +114 -0
- package/extensions/shared/ui/resizable.tsx +48 -0
- package/extensions/shared/ui/scroll-area.tsx +53 -0
- package/extensions/shared/ui/select.tsx +201 -0
- package/extensions/shared/ui/separator.tsx +25 -0
- package/extensions/shared/ui/tooltip.tsx +64 -0
- package/extensions/shared/ws-client.ts +74 -0
- package/package.json +59 -0
- package/prompts/coderabbit-rate-limit.md +95 -0
- package/prompts/create-coms-feature-manager.md +186 -0
- package/prompts/create-skill.md +128 -0
- package/prompts/domain-model.md +109 -0
- package/prompts/external-ai.md +665 -0
- package/prompts/implement-and-review.md +25 -0
- package/prompts/implement.md +26 -0
- package/prompts/issue-review.md +256 -0
- package/prompts/pr-review.md +753 -0
- package/prompts/query-db.md +154 -0
- package/prompts/refine-review.md +208 -0
- package/prompts/release.md +235 -0
- package/prompts/remember.md +33 -0
- package/prompts/review-handler-status.md +27 -0
- package/prompts/review-handler.md +677 -0
- package/prompts/review-local.md +159 -0
- package/prompts/scout-and-plan.md +21 -0
- package/rules/00-orchestrator-core.md +42 -0
- package/rules/05-issue-first-workflow.md +119 -0
- package/rules/10-agent-routing.md +58 -0
- package/rules/15-mcp-launchpad.md +46 -0
- package/rules/20-code-review-loop.md +234 -0
- package/rules/25-documentation-updates.md +15 -0
- package/rules/30-prompt-templates.md +19 -0
- package/rules/35-memory.md +240 -0
- package/rules/40-critical-rules.md +124 -0
- package/rules/45-file-preview.md +29 -0
- package/rules/50-agent-bug-reporting.md +90 -0
- package/rules/55-coms-protocol.md +63 -0
- package/rules/60-task-tracking.md +66 -0
- package/skills/conflict-resolver/SKILL.md +213 -0
- package/skills/skill-creator/SKILL.md +85 -0
- package/skills/systematic-debugging/SKILL.md +353 -0
- package/skills/test-driven-development/SKILL.md +335 -0
- package/skills/update-agents-md/SKILL.md +282 -0
- package/templates/coms-feature-manager-prompt.md +313 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Provider Extension for pi
|
|
3
|
+
*
|
|
4
|
+
* Single extension that replaces both cli-provider/index.ts and
|
|
5
|
+
* acpx-provider/index.ts. Uses the driver-based architecture inspired
|
|
6
|
+
* by t3code's ProviderDriver SPI.
|
|
7
|
+
*
|
|
8
|
+
* Registers via createProvider() (pi >= 0.81): /login, fetchModels,
|
|
9
|
+
* filterModels, and native ProviderStreams.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
* 1. Read cli_agents and acpx_agents from settings
|
|
13
|
+
* 2. For each agent, find the matching driver from BUILT_IN_DRIVERS
|
|
14
|
+
* 3. Use ProviderDriverRegistry to probe + create instances
|
|
15
|
+
* 4. Register each instance as a pi provider via createRuntimeProvider
|
|
16
|
+
* 5. Unified session_start/shutdown/before_agent_start handlers
|
|
17
|
+
*
|
|
18
|
+
* Loaded from: extensions/providers/
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
AssistantMessage,
|
|
23
|
+
AssistantMessageEventStream,
|
|
24
|
+
Context,
|
|
25
|
+
Model,
|
|
26
|
+
SimpleStreamOptions,
|
|
27
|
+
StreamOptions,
|
|
28
|
+
} from "@earendil-works/pi-ai";
|
|
29
|
+
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
30
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
31
|
+
import { asStringArray, getSetting } from "../orchestrator/project-settings.js";
|
|
32
|
+
import { buildExternalSystemPrompt } from "../shared/build-system-prompt.js";
|
|
33
|
+
import {
|
|
34
|
+
checkMinPiVersion,
|
|
35
|
+
isPiMetaInvocation,
|
|
36
|
+
} from "../orchestrator/utils.js";
|
|
37
|
+
import {
|
|
38
|
+
buildAmbientLoginAuth,
|
|
39
|
+
buildRuntimeModel,
|
|
40
|
+
createRuntimeProvider,
|
|
41
|
+
filterModelsWhenConfigured,
|
|
42
|
+
} from "../shared/create-runtime-provider.js";
|
|
43
|
+
import { StreamAssembler, createAssistantMessageOutput } from "../shared/stream-builder.js";
|
|
44
|
+
import { ProviderDriverRegistry } from "../shared/provider-registry.js";
|
|
45
|
+
import type { ProviderInstance, DiscoveredModel } from "../shared/provider-driver.js";
|
|
46
|
+
import { BUILT_IN_DRIVERS, CLI_AGENT_TO_DRIVER, ACPX_AGENT_TO_DRIVER } from "./built-in-drivers.js";
|
|
47
|
+
import { mapCliDiscoveredModels } from "../cli-provider/runtime-models.js";
|
|
48
|
+
import { mapAcpxDiscoveredModels } from "../acpx-provider/runtime-models.js";
|
|
49
|
+
import {
|
|
50
|
+
startCliSessionReaper,
|
|
51
|
+
stopCliSessionReaper,
|
|
52
|
+
} from "../cli-provider/session-reaper.js";
|
|
53
|
+
import {
|
|
54
|
+
readPiSessionIdFromManager,
|
|
55
|
+
} from "../cli-provider/sessions.js";
|
|
56
|
+
import { fileLog } from "../shared/file-logger.js";
|
|
57
|
+
import { isCliAgentName } from "../cli-provider/providers.js";
|
|
58
|
+
|
|
59
|
+
const LOG_DOMAIN = "providers";
|
|
60
|
+
const DISCOVERY_TIMEOUT_MS = 30_000;
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// State
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
const registry = new ProviderDriverRegistry();
|
|
67
|
+
|
|
68
|
+
/** Track which instances are CLI vs ACPX for configured gates. */
|
|
69
|
+
const cliInstances = new Map<string, ProviderInstance>();
|
|
70
|
+
const acpxInstances = new Map<string, ProviderInstance>();
|
|
71
|
+
|
|
72
|
+
let projectCwd = "";
|
|
73
|
+
let initialized = false;
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Configured Gates
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
function isCliInstanceConfigured(agent: string): boolean {
|
|
80
|
+
return cliInstances.has(agent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isAcpxInstanceConfigured(agent: string): boolean {
|
|
84
|
+
return acpxInstances.has(agent);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Context Helpers
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
function messageText(msg: { role: string; content: unknown }): string {
|
|
92
|
+
if (typeof msg.content === "string") return msg.content;
|
|
93
|
+
if (!Array.isArray(msg.content)) return "";
|
|
94
|
+
const textParts: string[] = [];
|
|
95
|
+
for (const block of msg.content) {
|
|
96
|
+
if ("text" in block && typeof block.text === "string") {
|
|
97
|
+
textParts.push(block.text);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return textParts.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function extractLatestUserMessage(context: Context): string {
|
|
104
|
+
for (let i = context.messages.length - 1; i >= 0; i--) {
|
|
105
|
+
const msg = context.messages[i];
|
|
106
|
+
if (msg.role === "user") {
|
|
107
|
+
const text = messageText(msg);
|
|
108
|
+
if (text) return text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return "hello";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Stream Factory
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
function makeStreamFunction(
|
|
119
|
+
kind: "cli" | "acpx",
|
|
120
|
+
agent: string,
|
|
121
|
+
getInstance: () => ProviderInstance | undefined,
|
|
122
|
+
) {
|
|
123
|
+
return function streamProvider(
|
|
124
|
+
model: Model<any>,
|
|
125
|
+
context: Context,
|
|
126
|
+
options?: SimpleStreamOptions | StreamOptions,
|
|
127
|
+
): AssistantMessageEventStream {
|
|
128
|
+
const stream = createAssistantMessageEventStream();
|
|
129
|
+
const output = createAssistantMessageOutput(model);
|
|
130
|
+
const assembler = new StreamAssembler(output, stream);
|
|
131
|
+
|
|
132
|
+
(async () => {
|
|
133
|
+
try {
|
|
134
|
+
stream.push({ type: "start", partial: output });
|
|
135
|
+
|
|
136
|
+
const instance = getInstance();
|
|
137
|
+
if (!instance) {
|
|
138
|
+
throw new Error(`${kind}-${agent}: driver instance not available`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Parse model from pi model id: "agent:modelId"
|
|
142
|
+
const colonIdx = model.id.indexOf(":");
|
|
143
|
+
const driverModelId = colonIdx >= 0 ? model.id.substring(colonIdx + 1) : "default";
|
|
144
|
+
|
|
145
|
+
// Build system prompt
|
|
146
|
+
const systemPrompt = buildExternalSystemPrompt(context, projectCwd);
|
|
147
|
+
|
|
148
|
+
// Ensure session
|
|
149
|
+
const handle = await instance.adapter.startSession({
|
|
150
|
+
model: driverModelId,
|
|
151
|
+
systemPrompt,
|
|
152
|
+
cwd: projectCwd,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Extract latest user message
|
|
156
|
+
const prompt = extractLatestUserMessage(context);
|
|
157
|
+
|
|
158
|
+
// Send turn with event bridging
|
|
159
|
+
const result = await instance.adapter.sendTurn(handle, prompt, {
|
|
160
|
+
signal: options?.signal,
|
|
161
|
+
context: { messages: context.messages },
|
|
162
|
+
onEvent: (event) => assembler.handleEvent(event),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
assembler.finalize({
|
|
166
|
+
finalText: result.text,
|
|
167
|
+
finalThinking: result.thinking,
|
|
168
|
+
stopReason: result.stopReason,
|
|
169
|
+
usage: result.usage,
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
assembler.emitError(error, options?.signal?.aborted);
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
|
|
176
|
+
return stream;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Registration Helpers
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
async function registerCliAgent(
|
|
185
|
+
pi: ExtensionAPI,
|
|
186
|
+
agent: string,
|
|
187
|
+
instance: ProviderInstance,
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const providerId = `cli-${agent}`;
|
|
190
|
+
const snapshot = instance.snapshot.getSnapshot();
|
|
191
|
+
const discovered = snapshot.models;
|
|
192
|
+
|
|
193
|
+
const models = mapCliDiscoveredModels(agent, discovered);
|
|
194
|
+
const streamFn = makeStreamFunction("cli", agent, () => cliInstances.get(agent));
|
|
195
|
+
|
|
196
|
+
const provider = await createRuntimeProvider({
|
|
197
|
+
id: providerId,
|
|
198
|
+
name: `CLI ${agent}`,
|
|
199
|
+
auth: {
|
|
200
|
+
apiKey: buildAmbientLoginAuth({
|
|
201
|
+
displayName: `CLI ${agent}`,
|
|
202
|
+
isConfigured: () => isCliInstanceConfigured(agent),
|
|
203
|
+
sourceLabel: `${agent} CLI on PATH`,
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
models,
|
|
207
|
+
fetchModels: async () => {
|
|
208
|
+
if (!isCliInstanceConfigured(agent)) return [];
|
|
209
|
+
const inst = cliInstances.get(agent);
|
|
210
|
+
if (!inst) return [];
|
|
211
|
+
const snap = await inst.snapshot.refresh();
|
|
212
|
+
return mapCliDiscoveredModels(agent, snap.models);
|
|
213
|
+
},
|
|
214
|
+
filterModels: (catalog, credential) =>
|
|
215
|
+
filterModelsWhenConfigured(catalog, credential, () =>
|
|
216
|
+
isCliInstanceConfigured(agent),
|
|
217
|
+
),
|
|
218
|
+
api: { stream: streamFn, streamSimple: streamFn },
|
|
219
|
+
});
|
|
220
|
+
pi.registerProvider(provider);
|
|
221
|
+
fileLog(LOG_DOMAIN, "info", LOG_DOMAIN,
|
|
222
|
+
`cli-${agent}: ${models.length} model(s) registered`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function registerAcpxAgent(
|
|
226
|
+
pi: ExtensionAPI,
|
|
227
|
+
agent: string,
|
|
228
|
+
instance: ProviderInstance,
|
|
229
|
+
): Promise<void> {
|
|
230
|
+
const providerId = `acpx-${agent}`;
|
|
231
|
+
const snapshot = instance.snapshot.getSnapshot();
|
|
232
|
+
const modelIds = snapshot.models.map((m) => m.id);
|
|
233
|
+
|
|
234
|
+
const models = mapAcpxDiscoveredModels(agent, modelIds);
|
|
235
|
+
const streamFn = makeStreamFunction("acpx", agent, () => acpxInstances.get(agent));
|
|
236
|
+
|
|
237
|
+
const provider = await createRuntimeProvider({
|
|
238
|
+
id: providerId,
|
|
239
|
+
name: `ACPX ${agent}`,
|
|
240
|
+
auth: {
|
|
241
|
+
apiKey: buildAmbientLoginAuth({
|
|
242
|
+
displayName: `ACPX ${agent}`,
|
|
243
|
+
isConfigured: () => isAcpxInstanceConfigured(agent),
|
|
244
|
+
sourceLabel: `${agent} acpx runtime`,
|
|
245
|
+
}),
|
|
246
|
+
},
|
|
247
|
+
models,
|
|
248
|
+
fetchModels: async () => {
|
|
249
|
+
const inst = acpxInstances.get(agent);
|
|
250
|
+
if (!inst) return [];
|
|
251
|
+
const snap = await inst.snapshot.refresh();
|
|
252
|
+
return mapAcpxDiscoveredModels(agent, snap.models.map((m) => m.id));
|
|
253
|
+
},
|
|
254
|
+
filterModels: (catalog, credential) =>
|
|
255
|
+
filterModelsWhenConfigured(catalog, credential, () =>
|
|
256
|
+
isAcpxInstanceConfigured(agent),
|
|
257
|
+
),
|
|
258
|
+
api: { stream: streamFn, streamSimple: streamFn },
|
|
259
|
+
});
|
|
260
|
+
pi.registerProvider(provider);
|
|
261
|
+
fileLog(LOG_DOMAIN, "info", LOG_DOMAIN,
|
|
262
|
+
`acpx-${agent}: ${models.length} model(s) registered`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// Extension Entry Point
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
export default async function (pi: ExtensionAPI) {
|
|
270
|
+
// pi --help / --version — skip discovery
|
|
271
|
+
if (isPiMetaInvocation()) return;
|
|
272
|
+
|
|
273
|
+
// Idempotency guard — shims re-export this default, so it may be called
|
|
274
|
+
// multiple times (cli-provider shim, acpx-provider shim, providers dir).
|
|
275
|
+
// Only run once per process.
|
|
276
|
+
if (initialized) return;
|
|
277
|
+
initialized = true;
|
|
278
|
+
|
|
279
|
+
const versionCheck = checkMinPiVersion();
|
|
280
|
+
if (!versionCheck.ok && versionCheck.installed !== null) {
|
|
281
|
+
fileLog(LOG_DOMAIN, "error", LOG_DOMAIN,
|
|
282
|
+
`pi ${versionCheck.installed} below minimum ${versionCheck.required}; skipping`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
projectCwd = process.cwd();
|
|
287
|
+
const isSubagent = process.env.PI_SUBAGENT_CHILD === "1";
|
|
288
|
+
|
|
289
|
+
// Register all built-in drivers with the registry
|
|
290
|
+
for (const driver of BUILT_IN_DRIVERS) {
|
|
291
|
+
registry.registerDriver(driver);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// CLI agents (load in subagent children too)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
const cliAgentList = asStringArray(getSetting(projectCwd, "cli_agents")).filter(isCliAgentName);
|
|
298
|
+
|
|
299
|
+
if (cliAgentList.length > 0) {
|
|
300
|
+
startCliSessionReaper({
|
|
301
|
+
cwd: projectCwd,
|
|
302
|
+
getActivePiSessionId: () => null, // Managed by driver adapters
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Bind pi session on before_agent_start (same as old cli-provider)
|
|
306
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
307
|
+
const { readPiSessionId: sid } = readPiSessionIdFromManager(ctx.sessionManager);
|
|
308
|
+
if (sid) {
|
|
309
|
+
for (const inst of cliInstances.values()) {
|
|
310
|
+
const adapter = inst.adapter as any;
|
|
311
|
+
if (typeof adapter.bindPiSessionId === "function") {
|
|
312
|
+
adapter.bindPiSessionId(sid);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Session lifecycle (same as old cli-provider)
|
|
319
|
+
pi.on("session_start", (event, ctx) => {
|
|
320
|
+
const reason = typeof (event as any)?.reason === "string"
|
|
321
|
+
? (event as any).reason : "";
|
|
322
|
+
const { readPiSessionId: sid } = readPiSessionIdFromManager(ctx.sessionManager);
|
|
323
|
+
|
|
324
|
+
for (const inst of cliInstances.values()) {
|
|
325
|
+
const adapter = inst.adapter as any;
|
|
326
|
+
if (typeof adapter.handleSessionStart === "function") {
|
|
327
|
+
adapter.handleSessionStart(reason, sid);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// Discovery — CLI and ACPX in parallel (on main these were separate
|
|
335
|
+
// extensions loaded concurrently by pi; keep the same parallelism)
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
async function discoverAndRegisterCli(): Promise<void> {
|
|
338
|
+
if (cliAgentList.length === 0) return;
|
|
339
|
+
|
|
340
|
+
const cliResults = await Promise.allSettled(
|
|
341
|
+
cliAgentList.map(async (agent) => {
|
|
342
|
+
const driverKind = CLI_AGENT_TO_DRIVER[agent];
|
|
343
|
+
if (!driverKind) {
|
|
344
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
345
|
+
`cli-${agent}: no driver registered`);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
350
|
+
let timedOut = false;
|
|
351
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
352
|
+
timer = setTimeout(() => {
|
|
353
|
+
timedOut = true;
|
|
354
|
+
reject(new Error(`timed out after ${DISCOVERY_TIMEOUT_MS / 1000}s`));
|
|
355
|
+
}, DISCOVERY_TIMEOUT_MS);
|
|
356
|
+
if (timer.unref) timer.unref();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const discovery = (async () => {
|
|
360
|
+
try {
|
|
361
|
+
const instance = await registry.createInstance(
|
|
362
|
+
`cli-${agent}`,
|
|
363
|
+
{ driver: driverKind, enabled: true },
|
|
364
|
+
projectCwd,
|
|
365
|
+
);
|
|
366
|
+
if (timedOut) return { agent, instance };
|
|
367
|
+
cliInstances.set(agent, instance);
|
|
368
|
+
return { agent, instance };
|
|
369
|
+
} catch (err) {
|
|
370
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
371
|
+
`cli-${agent}: driver create failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
})();
|
|
375
|
+
|
|
376
|
+
// Clean up leaked instance if discovery completed after timeout
|
|
377
|
+
discovery.then((result) => {
|
|
378
|
+
if (timedOut && result) {
|
|
379
|
+
registry.teardownInstance(`cli-${agent}`).catch(() => {});
|
|
380
|
+
}
|
|
381
|
+
}).catch(() => {});
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const result = await Promise.race([discovery, timeout]);
|
|
385
|
+
clearTimeout(timer!);
|
|
386
|
+
return result;
|
|
387
|
+
} catch (err) {
|
|
388
|
+
clearTimeout(timer!);
|
|
389
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
390
|
+
`cli-${agent}: ${err instanceof Error ? err.message : String(err)}`);
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
for (const result of cliResults) {
|
|
397
|
+
if (result.status !== "fulfilled" || !result.value) continue;
|
|
398
|
+
const { agent, instance } = result.value;
|
|
399
|
+
try {
|
|
400
|
+
await registerCliAgent(pi, agent, instance);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
fileLog(LOG_DOMAIN, "error", LOG_DOMAIN,
|
|
403
|
+
`cli-${agent}: registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function discoverAndRegisterAcpx(): Promise<void> {
|
|
409
|
+
if (isSubagent) return;
|
|
410
|
+
const acpxAgentList = asStringArray(getSetting(projectCwd, "acpx_agents"));
|
|
411
|
+
if (acpxAgentList.length === 0) return;
|
|
412
|
+
|
|
413
|
+
const acpxResults = await Promise.allSettled(
|
|
414
|
+
acpxAgentList.map(async (agent) => {
|
|
415
|
+
const driverKind = ACPX_AGENT_TO_DRIVER[agent] || "acpx";
|
|
416
|
+
// Only proceed if the driver is registered
|
|
417
|
+
if (!registry.getDriver(driverKind)) {
|
|
418
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
419
|
+
`acpx-${agent}: no driver '${driverKind}' registered`);
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
424
|
+
let timedOut = false;
|
|
425
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
426
|
+
timer = setTimeout(() => {
|
|
427
|
+
timedOut = true;
|
|
428
|
+
reject(new Error(`timed out after ${DISCOVERY_TIMEOUT_MS / 1000}s`));
|
|
429
|
+
}, DISCOVERY_TIMEOUT_MS);
|
|
430
|
+
if (timer.unref) timer.unref();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const discovery = (async () => {
|
|
434
|
+
try {
|
|
435
|
+
const instance = await registry.createInstance(
|
|
436
|
+
`acpx-${agent}`,
|
|
437
|
+
{ driver: driverKind, config: { agent }, enabled: true },
|
|
438
|
+
projectCwd,
|
|
439
|
+
);
|
|
440
|
+
if (timedOut) return { agent, instance };
|
|
441
|
+
acpxInstances.set(agent, instance);
|
|
442
|
+
return { agent, instance };
|
|
443
|
+
} catch (err) {
|
|
444
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
445
|
+
`acpx-${agent}: driver create failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
})();
|
|
449
|
+
|
|
450
|
+
// Clean up leaked instance if discovery completed after timeout
|
|
451
|
+
discovery.then((result) => {
|
|
452
|
+
if (timedOut && result) {
|
|
453
|
+
registry.teardownInstance(`acpx-${agent}`).catch(() => {});
|
|
454
|
+
}
|
|
455
|
+
}).catch(() => {});
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const result = await Promise.race([discovery, timeout]);
|
|
459
|
+
clearTimeout(timer!);
|
|
460
|
+
return result;
|
|
461
|
+
} catch (err) {
|
|
462
|
+
clearTimeout(timer!);
|
|
463
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
464
|
+
`acpx-${agent}: ${err instanceof Error ? err.message : String(err)}`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
for (const result of acpxResults) {
|
|
471
|
+
if (result.status !== "fulfilled" || !result.value) continue;
|
|
472
|
+
const { agent, instance } = result.value;
|
|
473
|
+
try {
|
|
474
|
+
await registerAcpxAgent(pi, agent, instance);
|
|
475
|
+
} catch (err) {
|
|
476
|
+
fileLog(LOG_DOMAIN, "error", LOG_DOMAIN,
|
|
477
|
+
`acpx-${agent}: registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Run CLI and ACPX discovery concurrently
|
|
483
|
+
await Promise.all([discoverAndRegisterCli(), discoverAndRegisterAcpx()]);
|
|
484
|
+
|
|
485
|
+
// Show discovery summary on session start
|
|
486
|
+
const providerSummaryParts: string[] = [];
|
|
487
|
+
for (const [agent, inst] of cliInstances) {
|
|
488
|
+
const count = inst.snapshot.getSnapshot().models.length;
|
|
489
|
+
providerSummaryParts.push(`cli-${agent} (${count})`);
|
|
490
|
+
}
|
|
491
|
+
for (const [agent, inst] of acpxInstances) {
|
|
492
|
+
const count = inst.snapshot.getSnapshot().models.length;
|
|
493
|
+
providerSummaryParts.push(`acpx-${agent} (${count})`);
|
|
494
|
+
}
|
|
495
|
+
if (providerSummaryParts.length > 0) {
|
|
496
|
+
pi.on("session_start", (_event, ctx) => {
|
|
497
|
+
if (!ctx.hasUI) return;
|
|
498
|
+
try {
|
|
499
|
+
ctx.ui.notify(`Providers: ${providerSummaryParts.join(", ")}`, "info");
|
|
500
|
+
} catch { /* stale ctx */ }
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// Shutdown
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
pi.on("session_shutdown", async () => {
|
|
508
|
+
stopCliSessionReaper();
|
|
509
|
+
await registry.teardownAll();
|
|
510
|
+
cliInstances.clear();
|
|
511
|
+
acpxInstances.clear();
|
|
512
|
+
});
|
|
513
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared system prompt builder for CLI and ACPX providers.
|
|
3
|
+
* Injects enforced memory entries so external agents follow the same rules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadEnforcedEntries } from "../orchestrator/enforcement-rules.js";
|
|
7
|
+
|
|
8
|
+
interface ProviderContext {
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build the system prompt for external LLM providers (CLI/ACPX).
|
|
14
|
+
* Appends enforced memory rules so external agents follow project enforcement.
|
|
15
|
+
*/
|
|
16
|
+
export function buildExternalSystemPrompt(context: ProviderContext, cwd?: string): string | undefined {
|
|
17
|
+
if (!context.systemPrompt) return undefined;
|
|
18
|
+
const parts = [
|
|
19
|
+
"You are being used as a backend LLM through pi coding agent.",
|
|
20
|
+
"You have full permission to read, write, edit, and execute any files or commands.",
|
|
21
|
+
"Follow these instructions:",
|
|
22
|
+
"",
|
|
23
|
+
context.systemPrompt,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Inject enforced memory entries so CLI/ACPX agents follow the same rules
|
|
27
|
+
if (cwd) {
|
|
28
|
+
try {
|
|
29
|
+
const entries = loadEnforcedEntries(cwd);
|
|
30
|
+
if (entries.length > 0) {
|
|
31
|
+
const rules: string[] = [];
|
|
32
|
+
for (const e of entries) {
|
|
33
|
+
const trigger = e.trigger
|
|
34
|
+
.replace(/^bash_contains\s+/, "")
|
|
35
|
+
.replace(/^bash_regex\s+/, "")
|
|
36
|
+
.replace(/^tool_name\s+/, "")
|
|
37
|
+
.replace(/^file_modified\s+/, "");
|
|
38
|
+
if (e.action === "block") {
|
|
39
|
+
rules.push(`- NEVER: ${trigger} — ${e.text}`);
|
|
40
|
+
} else if (e.action === "warn") {
|
|
41
|
+
rules.push(`- WARNING when running \`${trigger}\`: ${e.text}`);
|
|
42
|
+
} else if (e.action.startsWith("run_after")) {
|
|
43
|
+
const cmd = e.actionCommand || e.action.slice("run_after ".length);
|
|
44
|
+
if (cmd) {
|
|
45
|
+
rules.push(`- After running \`${trigger}\`, ALWAYS run: \`${cmd}\``);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (rules.length > 0) {
|
|
50
|
+
parts.push("", "## Enforced Rules (MANDATORY)", "", ...rules);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch { /* enforcement should never break the provider */ }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return parts.join("\n");
|
|
57
|
+
}
|