pi-harness-runtime 1.1.413 → 1.1.415

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.
Files changed (135) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/logging/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/package.json +1 -1
  77. package/packages/release-manager/package.json +1 -1
  78. package/packages/requirement-compiler/package.json +1 -1
  79. package/packages/runtime/package.json +1 -1
  80. package/packages/scheduler/package.json +1 -1
  81. package/packages/scheduler-adapter/package.json +1 -1
  82. package/packages/session/package.json +1 -1
  83. package/packages/session-api/package.json +1 -1
  84. package/packages/session-export/package.json +1 -1
  85. package/packages/shared-context/package.json +1 -1
  86. package/packages/skill-mcp-client/package.json +1 -1
  87. package/packages/skill-registry/package.json +1 -1
  88. package/packages/skills/dist/index.d.ts +1 -0
  89. package/packages/skills/dist/index.d.ts.map +1 -1
  90. package/packages/skills/dist/index.js +1 -0
  91. package/packages/skills/dist/index.js.map +1 -1
  92. package/packages/skills/dist/loader.d.ts +19 -7
  93. package/packages/skills/dist/loader.d.ts.map +1 -1
  94. package/packages/skills/dist/loader.js +39 -22
  95. package/packages/skills/dist/loader.js.map +1 -1
  96. package/packages/skills/dist/sanitize.d.ts +27 -0
  97. package/packages/skills/dist/sanitize.d.ts.map +1 -0
  98. package/packages/skills/dist/sanitize.js +75 -0
  99. package/packages/skills/dist/sanitize.js.map +1 -0
  100. package/packages/skills/dist/vector-schema.d.ts +93 -4
  101. package/packages/skills/dist/vector-schema.d.ts.map +1 -1
  102. package/packages/skills/dist/vector-schema.js +71 -4
  103. package/packages/skills/dist/vector-schema.js.map +1 -1
  104. package/packages/skills/package.json +1 -1
  105. package/packages/skills/src/index.ts +1 -0
  106. package/packages/skills/src/loader.ts +53 -27
  107. package/packages/skills/src/sanitize.ts +86 -0
  108. package/packages/skills/src/vector-schema.ts +127 -4
  109. package/packages/skills/test/remote-skills.test.ts +41 -0
  110. package/packages/skills/test/sanitize.test.ts +88 -0
  111. package/packages/skills/test/vector-schema.test.ts +167 -0
  112. package/packages/sprint-planner/package.json +1 -1
  113. package/packages/subscription-engine/package.json +1 -1
  114. package/packages/task-compiler/package.json +1 -1
  115. package/packages/telegram/package.json +1 -1
  116. package/packages/tencentdb-memory/package.json +1 -1
  117. package/packages/tencentdb-sync/package.json +1 -1
  118. package/packages/test-data-generator/package.json +1 -1
  119. package/packages/test-generator/package.json +1 -1
  120. package/packages/todo-bd-sync/package.json +1 -1
  121. package/packages/token-estimation/package.json +1 -1
  122. package/packages/token-optimizer/package.json +1 -1
  123. package/packages/tooling-preference/package.json +1 -1
  124. package/packages/trajectory/package.json +1 -1
  125. package/packages/tui/package.json +1 -1
  126. package/packages/two-phase-review/package.json +1 -1
  127. package/packages/types/package.json +1 -1
  128. package/packages/voice-api/package.json +1 -1
  129. package/packages/webmcp-tester/package.json +1 -1
  130. package/packages/wiki/package.json +1 -1
  131. package/packages/workflow-events/package.json +1 -1
  132. package/packages/workspace-scanner/package.json +1 -1
  133. package/packages/worktree/package.json +1 -1
  134. package/packages/write-review/package.json +1 -1
  135. package/scripts/sync-to-qdrant.ts +29 -69
@@ -0,0 +1,75 @@
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
+ * The patterns of the client specific names.
15
+ *
16
+ * The list is about the names of the clients and the projects of the
17
+ * users of the harness - not about the skills themselves.
18
+ */
19
+ const SANITIZATION_RULES = [
20
+ // Client company names (case-insensitive)
21
+ { pattern: /\binpac\b/gi, replacement: "[Client A]" },
22
+ { pattern: /\bm-capital\b/gi, replacement: "[Client A]" },
23
+ { pattern: /\bmcapital\b/gi, replacement: "[Client A]" },
24
+ { pattern: /\bdigisoft\b/gi, replacement: "[Client B]" },
25
+ // Project/app specific names
26
+ { pattern: /\bgse-insurance\b/gi, replacement: "[Insurance Project]" },
27
+ { pattern: /\btbs-import\b/gi, replacement: "[Import Project]" },
28
+ { pattern: /\bdigisoft_erp\b/gi, replacement: "[Client B Erp]" },
29
+ { pattern: /\bm_capital\b/gi, replacement: "[Client A]" },
30
+ { pattern: /\binpac_\b/gi, replacement: "[Client A]_" },
31
+ { pattern: /\bpaperclip_\b/gi, replacement: "[Client D]_" },
32
+ { pattern: /\bopenclaw_\b/gi, replacement: "[Client F]_" },
33
+ // Generic replacements
34
+ { pattern: /Company Name/gi, replacement: "[Client Name]" },
35
+ { pattern: /your company/gi, replacement: "[Client]" },
36
+ { pattern: /our client/gi, replacement: "[Client]" },
37
+ { pattern: /client-specific/gi, replacement: "[Project-specific]" },
38
+ ];
39
+ /**
40
+ * The names that are renamed as a whole.
41
+ *
42
+ * A name that would be unreadable after the pattern rules - a name that
43
+ * carries the client name in its middle - is renamed here.
44
+ */
45
+ const SKILL_NAME_MAP = {
46
+ "gse-insurance-step5-slot-filter": "insurance-project-step-filter",
47
+ "inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
48
+ "inpac-po-approval-workflow": "client-a-po-approval-workflow",
49
+ "inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
50
+ "tbs-import-clearance-lcv": "import-project-clearance",
51
+ whispertool: "whisper-tool",
52
+ };
53
+ /**
54
+ * Replace the client specific names of a text.
55
+ *
56
+ * The text stays the same length, so a vector that was built from it
57
+ * keeps its shape.
58
+ */
59
+ export function sanitizeText(text) {
60
+ let result = text;
61
+ for (const rule of SANITIZATION_RULES) {
62
+ result = result.replace(rule.pattern, rule.replacement);
63
+ }
64
+ return result;
65
+ }
66
+ /**
67
+ * The name of a skill in the shared collection.
68
+ *
69
+ * The name is the identity of the point, so it has to be the same for
70
+ * every writer: the map first, then the pattern rules.
71
+ */
72
+ export function skillPointName(name) {
73
+ return SKILL_NAME_MAP[name] || sanitizeText(name);
74
+ }
75
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH;;;;;GAKG;AACH,MAAM,kBAAkB,GAAuB;IAC7C,0CAA0C;IAC1C,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE;IACrD,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE;IACzD,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE;IACxD,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE;IAExD,6BAA6B;IAC7B,EAAE,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACtE,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAChE,EAAE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,gBAAgB,EAAE;IAChE,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE;IACzD,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE;IACvD,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE;IAC3D,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE;IAE1D,uBAAuB;IACvB,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE;IAC3D,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE;IACtD,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE;IACpD,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,oBAAoB,EAAE;CACpE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,cAAc,GAA2B;IAC9C,iCAAiC,EAAE,+BAA+B;IAClE,4BAA4B,EAAE,+BAA+B;IAC7D,4BAA4B,EAAE,+BAA+B;IAC7D,4BAA4B,EAAE,+BAA+B;IAC7D,0BAA0B,EAAE,0BAA0B;IACtD,WAAW,EAAE,cAAc;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACxC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACvC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC"}
@@ -45,12 +45,101 @@ export interface SkillVectorSchema {
45
45
  model?: string;
46
46
  }
47
47
  /**
48
- * Why the collection does not match the expected schema - or null when it
49
- * does.
48
+ * The payload of a skill point.
50
49
  *
51
- * The message names the numbers, so the reader can tell WHICH schema the
52
- * collection was built with.
50
+ * Both writers of the collection - the runtime registry and
51
+ * `scripts/sync-to-qdrant.ts` - store the SAME payload under the SAME
52
+ * point id. A point is addressed by its id, so a writer that used a
53
+ * different id for the same skill would leave two points behind, and a
54
+ * writer whose id happened to collide with a foreign one would overwrite
55
+ * an unrelated skill.
53
56
  */
57
+ export type SkillPointPayload = {
58
+ /** The name of the skill. The point id is derived from it. */
59
+ name: string;
60
+ /** What the skill does. */
61
+ description: string;
62
+ /** The body of the skill. */
63
+ body: string;
64
+ /** The text the vector was built from. */
65
+ text: string;
66
+ };
67
+ /**
68
+ * The body of a payload is truncated.
69
+ *
70
+ * A payload is an index, not a file store - the complete skill comes from
71
+ * the skill service.
72
+ */
73
+ export declare const SKILL_PAYLOAD_BODY_LIMIT = 2000;
74
+ /** The text of a payload is truncated as well. */
75
+ export declare const SKILL_PAYLOAD_TEXT_LIMIT = 1500;
76
+ /**
77
+ * A stable hash of a string.
78
+ *
79
+ * The same string always produces the same number, in every process and
80
+ * on every machine - a point id has to survive a restart.
81
+ *
82
+ * Two uses:
83
+ * - the id of a point is the hash of the name its payload carries, so the
84
+ * point id and the payload name always belong together. Two writers
85
+ * that store the same name address the same point.
86
+ * - the hash of the text of a skill says whether its content changed.
87
+ */
88
+ export declare function stableHash(value: string): number;
89
+ /**
90
+ * The text a skill vector is built from.
91
+ *
92
+ * The title is added when it differs from the name: a skill whose
93
+ * directory is named differently from its title carries both.
94
+ */
95
+ export declare function skillPointText(input: {
96
+ /** The name the payload carries - the identity of the skill. */
97
+ name: string;
98
+ /** The title of the skill, when it differs from the name. */
99
+ title?: string;
100
+ /** What the skill does. */
101
+ description: string;
102
+ /** The body of the skill. */
103
+ body: string;
104
+ }): string;
105
+ /**
106
+ * A point of the skill collection.
107
+ *
108
+ * The id is the hash of the name the payload carries, so the point id and
109
+ * the payload name always belong together: two writers that store the
110
+ * same name address the same point.
111
+ */
112
+ export interface SkillQdrantPoint {
113
+ id: number;
114
+ payload: SkillPointPayload;
115
+ }
116
+ /**
117
+ * The point of a skill.
118
+ *
119
+ * The runtime registry and `scripts/sync-to-qdrant.ts` both build their
120
+ * points here, so they cannot drift apart. A writer that used its own id
121
+ * would leave a second point for every skill the other one indexes, and
122
+ * an id that happens to collide with a foreign one would overwrite an
123
+ * unrelated skill.
124
+ */
125
+ export declare function skillQdrantPoint(payload: SkillPointPayload): SkillQdrantPoint;
126
+ /**
127
+ * The payload of a skill point.
128
+ *
129
+ * The payload is sanitized: the collection is shared, so the client names
130
+ * of a user must not travel into it. Both writers use this function, so
131
+ * both write the same sanitized payload for the same skill.
132
+ */
133
+ export declare function skillPointPayload(input: {
134
+ /** The name the payload carries - the identity of the skill. */
135
+ name: string;
136
+ /** The title of the skill, when it differs from the name. */
137
+ title?: string;
138
+ /** What the skill does. */
139
+ description: string;
140
+ /** The body of the skill. */
141
+ body: string;
142
+ }): SkillPointPayload;
54
143
  export declare function describeSkillSchemaMismatch(actual: {
55
144
  dimensions?: number;
56
145
  distance?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"vector-schema.d.ts","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;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;;;;;;GAMG;AACH,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"}
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
  *
@@ -36,12 +37,78 @@ export const LEGACY_SKILL_COLLECTION_MODEL = "sentence-transformers/all-MiniLM-L
36
37
  /** The vector size of the legacy collection. */
37
38
  export const LEGACY_SKILL_COLLECTION_DIMENSIONS = 384;
38
39
  /**
39
- * Why the collection does not match the expected schema - or null when it
40
- * does.
40
+ * The body of a payload is truncated.
41
41
  *
42
- * The message names the numbers, so the reader can tell WHICH schema the
43
- * collection was built with.
42
+ * A payload is an index, not a file store - the complete skill comes from
43
+ * the skill service.
44
44
  */
45
+ export const SKILL_PAYLOAD_BODY_LIMIT = 2000;
46
+ /** The text of a payload is truncated as well. */
47
+ export const SKILL_PAYLOAD_TEXT_LIMIT = 1500;
48
+ /**
49
+ * A stable hash of a string.
50
+ *
51
+ * The same string always produces the same number, in every process and
52
+ * on every machine - a point id has to survive a restart.
53
+ *
54
+ * Two uses:
55
+ * - the id of a point is the hash of the name its payload carries, so the
56
+ * point id and the payload name always belong together. Two writers
57
+ * that store the same name address the same point.
58
+ * - the hash of the text of a skill says whether its content changed.
59
+ */
60
+ export function stableHash(value) {
61
+ let hash = 0;
62
+ for (let i = 0; i < value.length; i++) {
63
+ hash = (Math.imul(31, hash) + value.charCodeAt(i)) | 0;
64
+ }
65
+ return Math.abs(hash);
66
+ }
67
+ /**
68
+ * The text a skill vector is built from.
69
+ *
70
+ * The title is added when it differs from the name: a skill whose
71
+ * directory is named differently from its title carries both.
72
+ */
73
+ export function skillPointText(input) {
74
+ const parts = [input.name];
75
+ if (input.title && input.title !== input.name)
76
+ parts.push(input.title);
77
+ parts.push(input.description);
78
+ parts.push(input.body.slice(0, SKILL_PAYLOAD_TEXT_LIMIT));
79
+ return parts.join(" ");
80
+ }
81
+ /**
82
+ * The point of a skill.
83
+ *
84
+ * The runtime registry and `scripts/sync-to-qdrant.ts` both build their
85
+ * points here, so they cannot drift apart. A writer that used its own id
86
+ * would leave a second point for every skill the other one indexes, and
87
+ * an id that happens to collide with a foreign one would overwrite an
88
+ * unrelated skill.
89
+ */
90
+ export function skillQdrantPoint(payload) {
91
+ return { id: stableHash(payload.name), payload };
92
+ }
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.
99
+ */
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);
105
+ return {
106
+ name,
107
+ description,
108
+ body,
109
+ text: skillPointText({ name, title, description, body }),
110
+ };
111
+ }
45
112
  export function describeSkillSchemaMismatch(actual, expected) {
46
113
  if (actual.dimensions !== undefined && actual.dimensions !== expected.dimensions) {
47
114
  const model = expected.model ? ` (${expected.model})` : "";
@@ -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;AAYtD;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAC1C,MAAkD,EAClD,QAA2B;IAE3B,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC;QAClF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,wBAAwB,MAAM,CAAC,UAAU,iDAAiD,QAAQ,CAAC,UAAU,GAAG,KAAK,EAAE,CAAC;IAChI,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC5E,OAAO,2BAA2B,MAAM,CAAC,QAAQ,kCAAkC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7D;;;;;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;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KASjC;IACC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC;IACzE,OAAO;QACL,IAAI;QACJ,WAAW;QACX,IAAI;QACJ,IAAI,EAAE,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;KACzD,CAAC;AACJ,CAAC;AACD,MAAM,UAAU,2BAA2B,CAC1C,MAAkD,EAClD,QAA2B;IAE3B,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC;QAClF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,wBAAwB,MAAM,CAAC,UAAU,iDAAiD,QAAQ,CAAC,UAAU,GAAG,KAAK,EAAE,CAAC;IAChI,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC5E,OAAO,2BAA2B,MAAM,CAAC,QAAQ,kCAAkC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/skills",
3
- "version": "1.1.413",
3
+ "version": "1.1.415",
4
4
  "description": "File-based skill system with pi.dev compatibility and Hermes self-improvement",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -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";
@@ -22,8 +22,13 @@ import {
22
22
  LEGACY_SKILL_COLLECTION_MODEL,
23
23
  SKILL_COLLECTION,
24
24
  describeSkillSchemaMismatch,
25
+ skillPointPayload,
26
+ skillQdrantPoint,
27
+ stableHash,
28
+ type SkillPointPayload,
25
29
  type SkillVectorSchema,
26
30
  } from "./vector-schema.js";
31
+ import { skillPointName } from "./sanitize.js";
27
32
  import {
28
33
  describeRemoteFailure,
29
34
  isStale,
@@ -39,11 +44,15 @@ import {
39
44
  // At runtime, packages/ is resolved from the monorepo root or node_modules.
40
45
 
41
46
  export interface QdrantSkillPoint {
47
+ /**
48
+ * The id of the point.
49
+ *
50
+ * It is the hash of the name in the payload, so the point id and the
51
+ * payload name always belong together.
52
+ */
42
53
  id: number;
43
- name: string;
44
- description: string;
45
- body: string;
46
- text: string;
54
+ /** The payload of the point. */
55
+ payload: SkillPointPayload;
47
56
  }
48
57
 
49
58
  export interface QdrantClientLike {
@@ -149,7 +158,7 @@ const RUNTIME_VECTOR_DISTANCE = "Cosine";
149
158
  export interface SkillVectorPoint {
150
159
  id: number;
151
160
  vector: number[];
152
- payload: QdrantSkillPoint;
161
+ payload: SkillPointPayload;
153
162
  }
154
163
 
155
164
  /**
@@ -331,6 +340,15 @@ export class SkillRegistry {
331
340
  * disk and how many it can reach through the vector store.
332
341
  */
333
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();
334
352
  /**
335
353
  * The remote skills whose content is COMPLETE.
336
354
  *
@@ -613,13 +631,17 @@ export class SkillRegistry {
613
631
 
614
632
  /** The point of a skill in the vector index. */
615
633
  private _toQdrantPoint(skill: Skill): QdrantSkillPoint {
616
- return {
617
- id: this._hashId(skill.id),
634
+ // The payload and the point id come from the shared schema, so the
635
+ // runtime and `scripts/sync-to-qdrant.ts` store the same point for the
636
+ // same skill. A writer that used its own id would leave two points
637
+ // behind - or overwrite an unrelated one.
638
+ const payload = skillPointPayload({
618
639
  name: skill.id,
640
+ title: skill.frontmatter.name,
619
641
  description: skill.frontmatter.description,
620
- body: skill.body.slice(0, 2000),
621
- text: [skill.id, skill.frontmatter.name, skill.frontmatter.description, skill.body.slice(0, 1500)].join(" "),
622
- };
642
+ body: skill.body,
643
+ });
644
+ return skillQdrantPoint(payload);
623
645
  }
624
646
 
625
647
  /**
@@ -677,7 +699,7 @@ export class SkillRegistry {
677
699
  */
678
700
  private _scheduleIndex(skill: Skill): void {
679
701
  const point = this._toQdrantPoint(skill);
680
- const hash = this._hashId(point.text);
702
+ const hash = stableHash(point.payload.text);
681
703
  if (this._indexedHashes.get(skill.id) === hash) return;
682
704
 
683
705
  this._indexedHashes.set(skill.id, hash);
@@ -729,12 +751,12 @@ export class SkillRegistry {
729
751
  while (this._indexQueue.length > 0) {
730
752
  const batch = this._indexQueue.splice(0, this._batchSize);
731
753
  try {
732
- const vectors = await indexer.embed(batch.map((item) => item.point.text));
754
+ const vectors = await indexer.embed(batch.map((item) => item.point.payload.text));
733
755
  const points: SkillVectorPoint[] = [];
734
756
  batch.forEach((item, position) => {
735
757
  const vector = vectors[position];
736
758
  if (!vector || vector.length === 0) return;
737
- points.push({ id: item.point.id, vector, payload: item.point });
759
+ points.push({ id: item.point.id, vector, payload: item.point.payload });
738
760
  });
739
761
  if (points.length > 0) await indexer.upsert(points);
740
762
  } catch {
@@ -749,20 +771,13 @@ export class SkillRegistry {
749
771
  }
750
772
  }
751
773
 
752
- /** Simple deterministic ID for Qdrant (no bigints needed). */
753
- private _hashId(id: string): number {
754
- let h = 0;
755
- for (let i = 0; i < id.length; i++) {
756
- h = (Math.imul(31, h) + id.charCodeAt(i)) | 0;
757
- }
758
- return Math.abs(h);
759
- }
760
-
761
774
  /**
762
775
  * Register a skill (auto-indexes to Qdrant if configured).
763
776
  */
764
777
  register(skill: Skill): void {
765
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);
766
781
  this.updateIndex(skill);
767
782
  // Queue the skill for the vector index without blocking registration.
768
783
  this._scheduleIndex(skill);
@@ -775,6 +790,12 @@ export class SkillRegistry {
775
790
  const skill = this.skills.get(skillId);
776
791
  if (skill) {
777
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
+ }
778
799
  this.index.entries = this.index.entries.filter(
779
800
  (e) => e.id !== skillId
780
801
  );
@@ -788,8 +809,13 @@ export class SkillRegistry {
788
809
  */
789
810
  get(skillId: string): Skill | undefined {
790
811
  // A server skill is only known after a vector search found it, so it is
791
- // looked up second - the local skill always wins.
792
- return this.skills.get(skillId) ?? this.remoteSkills.get(skillId);
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
+ );
793
819
  }
794
820
 
795
821
  /**
@@ -1051,7 +1077,7 @@ export class SkillRegistry {
1051
1077
  const payload = pt.payload ?? {};
1052
1078
  const name = typeof payload.name === "string" ? payload.name : undefined;
1053
1079
  if (!name) continue;
1054
- const local = this.skills.get(name);
1080
+ const local = this._skillsByPointName.get(name) ?? this.skills.get(name);
1055
1081
  if (local) {
1056
1082
  results.push({ skill: local, score: pt.score, source: "local" });
1057
1083
  continue;
@@ -1084,12 +1110,12 @@ export class SkillRegistry {
1084
1110
  const batch = skills.slice(i, i + this._batchSize);
1085
1111
  const points = batch.map((skill) => this._toQdrantPoint(skill));
1086
1112
  try {
1087
- const vectors = await indexer.embed(points.map((point) => point.text));
1113
+ const vectors = await indexer.embed(points.map((point) => point.payload.text));
1088
1114
  const payload: SkillVectorPoint[] = [];
1089
1115
  points.forEach((point, position) => {
1090
1116
  const vector = vectors[position];
1091
1117
  if (!vector || vector.length === 0) return;
1092
- payload.push({ id: point.id, vector, payload: point });
1118
+ payload.push({ id: point.id, vector, payload: point.payload });
1093
1119
  });
1094
1120
  if (payload.length > 0) await indexer.upsert(payload);
1095
1121
  } catch {
@@ -0,0 +1,86 @@
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
+ const SANITIZATION_RULES: SanitizationRule[] = [
27
+ // Client company names (case-insensitive)
28
+ { pattern: /\binpac\b/gi, replacement: "[Client A]" },
29
+ { pattern: /\bm-capital\b/gi, replacement: "[Client A]" },
30
+ { pattern: /\bmcapital\b/gi, replacement: "[Client A]" },
31
+ { pattern: /\bdigisoft\b/gi, replacement: "[Client B]" },
32
+
33
+ // Project/app specific names
34
+ { pattern: /\bgse-insurance\b/gi, replacement: "[Insurance Project]" },
35
+ { pattern: /\btbs-import\b/gi, replacement: "[Import Project]" },
36
+ { pattern: /\bdigisoft_erp\b/gi, replacement: "[Client B Erp]" },
37
+ { pattern: /\bm_capital\b/gi, replacement: "[Client A]" },
38
+ { pattern: /\binpac_\b/gi, replacement: "[Client A]_" },
39
+ { pattern: /\bpaperclip_\b/gi, replacement: "[Client D]_" },
40
+ { pattern: /\bopenclaw_\b/gi, replacement: "[Client F]_" },
41
+
42
+ // Generic replacements
43
+ { pattern: /Company Name/gi, replacement: "[Client Name]" },
44
+ { pattern: /your company/gi, replacement: "[Client]" },
45
+ { pattern: /our client/gi, replacement: "[Client]" },
46
+ { pattern: /client-specific/gi, replacement: "[Project-specific]" },
47
+ ];
48
+
49
+ /**
50
+ * The names that are renamed as a whole.
51
+ *
52
+ * A name that would be unreadable after the pattern rules - a name that
53
+ * carries the client name in its middle - is renamed here.
54
+ */
55
+ const SKILL_NAME_MAP: Record<string, string> = {
56
+ "gse-insurance-step5-slot-filter": "insurance-project-step-filter",
57
+ "inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
58
+ "inpac-po-approval-workflow": "client-a-po-approval-workflow",
59
+ "inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
60
+ "tbs-import-clearance-lcv": "import-project-clearance",
61
+ whispertool: "whisper-tool",
62
+ };
63
+
64
+ /**
65
+ * Replace the client specific names of a text.
66
+ *
67
+ * The text stays the same length, so a vector that was built from it
68
+ * keeps its shape.
69
+ */
70
+ export function sanitizeText(text: string): string {
71
+ let result = text;
72
+ for (const rule of SANITIZATION_RULES) {
73
+ result = result.replace(rule.pattern, rule.replacement);
74
+ }
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * The name of a skill in the shared collection.
80
+ *
81
+ * The name is the identity of the point, so it has to be the same for
82
+ * every writer: the map first, then the pattern rules.
83
+ */
84
+ export function skillPointName(name: string): string {
85
+ return SKILL_NAME_MAP[name] || sanitizeText(name);
86
+ }