pi-harness-runtime 1.1.97 → 1.1.99
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/index.ts +38 -1
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/loader.d.ts +42 -1
- package/packages/skills/dist/loader.d.ts.map +1 -1
- package/packages/skills/dist/loader.js +185 -1
- package/packages/skills/dist/loader.js.map +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/skills/src/loader.ts +238 -2
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill Registry
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Stores and manages skills with progressive disclosure.
|
|
5
5
|
* Based on pi.dev Agent Skills specification.
|
|
6
6
|
*/
|
|
@@ -19,6 +19,100 @@ import type {
|
|
|
19
19
|
} from "./types.js";
|
|
20
20
|
import { parseSkillFile, scanSkillDirectory } from "./parser.js";
|
|
21
21
|
|
|
22
|
+
// --- Qdrant integration (optional) -----------------------------------------
|
|
23
|
+
// Loaded dynamically so skills work without Qdrant configured.
|
|
24
|
+
// At runtime, packages/ is resolved from the monorepo root or node_modules.
|
|
25
|
+
|
|
26
|
+
interface QdrantSkillPoint {
|
|
27
|
+
id: number;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
body: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface QdrantClientLike {
|
|
35
|
+
upsert(collection: string, opts: { wait?: boolean; points: unknown[] }): Promise<void>;
|
|
36
|
+
query(collection: string, opts: {
|
|
37
|
+
query?: { text?: string; model?: string };
|
|
38
|
+
limit?: number;
|
|
39
|
+
with_payload?: boolean;
|
|
40
|
+
filter?: Record<string, unknown>;
|
|
41
|
+
}): Promise<unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface QdrantEmbedderLike {
|
|
45
|
+
createEmbedding(text: string, apiKey?: string): Promise<{ embedding: number[]; tokens: number }>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolve the Qdrant skill module path (monorepo sibling or npm package) */
|
|
49
|
+
async function resolveQdrantModules(): Promise<{
|
|
50
|
+
createQdrantClient: (cfg: unknown) => { upsert(c: string, o: unknown): Promise<void>; query(c: string, o: unknown): Promise<unknown>; };
|
|
51
|
+
createCollection: (client: unknown, name: string, dims: number) => Promise<void>;
|
|
52
|
+
createEmbedding: (text: string, apiKey?: string) => Promise<{ embedding: number[]; tokens: number }>;
|
|
53
|
+
} | null> {
|
|
54
|
+
// Try monorepo sibling first (resolved from packages/skills/src/loader.ts)
|
|
55
|
+
for (const base of ["../../../packages/qdrant-skills/src", "../../packages/qdrant-skills/src"]) {
|
|
56
|
+
try {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
|
+
const mod = require(/* @vite-ignore */ base + "/index.js");
|
|
59
|
+
return mod as Awaited<ReturnType<typeof resolveQdrantModules>>;
|
|
60
|
+
} catch {
|
|
61
|
+
// Try next path
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Fall back to npm package
|
|
65
|
+
try {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
67
|
+
const mod = require(/* @vite-ignore */ "@pi-harness/qdrant-skills") as { createQdrantClient: unknown; createCollection: unknown; createEmbedding: unknown };
|
|
68
|
+
return {
|
|
69
|
+
createQdrantClient: (cfg: unknown) => (mod.createQdrantClient as Function)(cfg) as { upsert(c: string, o: unknown): Promise<void>; query(c: string, o: unknown): Promise<unknown> },
|
|
70
|
+
createCollection: mod.createCollection as (client: unknown, name: string, dims: number) => Promise<void>,
|
|
71
|
+
createEmbedding: mod.createEmbedding as (text: string, apiKey?: string) => Promise<{ embedding: number[]; tokens: number }>,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Read a key file, returning null if missing or empty */
|
|
79
|
+
function readKeyFile(path: string): string | null {
|
|
80
|
+
try {
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
const val = readFileSync(path, "utf8").trim();
|
|
83
|
+
return val.length > 5 ? val : null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get Qdrant config from env or keys files.
|
|
91
|
+
* Returns null if not configured.
|
|
92
|
+
*/
|
|
93
|
+
export function getQdrantConfig(): {
|
|
94
|
+
url: string;
|
|
95
|
+
apiKey: string;
|
|
96
|
+
collection: string;
|
|
97
|
+
} | null {
|
|
98
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
|
|
99
|
+
const keysDir = `${homedir}/.pi-harness-runtime/keys`;
|
|
100
|
+
|
|
101
|
+
const url =
|
|
102
|
+
process.env.QDRANT_CLUSTER_ENDPOINT ||
|
|
103
|
+
readKeyFile(`${keysDir}/qdrant-cluster-url.txt`);
|
|
104
|
+
const apiKey =
|
|
105
|
+
process.env.QDRANT_API_KEY ||
|
|
106
|
+
readKeyFile(`${keysDir}/qdrant-api-key.txt`);
|
|
107
|
+
|
|
108
|
+
if (!url || !apiKey) return null;
|
|
109
|
+
return {
|
|
110
|
+
url,
|
|
111
|
+
apiKey,
|
|
112
|
+
collection: process.env.QDRANT_COLLECTION || "pi-harness-skills",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
22
116
|
/**
|
|
23
117
|
* Skill Registry - manages loaded and indexed skills
|
|
24
118
|
*/
|
|
@@ -27,12 +121,92 @@ export class SkillRegistry {
|
|
|
27
121
|
private index: SkillIndex = { entries: [], loadedAt: 0 };
|
|
28
122
|
private loading: Map<string, Promise<Skill>> = new Map();
|
|
29
123
|
|
|
124
|
+
// Qdrant state (lazily initialized on first register if configured)
|
|
125
|
+
private _qdrant: {
|
|
126
|
+
client: QdrantClientLike;
|
|
127
|
+
embedder: QdrantEmbedderLike;
|
|
128
|
+
collection: string;
|
|
129
|
+
ready: boolean;
|
|
130
|
+
} | null = null;
|
|
131
|
+
private _qdrantInitAttempted = false;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initialize Qdrant client if configured.
|
|
135
|
+
* Called lazily on first skill registration.
|
|
136
|
+
*/
|
|
137
|
+
private async _ensureQdrant(): Promise<void> {
|
|
138
|
+
if (this._qdrantInitAttempted) return;
|
|
139
|
+
this._qdrantInitAttempted = true;
|
|
140
|
+
|
|
141
|
+
const config = getQdrantConfig();
|
|
142
|
+
if (!config) return;
|
|
143
|
+
|
|
144
|
+
const mods = await resolveQdrantModules();
|
|
145
|
+
if (!mods) {
|
|
146
|
+
console.debug("[skills] Qdrant package not found — skipping vector indexing");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const client = mods.createQdrantClient({
|
|
152
|
+
url: config.url,
|
|
153
|
+
apiKey: config.apiKey,
|
|
154
|
+
});
|
|
155
|
+
await mods.createCollection(client, config.collection, 1536);
|
|
156
|
+
this._qdrant = {
|
|
157
|
+
client,
|
|
158
|
+
embedder: { createEmbedding: mods.createEmbedding },
|
|
159
|
+
collection: config.collection,
|
|
160
|
+
ready: true,
|
|
161
|
+
};
|
|
162
|
+
console.log(`[skills] Qdrant vector store ready (collection: ${config.collection})`);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error("[skills] Qdrant init failed:", err instanceof Error ? err.message : String(err));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Index a single skill into Qdrant (non-blocking).
|
|
170
|
+
*/
|
|
171
|
+
private async _indexSkillToQdrant(skill: Skill): Promise<void> {
|
|
172
|
+
if (!this._qdrant?.ready) return;
|
|
173
|
+
|
|
174
|
+
const point: QdrantSkillPoint = {
|
|
175
|
+
id: this._hashId(skill.id),
|
|
176
|
+
name: skill.id,
|
|
177
|
+
description: skill.frontmatter.description,
|
|
178
|
+
body: skill.body.slice(0, 2000),
|
|
179
|
+
text: [skill.id, skill.frontmatter.name, skill.frontmatter.description, skill.body.slice(0, 1500)].join(" "),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const { embedding } = await this._qdrant.embedder.createEmbedding(point.text);
|
|
184
|
+
await this._qdrant.client.upsert(this._qdrant.collection, {
|
|
185
|
+
wait: false,
|
|
186
|
+
points: [{ id: point.id, vector: embedding, payload: point }],
|
|
187
|
+
});
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error(`[skills] Failed to index "${skill.id}" to Qdrant:`, err instanceof Error ? err.message : String(err));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Simple deterministic ID for Qdrant (no bigints needed). */
|
|
194
|
+
private _hashId(id: string): number {
|
|
195
|
+
let h = 0;
|
|
196
|
+
for (let i = 0; i < id.length; i++) {
|
|
197
|
+
h = (Math.imul(31, h) + id.charCodeAt(i)) | 0;
|
|
198
|
+
}
|
|
199
|
+
return Math.abs(h);
|
|
200
|
+
}
|
|
201
|
+
|
|
30
202
|
/**
|
|
31
|
-
* Register a skill
|
|
203
|
+
* Register a skill (auto-indexes to Qdrant if configured).
|
|
32
204
|
*/
|
|
33
205
|
register(skill: Skill): void {
|
|
34
206
|
this.skills.set(skill.id, skill);
|
|
35
207
|
this.updateIndex(skill);
|
|
208
|
+
// Trigger async Qdrant indexing without blocking registration
|
|
209
|
+
this._ensureQdrant().then(() => this._indexSkillToQdrant(skill)).catch(() => {});
|
|
36
210
|
}
|
|
37
211
|
|
|
38
212
|
/**
|
|
@@ -166,6 +340,68 @@ export class SkillRegistry {
|
|
|
166
340
|
return results[0] ?? null;
|
|
167
341
|
}
|
|
168
342
|
|
|
343
|
+
// --- Qdrant vector search --------------------------------------------------
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Search skills by semantic similarity using Qdrant.
|
|
347
|
+
* Falls back to an empty result if Qdrant is not available.
|
|
348
|
+
*
|
|
349
|
+
* @param query Natural-language query
|
|
350
|
+
* @param limit Max results (default 5)
|
|
351
|
+
* @param threshold Minimum cosine score (default 0.5)
|
|
352
|
+
*/
|
|
353
|
+
async findVector(
|
|
354
|
+
query: string,
|
|
355
|
+
limit = 5,
|
|
356
|
+
threshold = 0.5,
|
|
357
|
+
): Promise<Array<{ skill: Skill; score: number }>> {
|
|
358
|
+
await this._ensureQdrant();
|
|
359
|
+
if (!this._qdrant?.ready) return [];
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const { embedding } = await this._qdrant.embedder.createEmbedding(query);
|
|
363
|
+
// SAFETY: Qdrant's query() accepts number[] for dense vector search via its internal
|
|
364
|
+
// OpenAPI spec — the float[] overload is not in the generated TypeScript types but
|
|
365
|
+
// the server handles it correctly at runtime.
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
367
|
+
const queryOpts: any = { query: embedding, limit, with_payload: true };
|
|
368
|
+
const raw = await this._qdrant.client.query(this._qdrant.collection, queryOpts) as {
|
|
369
|
+
result?: { points?: Array<{ id: number; score: number; payload?: Record<string, unknown> }> } };
|
|
370
|
+
|
|
371
|
+
const results: Array<{ skill: Skill; score: number }> = [];
|
|
372
|
+
for (const pt of raw.result?.points ?? []) {
|
|
373
|
+
if (pt.score < threshold) continue;
|
|
374
|
+
const name = pt.payload?.name as string | undefined;
|
|
375
|
+
const skill = name ? this.skills.get(name) : undefined;
|
|
376
|
+
if (skill) results.push({ skill, score: pt.score });
|
|
377
|
+
}
|
|
378
|
+
return results;
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error("[skills] Qdrant vector search failed:", err instanceof Error ? err.message : String(err));
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Sync all registered skills to Qdrant.
|
|
387
|
+
* Useful after Qdrant becomes available or collection is rebuilt.
|
|
388
|
+
*/
|
|
389
|
+
async syncAllToQdrant(onProgress?: (index: number, total: number) => void): Promise<void> {
|
|
390
|
+
await this._ensureQdrant();
|
|
391
|
+
if (!this._qdrant?.ready) return;
|
|
392
|
+
|
|
393
|
+
const skills = this.list();
|
|
394
|
+
for (let i = 0; i < skills.length; i++) {
|
|
395
|
+
await this._indexSkillToQdrant(skills[i]);
|
|
396
|
+
onProgress?.(i + 1, skills.length);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Whether Qdrant is available and ready. */
|
|
401
|
+
get isQdrantReady(): boolean {
|
|
402
|
+
return this._qdrant?.ready ?? false;
|
|
403
|
+
}
|
|
404
|
+
|
|
169
405
|
/**
|
|
170
406
|
* Load a skill's full content (progressive disclosure)
|
|
171
407
|
*/
|