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,510 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coms-shared.ts — Shared utilities for coms and coms-net wrappers.
|
|
3
|
+
*
|
|
4
|
+
* Handles reload resilience: when coms is active and the user runs /reload,
|
|
5
|
+
* session_shutdown fires (upstream cleans up the old socket/connection),
|
|
6
|
+
* then session_start fires with reason="reload". We detect this and
|
|
7
|
+
* auto-reactivate with the same flags.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as net from "node:net";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import * as crypto from "node:crypto";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tokenize a command string respecting double and single quotes.
|
|
19
|
+
* Quoted values are returned as single tokens with quotes stripped.
|
|
20
|
+
*/
|
|
21
|
+
export function tokenizeArgs(input: string): string[] {
|
|
22
|
+
const tokens: string[] = [];
|
|
23
|
+
let current = '';
|
|
24
|
+
let inQuote: string | null = null;
|
|
25
|
+
for (const ch of input) {
|
|
26
|
+
if (inQuote) {
|
|
27
|
+
if (ch === inQuote) {
|
|
28
|
+
inQuote = null;
|
|
29
|
+
} else {
|
|
30
|
+
current += ch;
|
|
31
|
+
}
|
|
32
|
+
} else if (ch === '"' || ch === "'") {
|
|
33
|
+
inQuote = ch;
|
|
34
|
+
} else if (/\s/.test(ch)) {
|
|
35
|
+
if (current) { tokens.push(current); current = ''; }
|
|
36
|
+
} else {
|
|
37
|
+
current += ch;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (current) tokens.push(current);
|
|
41
|
+
return tokens;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse --key value pairs from command arguments into a Map.
|
|
46
|
+
* Boolean flags (like --explicit) are set to true without consuming the next token.
|
|
47
|
+
*/
|
|
48
|
+
export function parseFlags(parts: string[], values: Map<string, any>): void {
|
|
49
|
+
for (let i = 0; i < parts.length; i++) {
|
|
50
|
+
const part = parts[i];
|
|
51
|
+
if (!part.startsWith('--')) continue;
|
|
52
|
+
const key = part.slice(2);
|
|
53
|
+
if (key === 'explicit') {
|
|
54
|
+
values.set(key, true);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (i + 1 < parts.length) {
|
|
58
|
+
const val = parts[i + 1];
|
|
59
|
+
if (val && !val.startsWith('--')) {
|
|
60
|
+
values.set(key, val);
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DeferredUpstream {
|
|
68
|
+
/** Captured session_start handler from upstream */
|
|
69
|
+
capturedSessionStart: ((event: any, ctx: any) => Promise<void>) | null;
|
|
70
|
+
/** Captured session_shutdown handler from upstream */
|
|
71
|
+
capturedSessionShutdown: (() => Promise<void>) | null;
|
|
72
|
+
/** Flag values for upstream's getFlag calls */
|
|
73
|
+
flagValues: Map<string, any>;
|
|
74
|
+
/** Whether the upstream extension is active */
|
|
75
|
+
active: boolean;
|
|
76
|
+
/** Extra persisted state (e.g., serverStartedByUs for coms-net) */
|
|
77
|
+
extra?: Record<string, any>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a Proxy around the pi API that defers upstream extension activation.
|
|
82
|
+
* On reload, session_shutdown passes through (upstream cleans up), then
|
|
83
|
+
* session_start auto-reactivates if the persist key exists.
|
|
84
|
+
*/
|
|
85
|
+
export function createDeferredProxy(
|
|
86
|
+
pi: ExtensionAPI,
|
|
87
|
+
state: DeferredUpstream,
|
|
88
|
+
inactiveMessage: string,
|
|
89
|
+
persistKey: string,
|
|
90
|
+
): ExtensionAPI {
|
|
91
|
+
return new Proxy(pi, {
|
|
92
|
+
get(target: any, prop: string | symbol) {
|
|
93
|
+
if (typeof prop === 'symbol') {
|
|
94
|
+
const val = target[prop];
|
|
95
|
+
return typeof val === 'function' ? val.bind(target) : val;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
switch (prop) {
|
|
99
|
+
case 'registerFlag':
|
|
100
|
+
return () => {};
|
|
101
|
+
case 'getFlag':
|
|
102
|
+
return (name: string) => state.flagValues.get(name);
|
|
103
|
+
case 'registerCommand':
|
|
104
|
+
return () => {};
|
|
105
|
+
case 'registerTool':
|
|
106
|
+
return (tool: any) => {
|
|
107
|
+
// Inject anti-loop warning into coms_send description
|
|
108
|
+
// (upstream coms_net_send has this but coms_send doesn't)
|
|
109
|
+
if (tool.name === "coms_send" && tool.description && !tool.description.includes("DO NOT")) {
|
|
110
|
+
tool.description +=
|
|
111
|
+
"\n\n\u26a0\ufe0f DO NOT call this tool to REPLY to an inbound message. " +
|
|
112
|
+
"When you receive a `[from <peer>] \u2026` follow-up, just write your answer as your normal assistant message \u2014 " +
|
|
113
|
+
"the coms extension automatically captures the final assistant text at the end of your turn and " +
|
|
114
|
+
"submits it back to the original caller. Calling coms_send in response creates a new outbound message, not a reply.";
|
|
115
|
+
}
|
|
116
|
+
const origExecute = tool.execute;
|
|
117
|
+
tool.execute = async (callId: string, params: any, signal?: AbortSignal, ...rest: any[]) => {
|
|
118
|
+
if (!state.active) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text" as const, text: inactiveMessage }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// For *_await tools: wrap with abort signal support
|
|
124
|
+
// so ESC can interrupt the blocking wait
|
|
125
|
+
if (tool.name?.endsWith("_await") && signal) {
|
|
126
|
+
return Promise.race([
|
|
127
|
+
origExecute(callId, params, signal, ...rest),
|
|
128
|
+
new Promise<any>((_, reject) => {
|
|
129
|
+
if (signal.aborted) reject(new Error("aborted"));
|
|
130
|
+
signal.addEventListener("abort", () => reject(new Error("aborted")), { once: true });
|
|
131
|
+
}),
|
|
132
|
+
]).catch((err) => ({
|
|
133
|
+
content: [{ type: "text" as const, text: `⚠️ ${tool.name} interrupted: ${err.message}` }],
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
return origExecute(callId, params, signal, ...rest);
|
|
137
|
+
};
|
|
138
|
+
return target.registerTool(tool);
|
|
139
|
+
};
|
|
140
|
+
case 'on':
|
|
141
|
+
return (event: string, handler: any) => {
|
|
142
|
+
if (event === 'session_start') {
|
|
143
|
+
state.capturedSessionStart = handler;
|
|
144
|
+
// Register with real pi to auto-reactivate on reload
|
|
145
|
+
return target.on(event, async (evt: any, ctx: any) => {
|
|
146
|
+
if (evt?.reason !== "reload") return;
|
|
147
|
+
// Check if coms was active before reload
|
|
148
|
+
let wasActive = false;
|
|
149
|
+
let savedFlags: Record<string, any> = {};
|
|
150
|
+
let savedExtra: Record<string, any> = {};
|
|
151
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
152
|
+
if (entry.type === "custom" && entry.customType === persistKey) {
|
|
153
|
+
wasActive = entry.data?.active === true;
|
|
154
|
+
savedFlags = entry.data?.flags || {};
|
|
155
|
+
savedExtra = entry.data?.extra || {};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (wasActive) {
|
|
159
|
+
state.flagValues = new Map(Object.entries(savedFlags));
|
|
160
|
+
state.extra = savedExtra;
|
|
161
|
+
try {
|
|
162
|
+
await handler(evt, ctx);
|
|
163
|
+
state.active = true;
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error(`[coms] reload reactivation failed:`, err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (event === 'session_shutdown') {
|
|
171
|
+
state.capturedSessionShutdown = handler;
|
|
172
|
+
// Pass through — upstream needs to clean up socket/connection
|
|
173
|
+
return target.on(event, handler);
|
|
174
|
+
}
|
|
175
|
+
return target.on(event, handler);
|
|
176
|
+
};
|
|
177
|
+
default: {
|
|
178
|
+
const val = target[prop];
|
|
179
|
+
if (typeof val === 'function') {
|
|
180
|
+
return val.bind(target);
|
|
181
|
+
}
|
|
182
|
+
return val;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}) as ExtensionAPI;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Persist coms activation state so it survives reload.
|
|
191
|
+
*/
|
|
192
|
+
export function persistState(pi: ExtensionAPI, persistKey: string, state: DeferredUpstream): void {
|
|
193
|
+
try {
|
|
194
|
+
const flags: Record<string, any> = {};
|
|
195
|
+
for (const [k, v] of state.flagValues) flags[k] = v;
|
|
196
|
+
pi.appendEntry(persistKey, { active: state.active, flags, extra: state.extra || {} });
|
|
197
|
+
} catch (e: any) { console.debug("[coms-shared] persist state failed:", e?.message || e); }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Prune stale coms registry entries on startup — removes entries with dead PIDs.
|
|
202
|
+
* Call from session_start in both coms.ts and coms-net.ts.
|
|
203
|
+
*/
|
|
204
|
+
let _pruned = false;
|
|
205
|
+
/**
|
|
206
|
+
* Prune stale coms registry entries — non-blocking.
|
|
207
|
+
* Runs once per session (coms + coms-net both call this).
|
|
208
|
+
*/
|
|
209
|
+
export function pruneStaleRegistry(): void {
|
|
210
|
+
if (_pruned) return;
|
|
211
|
+
_pruned = true;
|
|
212
|
+
// Run async to avoid blocking session_start
|
|
213
|
+
setImmediate(() => {
|
|
214
|
+
try {
|
|
215
|
+
const comsDir = process.env.PI_COMS_DIR || path.join(os.homedir(), ".pi", "coms");
|
|
216
|
+
const projectsDir = path.join(comsDir, "projects");
|
|
217
|
+
if (!fs.existsSync(projectsDir)) return;
|
|
218
|
+
let dirs: string[];
|
|
219
|
+
try { dirs = fs.readdirSync(projectsDir); } catch { return; }
|
|
220
|
+
for (const proj of dirs) {
|
|
221
|
+
try {
|
|
222
|
+
const projDir = path.join(projectsDir, proj);
|
|
223
|
+
if (!fs.lstatSync(projDir).isDirectory()) continue;
|
|
224
|
+
const agentsDir = path.join(projDir, "agents");
|
|
225
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
226
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
227
|
+
if (!file.endsWith(".json")) continue;
|
|
228
|
+
const fp = path.join(agentsDir, file);
|
|
229
|
+
try {
|
|
230
|
+
const data = JSON.parse(fs.readFileSync(fp, "utf-8"));
|
|
231
|
+
if (typeof data?.pid !== "number") {
|
|
232
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Check socket endpoint — reliable in containers (pid reuse)
|
|
236
|
+
const endpoint = data?.endpoint;
|
|
237
|
+
if (typeof endpoint !== "string" || !endpoint) {
|
|
238
|
+
// No/invalid endpoint — old format entry, remove
|
|
239
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (!fs.existsSync(endpoint)) {
|
|
243
|
+
// Socket file gone — definitely dead
|
|
244
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// Socket exists — try connect to verify.
|
|
248
|
+
// Prefer the ping endpoint (.ping) which runs on a separate
|
|
249
|
+
// thread and is immune to main-thread event-loop blocks.
|
|
250
|
+
// Probe liveness via .ping endpoint (immune to main-thread blocks).
|
|
251
|
+
const pingEndpoint = `${endpoint}.ping`;
|
|
252
|
+
const sock = net.createConnection(pingEndpoint);
|
|
253
|
+
sock.setTimeout(500);
|
|
254
|
+
sock.on("connect", () => sock.destroy()); // alive
|
|
255
|
+
sock.on("error", (err: any) => {
|
|
256
|
+
sock.destroy();
|
|
257
|
+
// Only prune registry on definitive dead signals.
|
|
258
|
+
// Never delete socket files — that's destructive and
|
|
259
|
+
// irreversible, killing live peers whose ping worker
|
|
260
|
+
// may have crashed while the main server is still alive.
|
|
261
|
+
if (err?.code === "ECONNREFUSED" || err?.code === "ENOENT") {
|
|
262
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
sock.on("timeout", () => {
|
|
266
|
+
sock.destroy();
|
|
267
|
+
// Timeout means peer may be busy — only remove registry
|
|
268
|
+
// entry, NOT the socket file. The peer's keepalive can
|
|
269
|
+
// self-heal the registry, but a deleted socket file is
|
|
270
|
+
// permanent and kills the peer's connectivity.
|
|
271
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
272
|
+
// Do NOT unlink endpoint or endpoint.ping on timeout
|
|
273
|
+
});
|
|
274
|
+
} catch (e: any) {
|
|
275
|
+
if (e?.code === "ESRCH" || e instanceof SyntaxError) {
|
|
276
|
+
try { fs.unlinkSync(fp); } catch {}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch { /* skip unreadable project directory */ }
|
|
281
|
+
}
|
|
282
|
+
// Cleanup orphan sockets (no matching registry entry)
|
|
283
|
+
const socketsDir = path.join(comsDir, "sockets");
|
|
284
|
+
if (fs.existsSync(socketsDir)) {
|
|
285
|
+
try {
|
|
286
|
+
const allEndpoints = new Set<string>();
|
|
287
|
+
// Collect all endpoints from remaining registry entries
|
|
288
|
+
for (const p of dirs) {
|
|
289
|
+
try {
|
|
290
|
+
const pd = path.join(projectsDir, p);
|
|
291
|
+
if (!fs.lstatSync(pd).isDirectory()) continue;
|
|
292
|
+
const ad = path.join(pd, "agents");
|
|
293
|
+
if (!fs.existsSync(ad)) continue;
|
|
294
|
+
for (const f of fs.readdirSync(ad)) {
|
|
295
|
+
if (!f.endsWith(".json")) continue;
|
|
296
|
+
try {
|
|
297
|
+
const d = JSON.parse(fs.readFileSync(path.join(ad, f), "utf-8"));
|
|
298
|
+
if (d?.endpoint) allEndpoints.add(d.endpoint);
|
|
299
|
+
} catch {}
|
|
300
|
+
}
|
|
301
|
+
} catch {}
|
|
302
|
+
}
|
|
303
|
+
// Remove sockets with no registry entry
|
|
304
|
+
for (const sf of fs.readdirSync(socketsDir)) {
|
|
305
|
+
if (!sf.endsWith(".sock") && !sf.endsWith(".sock.ping")) continue;
|
|
306
|
+
const sp = path.join(socketsDir, sf);
|
|
307
|
+
// For .ping files, check if the parent .sock is in the endpoint set
|
|
308
|
+
const parentEndpoint = sf.endsWith(".sock.ping") ? sp.slice(0, -5) : sp;
|
|
309
|
+
if (!allEndpoints.has(parentEndpoint)) {
|
|
310
|
+
try { fs.unlinkSync(sp); } catch {}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch {}
|
|
314
|
+
}
|
|
315
|
+
} catch (e: any) { console.debug("[coms] async prune failed:", e?.message?.slice(0, 100)); }
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ━━ Shared helpers (used by both coms-p2p.ts and coms-net.ts) ━━━━━━━━━━━━━━
|
|
320
|
+
|
|
321
|
+
export const FALLBACK_PALETTE = [
|
|
322
|
+
"#72F1B8", "#36F9F6", "#FF7EDB", "#FEDE5D",
|
|
323
|
+
"#C792EA", "#FF8B39", "#4D9DE0", "#FFAA8B",
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // pragma: allowlist secret
|
|
327
|
+
|
|
328
|
+
export function ulid(): string {
|
|
329
|
+
const time = Date.now();
|
|
330
|
+
const rand = crypto.randomBytes(10);
|
|
331
|
+
let timeStr = "";
|
|
332
|
+
let t = time;
|
|
333
|
+
for (let i = 9; i >= 0; i--) {
|
|
334
|
+
timeStr = CROCKFORD[t % 32] + timeStr;
|
|
335
|
+
t = Math.floor(t / 32);
|
|
336
|
+
}
|
|
337
|
+
let randStr = "";
|
|
338
|
+
let bits = 0;
|
|
339
|
+
let value = 0;
|
|
340
|
+
for (const byte of rand) {
|
|
341
|
+
value = (value << 8) | byte;
|
|
342
|
+
bits += 8;
|
|
343
|
+
while (bits >= 5) {
|
|
344
|
+
bits -= 5;
|
|
345
|
+
randStr += CROCKFORD[(value >> bits) & 31];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return (timeStr + randStr).slice(0, 26);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function hexFg(hex: string, s: string): string {
|
|
352
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
353
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
354
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
355
|
+
return `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function isValidHex(hex: string): boolean {
|
|
359
|
+
return /^#[0-9a-fA-F]{6}$/.test(hex);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function fallbackColor(sessionId: string): string {
|
|
363
|
+
const h = crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, 8);
|
|
364
|
+
return FALLBACK_PALETTE[Number(BigInt("0x" + h)) % FALLBACK_PALETTE.length];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function comsParseYamlFrontmatter(raw: string): { name?: string; description?: string; color?: string; body: string } {
|
|
368
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
369
|
+
if (!match) return { body: raw };
|
|
370
|
+
const frontmatter: Record<string, string> = {};
|
|
371
|
+
for (const line of match[1].split("\n")) {
|
|
372
|
+
const idx = line.indexOf(":");
|
|
373
|
+
if (idx > 0) {
|
|
374
|
+
const key = line.slice(0, idx).trim();
|
|
375
|
+
let val = line.slice(idx + 1).trim();
|
|
376
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
377
|
+
val = val.slice(1, -1);
|
|
378
|
+
}
|
|
379
|
+
frontmatter[key] = val;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
name: frontmatter.name,
|
|
384
|
+
description: frontmatter.description,
|
|
385
|
+
color: frontmatter.color,
|
|
386
|
+
body: match[2],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function nowIso(): string {
|
|
391
|
+
return new Date().toISOString();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function abbreviateModel(model: string): string {
|
|
395
|
+
let m = model || "";
|
|
396
|
+
if (m.startsWith("claude-")) m = m.slice("claude-".length);
|
|
397
|
+
if (m.length > 14) m = m.slice(0, 14);
|
|
398
|
+
return m;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function findSystemPromptPath(argv: string[]): string | null {
|
|
402
|
+
const scan = (flag: string): string | null => {
|
|
403
|
+
for (let i = 0; i < argv.length; i++) {
|
|
404
|
+
if (argv[i] === flag && i + 1 < argv.length) {
|
|
405
|
+
const candidate = argv[i + 1];
|
|
406
|
+
if (candidate.endsWith(".md")) {
|
|
407
|
+
try {
|
|
408
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
409
|
+
return candidate;
|
|
410
|
+
}
|
|
411
|
+
} catch { /* fall through */ }
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
};
|
|
417
|
+
return scan("--system-prompt") ?? scan("--append-system-prompt");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function readFrontmatterFromArgv(argv: string[]): { name?: string; description?: string; color?: string } {
|
|
421
|
+
const p = findSystemPromptPath(argv);
|
|
422
|
+
if (!p) return {};
|
|
423
|
+
try {
|
|
424
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
425
|
+
const { name, description, color } = comsParseYamlFrontmatter(raw);
|
|
426
|
+
return { name, description, color };
|
|
427
|
+
} catch {
|
|
428
|
+
return {};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function readTaskSummary(cwd: string, sessionId?: string): { total: number; completed: number; in_progress: number } | null {
|
|
433
|
+
try {
|
|
434
|
+
const tasksDir = path.join(cwd, ".pi", "tasks");
|
|
435
|
+
if (!fs.existsSync(tasksDir)) return null;
|
|
436
|
+
const candidates = sessionId
|
|
437
|
+
? [`tasks-${sessionId}.json`, "tasks.json"]
|
|
438
|
+
: ["tasks.json"];
|
|
439
|
+
for (const candidate of candidates) {
|
|
440
|
+
const filePath = path.join(tasksDir, candidate);
|
|
441
|
+
if (!fs.existsSync(filePath)) continue;
|
|
442
|
+
try {
|
|
443
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
444
|
+
if (!Array.isArray(data.tasks) || data.tasks.length === 0) continue;
|
|
445
|
+
let total = 0, completed = 0, in_progress = 0;
|
|
446
|
+
for (const t of data.tasks) {
|
|
447
|
+
total++;
|
|
448
|
+
if (t.status === "completed") completed++;
|
|
449
|
+
else if (t.status === "in_progress") in_progress++;
|
|
450
|
+
}
|
|
451
|
+
return { total, completed, in_progress };
|
|
452
|
+
} catch { /* skip corrupt file */ }
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
} catch { return null; }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function buildInboundContent(
|
|
459
|
+
header: string,
|
|
460
|
+
prompt: string,
|
|
461
|
+
tasks?: Array<{ subject: string; description: string }> | null,
|
|
462
|
+
): string {
|
|
463
|
+
let content = `${header}\n\n${prompt}`;
|
|
464
|
+
if (tasks && tasks.length > 0) {
|
|
465
|
+
content += `\n\n## Assigned Tasks\n\nThe sender has assigned you these tasks. Use TaskCreate to add them to your task list, then work through them:\n\n`;
|
|
466
|
+
for (const task of tasks) {
|
|
467
|
+
content += `- **${task.subject}**: ${task.description}\n`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return content;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export interface TasksSummary {
|
|
474
|
+
total: number;
|
|
475
|
+
completed: number;
|
|
476
|
+
in_progress: number;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Format the auto-delivered coms response text, including queued message IDs when > 0. */
|
|
480
|
+
export function formatComsResponseText(targetName: string, response: any, error: string | null, queuedMsgIds: string[]): string {
|
|
481
|
+
const ids = queuedMsgIds.length <= 5 ? queuedMsgIds.join(", ") : `${queuedMsgIds.slice(0, 5).join(", ")} and ${queuedMsgIds.length - 5} more`;
|
|
482
|
+
const queueNote = queuedMsgIds.length > 0 ? ` (${queuedMsgIds.length} more queued: ${ids})` : "";
|
|
483
|
+
if (error) return `[coms response from ${targetName}${queueNote}] Error: ${error}`;
|
|
484
|
+
return typeof response === "string"
|
|
485
|
+
? `[coms response from ${targetName}${queueNote}] ${response}`
|
|
486
|
+
: `[coms response from ${targetName}${queueNote}] ${JSON.stringify(response, null, 2)}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Return " 📨N" suffix string for queue depth, or "" when zero/absent. */
|
|
490
|
+
export function formatQueueStr(queueDepth: number | undefined | null): string {
|
|
491
|
+
return typeof queueDepth === "number" && queueDepth > 0 ? ` 📨${queueDepth}` : "";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Return theme-colored queue indicator for the coms widget list row. */
|
|
495
|
+
export function renderQueuePart(queueDepth: number, theme: any): string {
|
|
496
|
+
return queueDepth > 0 ? theme.fg("dim", " ") + theme.fg("warning", `📨${queueDepth}`) : "";
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function renderTasksPart(tasks: TasksSummary | null | undefined, theme: any): string {
|
|
500
|
+
if (!tasks || tasks.total === 0) return "";
|
|
501
|
+
// Clamp to prevent negative/misleading values from corrupt data
|
|
502
|
+
const completed = Math.max(0, Math.min(tasks.completed, tasks.total));
|
|
503
|
+
const in_progress = Math.max(0, Math.min(tasks.in_progress, tasks.total - completed));
|
|
504
|
+
const pending = Math.max(0, tasks.total - completed - in_progress);
|
|
505
|
+
const parts: string[] = [];
|
|
506
|
+
if (completed > 0) parts.push(theme.fg("success", `${completed}✔`));
|
|
507
|
+
if (in_progress > 0) parts.push(theme.fg("accent", `${in_progress}◼`));
|
|
508
|
+
if (pending > 0) parts.push(theme.fg("dim", `${pending}◻`));
|
|
509
|
+
return theme.fg("dim", " ") + parts.join(theme.fg("dim", " "));
|
|
510
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coms-wrapper.ts — On-demand P2P agent communication wrapper
|
|
3
|
+
*
|
|
4
|
+
* Wraps the P2P coms extension (coms-p2p.ts) to support
|
|
5
|
+
* activation via /coms command instead of auto-start on session_start.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
10
|
+
import { fuzzyFilter } from "@earendil-works/pi-tui";
|
|
11
|
+
import { parseFlags, tokenizeArgs, createDeferredProxy, persistState, pruneStaleRegistry, type DeferredUpstream } from "./coms-shared.js";
|
|
12
|
+
import upstreamComsInit from "./coms-p2p.js";
|
|
13
|
+
|
|
14
|
+
function fuzzy(items: AutocompleteItem[], query: string): AutocompleteItem[] | null {
|
|
15
|
+
if (!query.trim()) return items.length > 0 ? items : null;
|
|
16
|
+
const result = fuzzyFilter(items, query, i => `${i.label} ${i.description || ""}`);
|
|
17
|
+
return result.length > 0 ? result : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerComs(pi: ExtensionAPI) {
|
|
21
|
+
const state: DeferredUpstream = {
|
|
22
|
+
capturedSessionStart: null,
|
|
23
|
+
capturedSessionShutdown: null,
|
|
24
|
+
flagValues: new Map(),
|
|
25
|
+
active: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const PERSIST_KEY = "coms-state";
|
|
29
|
+
|
|
30
|
+
const proxyPi = createDeferredProxy(
|
|
31
|
+
pi, state, "⚠️ coms not active. Run `/coms start` first.", PERSIST_KEY,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
upstreamComsInit(proxyPi as any);
|
|
35
|
+
|
|
36
|
+
// Prune stale registry entries on session start (cleans up after crashes)
|
|
37
|
+
// Also reset coms state on fresh starts (non-reload) to clear phantom peers
|
|
38
|
+
pi.on("session_start", (evt: any) => {
|
|
39
|
+
try { pruneStaleRegistry(); } catch (e: any) { console.debug("[coms] stale cleanup:", e?.message?.slice(0, 100)); }
|
|
40
|
+
if (evt?.reason !== "reload") {
|
|
41
|
+
state.active = false;
|
|
42
|
+
persistState(pi, PERSIST_KEY, state);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
pi.registerCommand("coms", {
|
|
47
|
+
description: "P2P agent communication: /coms start | stop | status",
|
|
48
|
+
getArgumentCompletions: (prefix: string) => {
|
|
49
|
+
// Parse: split on whitespace, trailing space means "next token position"
|
|
50
|
+
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
51
|
+
const atNextToken = prefix.endsWith(" ") || tokens.length === 0;
|
|
52
|
+
const lastPart = atNextToken ? "" : tokens[tokens.length - 1];
|
|
53
|
+
const completed = atNextToken ? tokens : tokens.slice(0, -1);
|
|
54
|
+
const base = atNextToken ? prefix : prefix.slice(0, prefix.length - lastPart.length);
|
|
55
|
+
const mk = (items: {v: string; l: string; d: string}[]) =>
|
|
56
|
+
fuzzy(items.map(i => ({ value: base + i.v, label: i.l, description: i.d })), lastPart);
|
|
57
|
+
|
|
58
|
+
if (completed.length === 0) {
|
|
59
|
+
return mk([
|
|
60
|
+
{ v: "start", l: "start", d: "Start P2P agent communication" },
|
|
61
|
+
{ v: "stop", l: "stop", d: "Stop coms" },
|
|
62
|
+
{ v: "status", l: "status", d: "Show coms status" },
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
if (completed[0] === "start" && (lastPart.startsWith("-") || lastPart === "")) {
|
|
66
|
+
const used = new Set(completed.filter(p => p.startsWith("--")));
|
|
67
|
+
return mk([
|
|
68
|
+
{ v: "--cname ", l: "--cname", d: "Agent name" },
|
|
69
|
+
{ v: "--purpose ", l: "--purpose", d: "Agent purpose" },
|
|
70
|
+
{ v: "--project ", l: "--project", d: "Project namespace" },
|
|
71
|
+
{ v: "--color ", l: "--color", d: "Hex color #RRGGBB" },
|
|
72
|
+
{ v: "--explicit", l: "--explicit", d: "Hide from auto-discovery" },
|
|
73
|
+
].filter(f => !used.has(f.v.trim())));
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
},
|
|
77
|
+
handler: async (args: string, ctx: any) => {
|
|
78
|
+
const trimmed = (args || "").trim();
|
|
79
|
+
const parts = tokenizeArgs(trimmed);
|
|
80
|
+
const subcommand = parts[0] || "status";
|
|
81
|
+
|
|
82
|
+
if (subcommand === "start") {
|
|
83
|
+
if (state.active) {
|
|
84
|
+
try { ctx.ui.notify("📡 coms already active", "warning"); } catch {}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
state.flagValues = new Map();
|
|
88
|
+
parseFlags(parts.slice(1), state.flagValues);
|
|
89
|
+
|
|
90
|
+
if (state.flagValues.has("name")) {
|
|
91
|
+
try { ctx.ui.notify("📡 coms: use --cname instead of --name", "error"); } catch {}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Default project to cwd so sessions in different dirs are isolated
|
|
96
|
+
if (!state.flagValues.has("project")) {
|
|
97
|
+
const cwd = ctx.cwd || "";
|
|
98
|
+
const proj = cwd.replace(/^[\\/]/,"").replace(/[\\/]/g, "__");
|
|
99
|
+
if (!proj) {
|
|
100
|
+
try { ctx.ui.notify("📡 coms: cannot start from /. Run from a project directory.", "error"); } catch {}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
state.flagValues.set("project", proj);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!state.capturedSessionStart) {
|
|
107
|
+
try { ctx.ui.notify("📡 coms: internal error — no session handler captured", "error"); } catch {}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await state.capturedSessionStart({}, ctx);
|
|
113
|
+
state.active = true;
|
|
114
|
+
persistState(pi, PERSIST_KEY, state);
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
try { ctx.ui.notify(`📡 coms start failed: ${err?.message ?? String(err)}`, "error"); } catch {}
|
|
117
|
+
}
|
|
118
|
+
} else if (subcommand === "stop") {
|
|
119
|
+
if (!state.active) {
|
|
120
|
+
try { ctx.ui.notify("📡 coms not active", "info"); } catch {}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (state.capturedSessionShutdown) {
|
|
124
|
+
try { await state.capturedSessionShutdown(); } catch {}
|
|
125
|
+
}
|
|
126
|
+
state.active = false;
|
|
127
|
+
persistState(pi, PERSIST_KEY, state);
|
|
128
|
+
try { ctx.ui.notify("📡 coms stopped", "info"); } catch {}
|
|
129
|
+
} else if (subcommand === "status") {
|
|
130
|
+
try {
|
|
131
|
+
ctx.ui.notify(state.active ? "📡 coms: active (P2P)" : "📡 coms: inactive — run /coms start", "info");
|
|
132
|
+
} catch {}
|
|
133
|
+
} else {
|
|
134
|
+
try { ctx.ui.notify(`📡 coms: unknown subcommand "${subcommand}". Use: start | stop | status`, "warning"); } catch {}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coms Extension for pi
|
|
3
|
+
*
|
|
4
|
+
* Inter-agent communication — P2P (coms) and networked (coms-net).
|
|
5
|
+
* Activated on-demand via /coms and /coms-net slash commands.
|
|
6
|
+
*
|
|
7
|
+
* Standalone extension so it can be loaded independently by pi-sidecar
|
|
8
|
+
* without pulling in the full orchestrator.
|
|
9
|
+
*
|
|
10
|
+
* Forked from disler/pi-vs-claude-code — we own the coms implementation files.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { registerComs } from "./coms-wrapper.js";
|
|
15
|
+
import { registerComsNet } from "./coms-net-wrapper.js";
|
|
16
|
+
|
|
17
|
+
export default function (pi: ExtensionAPI) {
|
|
18
|
+
registerComs(pi);
|
|
19
|
+
registerComsNet(pi);
|
|
20
|
+
}
|