pi-harness-runtime 1.1.414 → 1.1.416
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/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/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/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/contracts/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/durable-review/package.json +1 -1
- package/packages/e2e-tester/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-sink/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/logging/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/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/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/relay/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/index.d.ts +1 -0
- package/packages/skills/dist/index.d.ts.map +1 -1
- package/packages/skills/dist/index.js +1 -0
- package/packages/skills/dist/index.js.map +1 -1
- package/packages/skills/dist/loader.d.ts +9 -0
- package/packages/skills/dist/loader.d.ts.map +1 -1
- package/packages/skills/dist/loader.js +24 -3
- package/packages/skills/dist/loader.js.map +1 -1
- package/packages/skills/dist/sanitize.d.ts +32 -0
- package/packages/skills/dist/sanitize.d.ts.map +1 -0
- package/packages/skills/dist/sanitize.js +100 -0
- package/packages/skills/dist/sanitize.js.map +1 -0
- package/packages/skills/dist/vector-schema.d.ts +4 -0
- package/packages/skills/dist/vector-schema.d.ts.map +1 -1
- package/packages/skills/dist/vector-schema.js +13 -4
- package/packages/skills/dist/vector-schema.js.map +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +26 -3
- package/packages/skills/src/sanitize.ts +111 -0
- package/packages/skills/src/vector-schema.ts +14 -4
- package/packages/skills/test/remote-skills.test.ts +41 -0
- package/packages/skills/test/sanitize.test.ts +131 -0
- package/packages/skills/test/vector-schema.test.ts +15 -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/telegram/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/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/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
- package/scripts/sync-to-qdrant.ts +18 -83
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-schema.d.ts","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"vector-schema.d.ts","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAE3D,0CAA0C;AAC1C,eAAO,MAAM,6BAA6B,2CAA2C,CAAC;AAEtF,gDAAgD;AAChD,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IACjC,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAMT;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,iBAAiB,CAWpB;AACD,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAClD,QAAQ,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CASf"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* module owns the collection - that is a decision of the skills domain, not
|
|
16
16
|
* of the vector store.
|
|
17
17
|
*/
|
|
18
|
+
import { sanitizeText, skillPointName } from "./sanitize.js";
|
|
18
19
|
/**
|
|
19
20
|
* The collection the runtime indexes into and searches.
|
|
20
21
|
*
|
|
@@ -91,13 +92,21 @@ export function skillQdrantPoint(payload) {
|
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* The payload of a skill point.
|
|
95
|
+
*
|
|
96
|
+
* The payload is sanitized: the collection is shared, so the client names
|
|
97
|
+
* of a user must not travel into it. Both writers use this function, so
|
|
98
|
+
* both write the same sanitized payload for the same skill.
|
|
94
99
|
*/
|
|
95
100
|
export function skillPointPayload(input) {
|
|
101
|
+
const name = skillPointName(input.name);
|
|
102
|
+
const title = input.title ? skillPointName(input.title) : undefined;
|
|
103
|
+
const description = sanitizeText(input.description);
|
|
104
|
+
const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
|
|
96
105
|
return {
|
|
97
|
-
name
|
|
98
|
-
description
|
|
99
|
-
body
|
|
100
|
-
text: skillPointText(
|
|
106
|
+
name,
|
|
107
|
+
description,
|
|
108
|
+
body,
|
|
109
|
+
text: skillPointText({ name, title, description, body }),
|
|
101
110
|
};
|
|
102
111
|
}
|
|
103
112
|
export function describeSkillSchemaMismatch(actual, expected) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D,0CAA0C;AAC1C,MAAM,CAAC,MAAM,6BAA6B,GAAG,wCAAwC,CAAC;AAEtF,gDAAgD;AAChD,MAAM,CAAC,MAAM,kCAAkC,GAAG,GAAG,CAAC;AAiCtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C,kDAAkD;AAClD,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAS9B;IACC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACvE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC9B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAcD;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,OAAO,EAAE,EAAE,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;AACnD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export * from "./parser.js";
|
|
|
14
14
|
export * from "./scanner.js";
|
|
15
15
|
export * from "./loader.js";
|
|
16
16
|
export * from "./vector-schema.js";
|
|
17
|
+
export * from "./sanitize.js";
|
|
17
18
|
export * from "./remote-source.js";
|
|
18
19
|
export * from "./skill-commands.js";
|
|
19
20
|
export * from "./skill-tool.js";
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type SkillPointPayload,
|
|
29
29
|
type SkillVectorSchema,
|
|
30
30
|
} from "./vector-schema.js";
|
|
31
|
+
import { skillPointName } from "./sanitize.js";
|
|
31
32
|
import {
|
|
32
33
|
describeRemoteFailure,
|
|
33
34
|
isStale,
|
|
@@ -339,6 +340,15 @@ export class SkillRegistry {
|
|
|
339
340
|
* disk and how many it can reach through the vector store.
|
|
340
341
|
*/
|
|
341
342
|
private remoteSkills: Map<string, Skill> = new Map();
|
|
343
|
+
/**
|
|
344
|
+
* The local skills by their name in the shared collection.
|
|
345
|
+
*
|
|
346
|
+
* The name of a point is the SANITIZED name of the skill, because the
|
|
347
|
+
* collection is shared. A hit of the collection therefore carries a name
|
|
348
|
+
* that this machine does not use - the registry is keyed by the name on
|
|
349
|
+
* this machine. This map bridges the two.
|
|
350
|
+
*/
|
|
351
|
+
private _skillsByPointName: Map<string, Skill> = new Map();
|
|
342
352
|
/**
|
|
343
353
|
* The remote skills whose content is COMPLETE.
|
|
344
354
|
*
|
|
@@ -766,6 +776,8 @@ export class SkillRegistry {
|
|
|
766
776
|
*/
|
|
767
777
|
register(skill: Skill): void {
|
|
768
778
|
this.skills.set(skill.id, skill);
|
|
779
|
+
// The shared collection knows the skill by its sanitized name.
|
|
780
|
+
this._skillsByPointName.set(skillPointName(skill.id), skill);
|
|
769
781
|
this.updateIndex(skill);
|
|
770
782
|
// Queue the skill for the vector index without blocking registration.
|
|
771
783
|
this._scheduleIndex(skill);
|
|
@@ -778,6 +790,12 @@ export class SkillRegistry {
|
|
|
778
790
|
const skill = this.skills.get(skillId);
|
|
779
791
|
if (skill) {
|
|
780
792
|
this.skills.delete(skillId);
|
|
793
|
+
// Only drop the shared name when it still points at this skill: two
|
|
794
|
+
// skills may share a sanitized name.
|
|
795
|
+
const pointName = skillPointName(skill.id);
|
|
796
|
+
if (this._skillsByPointName.get(pointName) === skill) {
|
|
797
|
+
this._skillsByPointName.delete(pointName);
|
|
798
|
+
}
|
|
781
799
|
this.index.entries = this.index.entries.filter(
|
|
782
800
|
(e) => e.id !== skillId
|
|
783
801
|
);
|
|
@@ -791,8 +809,13 @@ export class SkillRegistry {
|
|
|
791
809
|
*/
|
|
792
810
|
get(skillId: string): Skill | undefined {
|
|
793
811
|
// A server skill is only known after a vector search found it, so it is
|
|
794
|
-
// looked up second - the local skill always wins.
|
|
795
|
-
|
|
812
|
+
// looked up second - the local skill always wins. The shared name of a
|
|
813
|
+
// skill resolves to the local skill as well.
|
|
814
|
+
return (
|
|
815
|
+
this.skills.get(skillId) ??
|
|
816
|
+
this._skillsByPointName.get(skillId) ??
|
|
817
|
+
this.remoteSkills.get(skillId)
|
|
818
|
+
);
|
|
796
819
|
}
|
|
797
820
|
|
|
798
821
|
/**
|
|
@@ -1054,7 +1077,7 @@ export class SkillRegistry {
|
|
|
1054
1077
|
const payload = pt.payload ?? {};
|
|
1055
1078
|
const name = typeof payload.name === "string" ? payload.name : undefined;
|
|
1056
1079
|
if (!name) continue;
|
|
1057
|
-
const local = this.skills.get(name);
|
|
1080
|
+
const local = this._skillsByPointName.get(name) ?? this.skills.get(name);
|
|
1058
1081
|
if (local) {
|
|
1059
1082
|
results.push({ skill: local, score: pt.score, source: "local" });
|
|
1060
1083
|
continue;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sanitization of a skill for the shared collection.
|
|
3
|
+
*
|
|
4
|
+
* The collection is shared, so a client name must not travel into it. The
|
|
5
|
+
* rules lived in `scripts/sync-to-qdrant.ts` - the only writer that could
|
|
6
|
+
* reach them. The runtime registry writes into the same collection, so it
|
|
7
|
+
* needs them too: a writer that skipped the rules would leave the raw
|
|
8
|
+
* name and the raw body of a client skill in a shared cluster.
|
|
9
|
+
*
|
|
10
|
+
* Both writers therefore build their points with the same rules, and the
|
|
11
|
+
* name of a point is the sanitized name. One skill, one point.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** A rule that replaces a client specific text. */
|
|
15
|
+
interface SanitizationRule {
|
|
16
|
+
pattern: RegExp;
|
|
17
|
+
replacement: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The patterns of the client specific names.
|
|
22
|
+
*
|
|
23
|
+
* The list is about the names of the clients and the projects of the
|
|
24
|
+
* users of the harness - not about the skills themselves.
|
|
25
|
+
*
|
|
26
|
+
* The patterns match a plain fragment, without a word boundary. A client
|
|
27
|
+
* name rarely stands alone: it appears inside a directory name
|
|
28
|
+
* (`th_digisoft_erp_chart.json`), inside a Python module (`inpac_pharma`)
|
|
29
|
+
* or in a sentence ("integrated with Paperclip"). A word boundary would
|
|
30
|
+
* miss all of them - and `_` is a word character, so a boundary never
|
|
31
|
+
* follows an underscore.
|
|
32
|
+
*/
|
|
33
|
+
const SANITIZATION_RULES: SanitizationRule[] = [
|
|
34
|
+
// Client company names (case-insensitive)
|
|
35
|
+
{ pattern: /inpac/gi, replacement: "[Client A]" },
|
|
36
|
+
{ pattern: /m-capital/gi, replacement: "[Client A]" },
|
|
37
|
+
{ pattern: /mcapital/gi, replacement: "[Client A]" },
|
|
38
|
+
{ pattern: /digisoft/gi, replacement: "[Client B]" },
|
|
39
|
+
|
|
40
|
+
// Project/app specific names
|
|
41
|
+
{ pattern: /gse-insurance/gi, replacement: "[Insurance Project]" },
|
|
42
|
+
{ pattern: /tbs-import/gi, replacement: "[Import Project]" },
|
|
43
|
+
{ pattern: /paperclip/gi, replacement: "[Client D]" },
|
|
44
|
+
{ pattern: /openclaw/gi, replacement: "[Client F]" },
|
|
45
|
+
|
|
46
|
+
// Generic replacements
|
|
47
|
+
{ pattern: /Company Name/gi, replacement: "[Client Name]" },
|
|
48
|
+
{ pattern: /your company/gi, replacement: "[Client]" },
|
|
49
|
+
{ pattern: /our client/gi, replacement: "[Client]" },
|
|
50
|
+
{ pattern: /client-specific/gi, replacement: "[Project-specific]" },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The names that are renamed as a whole.
|
|
55
|
+
*
|
|
56
|
+
* A name that would be unreadable after the pattern rules - a name that
|
|
57
|
+
* carries the client name in its middle - is renamed here.
|
|
58
|
+
*/
|
|
59
|
+
const SKILL_NAME_MAP: Record<string, string> = {
|
|
60
|
+
"gse-insurance-step5-slot-filter": "insurance-project-step-filter",
|
|
61
|
+
"inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
|
|
62
|
+
"inpac-po-approval-workflow": "client-a-po-approval-workflow",
|
|
63
|
+
"inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
|
|
64
|
+
"tbs-import-clearance-lcv": "import-project-clearance",
|
|
65
|
+
whispertool: "whisper-tool",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Replace the client specific names of a text.
|
|
70
|
+
*
|
|
71
|
+
* The text stays the same length, so a vector that was built from it
|
|
72
|
+
* keeps its shape.
|
|
73
|
+
*/
|
|
74
|
+
export function sanitizeText(text: string): string {
|
|
75
|
+
let result = text;
|
|
76
|
+
for (const rule of SANITIZATION_RULES) {
|
|
77
|
+
result = result.replace(rule.pattern, rule.replacement);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The name of a skill in the shared collection.
|
|
84
|
+
*
|
|
85
|
+
* The name is the identity of the point, so it has to be the same for
|
|
86
|
+
* every writer: the map first, then the pattern rules.
|
|
87
|
+
*
|
|
88
|
+
* The result is a skill name - lower case, words separated by a dash -
|
|
89
|
+
* because the name of a point is also the id of the skill that a search
|
|
90
|
+
* returns. The replacements of the rules carry a space and brackets,
|
|
91
|
+
* which no skill name may hold.
|
|
92
|
+
*/
|
|
93
|
+
export function skillPointName(name: string): string {
|
|
94
|
+
const mapped = SKILL_NAME_MAP[name];
|
|
95
|
+
if (mapped) return mapped;
|
|
96
|
+
return kebabName(sanitizeText(name));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Bring a name into the shape of a skill name.
|
|
101
|
+
*
|
|
102
|
+
* The name of a skill is lower case and its words are separated by a
|
|
103
|
+
* dash: `^[a-z0-9]+(-[a-z0-9]+)*$`.
|
|
104
|
+
*/
|
|
105
|
+
function kebabName(name: string): string {
|
|
106
|
+
return name
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
109
|
+
.replace(/-{2,}/g, "-")
|
|
110
|
+
.replace(/^-+|-+$/g, "");
|
|
111
|
+
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* of the vector store.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { sanitizeText, skillPointName } from "./sanitize.js";
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* The collection the runtime indexes into and searches.
|
|
21
23
|
*
|
|
@@ -152,6 +154,10 @@ export function skillQdrantPoint(payload: SkillPointPayload): SkillQdrantPoint {
|
|
|
152
154
|
|
|
153
155
|
/**
|
|
154
156
|
* The payload of a skill point.
|
|
157
|
+
*
|
|
158
|
+
* The payload is sanitized: the collection is shared, so the client names
|
|
159
|
+
* of a user must not travel into it. Both writers use this function, so
|
|
160
|
+
* both write the same sanitized payload for the same skill.
|
|
155
161
|
*/
|
|
156
162
|
export function skillPointPayload(input: {
|
|
157
163
|
/** The name the payload carries - the identity of the skill. */
|
|
@@ -163,11 +169,15 @@ export function skillPointPayload(input: {
|
|
|
163
169
|
/** The body of the skill. */
|
|
164
170
|
body: string;
|
|
165
171
|
}): SkillPointPayload {
|
|
172
|
+
const name = skillPointName(input.name);
|
|
173
|
+
const title = input.title ? skillPointName(input.title) : undefined;
|
|
174
|
+
const description = sanitizeText(input.description);
|
|
175
|
+
const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
|
|
166
176
|
return {
|
|
167
|
-
name
|
|
168
|
-
description
|
|
169
|
-
body
|
|
170
|
-
text: skillPointText(
|
|
177
|
+
name,
|
|
178
|
+
description,
|
|
179
|
+
body,
|
|
180
|
+
text: skillPointText({ name, title, description, body }),
|
|
171
181
|
};
|
|
172
182
|
}
|
|
173
183
|
export function describeSkillSchemaMismatch(
|
|
@@ -169,6 +169,47 @@ describe("a skill that only exists in the vector store", () => {
|
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
describe("a hit that carries the shared name of a local skill", () => {
|
|
173
|
+
it("resolves to the local skill", async () => {
|
|
174
|
+
// The collection is shared, so the point carries the SANITIZED name.
|
|
175
|
+
const client = fakeClient([
|
|
176
|
+
{
|
|
177
|
+
id: 1,
|
|
178
|
+
score: 0.9,
|
|
179
|
+
payload: {
|
|
180
|
+
name: "client-a-pe-approval-workflow",
|
|
181
|
+
description: "the workflow",
|
|
182
|
+
body: "the body",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
const registry = createSkillRegistry({
|
|
187
|
+
qdrantClient: client,
|
|
188
|
+
embedder: fakeEmbedder,
|
|
189
|
+
collection: "pi-harness-skills-test",
|
|
190
|
+
});
|
|
191
|
+
registry.register(localSkill("inpac-pe-approval-workflow"));
|
|
192
|
+
|
|
193
|
+
const results = await registry.findVector("workflow");
|
|
194
|
+
|
|
195
|
+
expect(results).toHaveLength(1);
|
|
196
|
+
// The skill of this machine stays a local skill - with its own
|
|
197
|
+
// name and its own body, not the sanitized copy.
|
|
198
|
+
expect(results[0].source).toBe("local");
|
|
199
|
+
expect(results[0].skill.id).toBe("inpac-pe-approval-workflow");
|
|
200
|
+
expect(results[0].skill.body).toBe("the local body of inpac-pe-approval-workflow");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("resolves the shared name of a local skill in a lookup", () => {
|
|
204
|
+
const registry = createSkillRegistry({ indexer: null });
|
|
205
|
+
registry.register(localSkill("inpac-pe-approval-workflow"));
|
|
206
|
+
|
|
207
|
+
expect(registry.get("client-a-pe-approval-workflow")?.id).toBe(
|
|
208
|
+
"inpac-pe-approval-workflow",
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
172
213
|
describe("the state of the vector store", () => {
|
|
173
214
|
it("reports the number of skills", async () => {
|
|
174
215
|
const client = fakeClient([], {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the sanitization of a skill for the shared collection.
|
|
3
|
+
*
|
|
4
|
+
* The collection is shared, so the client names of a user must not travel
|
|
5
|
+
* into it. The rules used to live in `scripts/sync-to-qdrant.ts` - the
|
|
6
|
+
* only writer that could reach them. The runtime registry writes into the
|
|
7
|
+
* same collection, so it needs them too.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { sanitizeText, skillPointName } from "../src/sanitize.js";
|
|
13
|
+
import { skillPointPayload } from "../src/vector-schema.js";
|
|
14
|
+
|
|
15
|
+
describe("the sanitization of a text", () => {
|
|
16
|
+
test("the name of a client is replaced", () => {
|
|
17
|
+
expect(sanitizeText("the inpac workflow")).toBe("the [Client A] workflow");
|
|
18
|
+
expect(sanitizeText("for digisoft")).toBe("for [Client B]");
|
|
19
|
+
expect(sanitizeText("gse-insurance step")).toBe("[Insurance Project] step");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("the name of a client is replaced in any case", () => {
|
|
23
|
+
expect(sanitizeText("INPAC and Inpac")).toBe("[Client A] and [Client A]");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("a text without a client name stays the same", () => {
|
|
27
|
+
expect(sanitizeText("a plain skill")).toBe("a plain skill");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("the generic phrases are replaced", () => {
|
|
31
|
+
expect(sanitizeText("Company Name")).toBe("[Client Name]");
|
|
32
|
+
expect(sanitizeText("your company")).toBe("[Client]");
|
|
33
|
+
expect(sanitizeText("our client")).toBe("[Client]");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a client name inside an identifier is replaced", () => {
|
|
37
|
+
// The name of a client rarely stands alone. A word boundary would
|
|
38
|
+
// miss every one of these - and an underscore is a word character,
|
|
39
|
+
// so a boundary never follows one.
|
|
40
|
+
expect(sanitizeText("inpac_pharma")).toBe("[Client A]_pharma");
|
|
41
|
+
expect(sanitizeText("th_digisoft_erp_chart.json")).toBe(
|
|
42
|
+
"th_[Client B]_erp_chart.json",
|
|
43
|
+
);
|
|
44
|
+
expect(sanitizeText("apps/inpac_pharma/setup")).toBe(
|
|
45
|
+
"apps/[Client A]_pharma/setup",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a client name in a sentence is replaced", () => {
|
|
50
|
+
expect(sanitizeText("integrated with Paperclip")).toBe(
|
|
51
|
+
"integrated with [Client D]",
|
|
52
|
+
);
|
|
53
|
+
expect(sanitizeText("OpenClaw secrets")).toBe("[Client F] secrets");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("the name of a skill in the shared collection", () => {
|
|
58
|
+
test("a name of the map is renamed", () => {
|
|
59
|
+
expect(skillPointName("inpac-pe-approval-workflow")).toBe(
|
|
60
|
+
"client-a-pe-approval-workflow",
|
|
61
|
+
);
|
|
62
|
+
expect(skillPointName("tbs-import-clearance-lcv")).toBe(
|
|
63
|
+
"import-project-clearance",
|
|
64
|
+
);
|
|
65
|
+
expect(skillPointName("whispertool")).toBe("whisper-tool");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("a name without a client name stays the same", () => {
|
|
69
|
+
expect(skillPointName("code-review")).toBe("code-review");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("the name never carries a client name", () => {
|
|
73
|
+
for (const name of [
|
|
74
|
+
"inpac-pe-approval-workflow",
|
|
75
|
+
"gse-insurance-step5-slot-filter",
|
|
76
|
+
"tbs-import-clearance-lcv",
|
|
77
|
+
"whispertool",
|
|
78
|
+
]) {
|
|
79
|
+
expect(skillPointName(name)).not.toMatch(/inpac|gse-insurance|tbs-import/i);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("the name is a valid skill name", () => {
|
|
84
|
+
// The name of a point is the id of the skill that a search returns,
|
|
85
|
+
// so it has to be a name that a skill may carry.
|
|
86
|
+
const NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
87
|
+
for (const name of [
|
|
88
|
+
"inpac-pe-approval-workflow",
|
|
89
|
+
"openclaw-channel",
|
|
90
|
+
"gse-insurance-step5-slot-filter",
|
|
91
|
+
"tbs-import-clearance-lcv",
|
|
92
|
+
"whispertool",
|
|
93
|
+
"code-review",
|
|
94
|
+
]) {
|
|
95
|
+
expect(skillPointName(name)).toMatch(NAME);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a name that needs no sanitization stays the same", () => {
|
|
100
|
+
expect(skillPointName("code-review")).toBe("code-review");
|
|
101
|
+
expect(skillPointName("frappe-doctype-field-order")).toBe(
|
|
102
|
+
"frappe-doctype-field-order",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("the payload of a point is sanitized", () => {
|
|
108
|
+
test("the client names of the body do not travel into the collection", () => {
|
|
109
|
+
const payload = skillPointPayload({
|
|
110
|
+
name: "inpac-pe-approval-workflow",
|
|
111
|
+
description: "the workflow of inpac",
|
|
112
|
+
body: "the approval of inpac for the client",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(payload.name).toBe("client-a-pe-approval-workflow");
|
|
116
|
+
expect(payload.description).not.toMatch(/inpac/i);
|
|
117
|
+
expect(payload.body).not.toMatch(/inpac/i);
|
|
118
|
+
expect(payload.text).not.toMatch(/inpac/i);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("the text is built from the sanitized parts", () => {
|
|
122
|
+
const payload = skillPointPayload({
|
|
123
|
+
name: "inpac-pe-approval-workflow",
|
|
124
|
+
description: "the workflow",
|
|
125
|
+
body: "the body",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(payload.text).toContain("client-a-pe-approval-workflow");
|
|
129
|
+
expect(payload.text).not.toContain("inpac-pe-approval-workflow");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -138,8 +138,21 @@ describe("the identity of a skill point", () => {
|
|
|
138
138
|
body: "b",
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// The title is treated like the name: it is sanitized and it is
|
|
142
|
+
// brought into the shape of a skill name.
|
|
143
|
+
expect(withTitle.text).toContain("a-human-title");
|
|
144
|
+
expect(withoutTitle.text).not.toContain("a-human-title");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a title that is the name is not repeated in the text", () => {
|
|
148
|
+
const payload = skillPointPayload({
|
|
149
|
+
name: "a-skill",
|
|
150
|
+
title: "a-skill",
|
|
151
|
+
description: "d",
|
|
152
|
+
body: "b",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(payload.text.match(/a-skill/g)?.length).toBe(1);
|
|
143
156
|
});
|
|
144
157
|
});
|
|
145
158
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/two-phase-review",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.416",
|
|
4
4
|
"description": "pi-harness-runtime — two-phase implementation and review workflow (queue review signals, gate the phase, scope the diff)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|