pi-harness-runtime 1.1.164 → 1.1.166

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 (111) 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/context-compiler/package.json +1 -1
  20. package/packages/context-discovery/package.json +1 -1
  21. package/packages/context-manager/package.json +1 -1
  22. package/packages/cookie-sanitizer/package.json +1 -1
  23. package/packages/cost-optimizer/package.json +1 -1
  24. package/packages/dependency-analyzer/package.json +1 -1
  25. package/packages/django-plugin/package.json +1 -1
  26. package/packages/doc-generator/package.json +1 -1
  27. package/packages/evaluation-engine/package.json +1 -1
  28. package/packages/evaluation-runner/package.json +1 -1
  29. package/packages/event-bus/package.json +1 -1
  30. package/packages/event-store/package.json +1 -1
  31. package/packages/experience-replay/package.json +1 -1
  32. package/packages/feedback-collector/package.json +1 -1
  33. package/packages/file-copy-helper/package.json +1 -1
  34. package/packages/framework-detector/package.json +1 -1
  35. package/packages/framework-plugin-sdk/package.json +1 -1
  36. package/packages/frappe-plugin/package.json +1 -1
  37. package/packages/generic-web-plugin/package.json +1 -1
  38. package/packages/health-monitor/package.json +1 -1
  39. package/packages/intent-analyzer/package.json +1 -1
  40. package/packages/jev-judge/package.json +1 -1
  41. package/packages/knowledge-graph/package.json +1 -1
  42. package/packages/knowledge-retrieval/package.json +1 -1
  43. package/packages/laravel-plugin/package.json +1 -1
  44. package/packages/learning-engine/package.json +1 -1
  45. package/packages/mcp-adapter/package.json +1 -1
  46. package/packages/memory-engine/package.json +1 -1
  47. package/packages/milestone-manager/package.json +1 -1
  48. package/packages/model-registry/package.json +1 -1
  49. package/packages/nextjs-plugin/package.json +1 -1
  50. package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
  51. package/packages/notification/dist/adapters/telegram-adapter.js +13 -0
  52. package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
  53. package/packages/notification/dist/index.d.ts +1 -0
  54. package/packages/notification/dist/index.d.ts.map +1 -1
  55. package/packages/notification/dist/index.js +1 -0
  56. package/packages/notification/dist/index.js.map +1 -1
  57. package/packages/notification/dist/telegram-content-filter.d.ts +36 -0
  58. package/packages/notification/dist/telegram-content-filter.d.ts.map +1 -0
  59. package/packages/notification/dist/telegram-content-filter.js +176 -0
  60. package/packages/notification/dist/telegram-content-filter.js.map +1 -0
  61. package/packages/notification/package.json +1 -1
  62. package/packages/notification/src/adapters/telegram-adapter.ts +16 -0
  63. package/packages/notification/src/index.ts +4 -0
  64. package/packages/notification/src/telegram-content-filter.ts +225 -0
  65. package/packages/observability/package.json +1 -1
  66. package/packages/okf-indexer/package.json +1 -1
  67. package/packages/paperclip-integration/package.json +1 -1
  68. package/packages/performance-optimizer/package.json +1 -1
  69. package/packages/privilege-broker/package.json +1 -1
  70. package/packages/project-analyzer/package.json +1 -1
  71. package/packages/project-bootstrap/package.json +1 -1
  72. package/packages/projection-engine/package.json +1 -1
  73. package/packages/prompt-compiler/package.json +1 -1
  74. package/packages/prompt-versioning/package.json +1 -1
  75. package/packages/provider-adapter-sdk/package.json +1 -1
  76. package/packages/provider-router/package.json +1 -1
  77. package/packages/provider-selector/package.json +1 -1
  78. package/packages/providers/package.json +1 -1
  79. package/packages/qdrant-skills/package.json +1 -1
  80. package/packages/quota-manager/package.json +1 -1
  81. package/packages/rate-limiter/package.json +1 -1
  82. package/packages/react-vite-plugin/package.json +1 -1
  83. package/packages/release-manager/package.json +1 -1
  84. package/packages/requirement-compiler/package.json +1 -1
  85. package/packages/runtime/package.json +1 -1
  86. package/packages/scheduler/package.json +1 -1
  87. package/packages/scheduler-adapter/package.json +1 -1
  88. package/packages/session/package.json +1 -1
  89. package/packages/session-api/package.json +1 -1
  90. package/packages/session-export/package.json +1 -1
  91. package/packages/shared-context/package.json +1 -1
  92. package/packages/skill-mcp-client/package.json +1 -1
  93. package/packages/skill-registry/package.json +1 -1
  94. package/packages/skills/package.json +1 -1
  95. package/packages/sprint-planner/package.json +1 -1
  96. package/packages/subscription-engine/package.json +1 -1
  97. package/packages/task-compiler/package.json +1 -1
  98. package/packages/tencentdb-memory/package.json +1 -1
  99. package/packages/tencentdb-sync/package.json +1 -1
  100. package/packages/test-data-generator/package.json +1 -1
  101. package/packages/test-generator/package.json +1 -1
  102. package/packages/todo-bd-sync/package.json +1 -1
  103. package/packages/token-estimation/package.json +1 -1
  104. package/packages/token-optimizer/package.json +1 -1
  105. package/packages/trajectory/package.json +1 -1
  106. package/packages/tui/package.json +1 -1
  107. package/packages/types/package.json +1 -1
  108. package/packages/workflow-events/package.json +1 -1
  109. package/packages/workspace-scanner/package.json +1 -1
  110. package/packages/worktree/package.json +1 -1
  111. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Telegram Content Filter — Jev-Powered Sensitive Content Detection
3
+ *
4
+ * Before sending to Telegram, this module analyzes content for sensitive
5
+ * credentials that should NOT be transmitted.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ /**
11
+ * Get API key from key file
12
+ */
13
+ function getKeyFromFile(keyName) {
14
+ const keyPath = join(homedir(), ".pi-harness-runtime", "keys", `${keyName}.txt`);
15
+ if (existsSync(keyPath)) {
16
+ try {
17
+ return readFileSync(keyPath, "utf8").trim();
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ /**
26
+ * Extract text content from notification payload
27
+ */
28
+ function extractContent(payload) {
29
+ const parts = [];
30
+ if (payload.title)
31
+ parts.push(payload.title);
32
+ if (payload.message)
33
+ parts.push(payload.message);
34
+ if (payload.details) {
35
+ for (const [_key, value] of Object.entries(payload.details)) {
36
+ if (typeof value === "string" && value) {
37
+ parts.push(value);
38
+ }
39
+ }
40
+ }
41
+ return parts.join("\n");
42
+ }
43
+ /**
44
+ * Simple heuristic check for obvious credentials (fallback when Jev unavailable)
45
+ */
46
+ function heuristicCheck(content) {
47
+ // Check for common credential patterns
48
+ const sensitivePatterns = [
49
+ /\d+:[-A-Za-z0-9_]{35,}/, // Telegram bot token
50
+ /sk-[a-zA-Z0-9]{20,}/, // OpenAI-style keys
51
+ /[a-zA-Z0-9_-]{32,}/, // Long API keys
52
+ /Bearer\s+[a-zA-Z0-9_-]{20,}/i, // Bearer tokens
53
+ /(api[_-]?key|secret|token|password|credential)[=:\s]+[^\s]{16,}/i,
54
+ /-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/,
55
+ /infisical[_-]?(client[_-])?(secret|id)/i,
56
+ ];
57
+ for (const pattern of sensitivePatterns) {
58
+ if (pattern.test(content)) {
59
+ let matchType = "api_key_or_secret";
60
+ if (/\d+:[-A-Za-z0-9_]{35,}/.test(content)) {
61
+ matchType = "telegram_bot_token";
62
+ }
63
+ else if (/sk-[a-zA-Z0-9]{20,}/.test(content)) {
64
+ matchType = "openai_style_key";
65
+ }
66
+ else if (/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/.test(content)) {
67
+ matchType = "private_key";
68
+ }
69
+ else if (/infisical/i.test(content)) {
70
+ matchType = "infisical_credential";
71
+ }
72
+ return {
73
+ shouldSend: false,
74
+ judgment: "BLOCK",
75
+ reason: `Sensitive content detected: ${matchType}`,
76
+ confidence: 0.95,
77
+ };
78
+ }
79
+ }
80
+ return {
81
+ shouldSend: true,
82
+ judgment: "SEND",
83
+ confidence: 0.9,
84
+ };
85
+ }
86
+ /**
87
+ * Call Jev to analyze content for sensitive credentials
88
+ */
89
+ async function callJev(content) {
90
+ // Get API key from environment or key file
91
+ const apiKey = process.env.TYPESAFE_API_KEY || getKeyFromFile("jev-api-key") || getKeyFromFile("typesafe-api-key");
92
+ if (!apiKey) {
93
+ return null; // Jev not available
94
+ }
95
+ try {
96
+ const response = await fetch("https://api.typesafe.ai/v1/jev/decide", {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ Authorization: `Bearer ${apiKey}`,
101
+ },
102
+ body: JSON.stringify({
103
+ question: "Does this message contain any API keys, tokens, secrets, passwords, or other sensitive credentials that should NOT be sent to a third-party service like Telegram? Answer only YES or NO.",
104
+ context: {
105
+ message: content,
106
+ action: "telegram_notification",
107
+ security_level: "high",
108
+ },
109
+ }),
110
+ });
111
+ if (!response.ok) {
112
+ return null;
113
+ }
114
+ const data = (await response.json());
115
+ const decision = data.decision?.toUpperCase() ?? "UNKNOWN";
116
+ const isSensitive = decision === "YES" || decision === "BLOCK";
117
+ return {
118
+ shouldSend: !isSensitive,
119
+ judgment: isSensitive ? "BLOCK" : "SEND",
120
+ reason: data.reasoning,
121
+ confidence: data.confidence,
122
+ };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * Filter notification content before sending to Telegram
130
+ *
131
+ * @param payload - The notification payload
132
+ * @returns Filter result with decision
133
+ */
134
+ export async function filterTelegramContent(payload) {
135
+ const content = extractContent(payload);
136
+ // First, run heuristic check (always available)
137
+ const heuristicResult = heuristicCheck(content);
138
+ if (heuristicResult.judgment === "BLOCK") {
139
+ logFilterDecision("BLOCK", heuristicResult.reason ?? "Heuristic match", payload.jobId);
140
+ return heuristicResult;
141
+ }
142
+ // Try Jev if available
143
+ const jevResult = await callJev(content);
144
+ if (jevResult) {
145
+ if (jevResult.judgment === "BLOCK") {
146
+ logFilterDecision("BLOCK", jevResult.reason ?? "Jev judgment", payload.jobId);
147
+ }
148
+ return jevResult;
149
+ }
150
+ // Jev unavailable - use heuristic result
151
+ return heuristicResult;
152
+ }
153
+ /**
154
+ * Log filter decision securely (no content, just metadata)
155
+ */
156
+ function logFilterDecision(decision, reason, jobId) {
157
+ console.warn(`[TelegramFilter] ${decision} job=${jobId} reason=${reason} channel=telegram`);
158
+ }
159
+ /**
160
+ * Mask a value safely for logging
161
+ */
162
+ function maskValue(value) {
163
+ if (!value || value.length <= 8)
164
+ return "****";
165
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
166
+ }
167
+ /**
168
+ * Get safe notification version (masked) for logging
169
+ */
170
+ export function getSafeNotificationLog(payload) {
171
+ const maskedTitle = maskValue(payload.title);
172
+ const maskedMessage = maskValue(payload.message);
173
+ const maskedJobId = maskValue(payload.jobId);
174
+ return `event=${payload.event} job=${maskedJobId} title=${maskedTitle} message_len=${maskedMessage.length}`;
175
+ }
176
+ //# sourceMappingURL=telegram-content-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-content-filter.js","sourceRoot":"","sources":["../src/telegram-content-filter.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/notification",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "pi-harness-runtime — notification",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,7 @@ import type {
19
19
  } from "../types.js";
20
20
  import { BaseChannelAdapter } from "../base-adapter.js";
21
21
  import { maskString } from "../mask-secrets.js";
22
+ import { filterTelegramContent, getSafeNotificationLog } from "../telegram-content-filter.js";
22
23
 
23
24
  export class TelegramAdapter extends BaseChannelAdapter {
24
25
  readonly id = "telegram";
@@ -71,6 +72,21 @@ return this._botUsername;
71
72
  */
72
73
  async send(payload: NotificationPayload): Promise<NotificationResult> {
73
74
  try {
75
+ // SECURITY: Jev-powered content filter - block if contains sensitive credentials
76
+ const filterResult = await filterTelegramContent(payload);
77
+ if (!filterResult.shouldSend) {
78
+ console.warn(
79
+ `[TelegramAdapter] BLOCKED by Jev judgment=${filterResult.judgment} reason=${filterResult.reason} job=${payload.jobId}`,
80
+ );
81
+ // Log safe notification metadata (no secrets)
82
+ console.log(`[TelegramAdapter] ${getSafeNotificationLog(payload)}`);
83
+ return {
84
+ success: false,
85
+ channel: this.id,
86
+ error: "Content blocked: sensitive credentials detected",
87
+ };
88
+ }
89
+
74
90
  const cfg = this.config.config as TelegramConfig;
75
91
  const message = this.formatMessage(payload);
76
92
 
@@ -17,3 +17,7 @@ export {
17
17
  maskPayload,
18
18
  isSensitiveField,
19
19
  } from "./mask-secrets.js";
20
+ export {
21
+ filterTelegramContent,
22
+ getSafeNotificationLog,
23
+ } from "./telegram-content-filter.js";
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Telegram Content Filter — Jev-Powered Sensitive Content Detection
3
+ *
4
+ * Before sending to Telegram, this module analyzes content for sensitive
5
+ * credentials that should NOT be transmitted.
6
+ */
7
+
8
+ import type { NotificationPayload } from "./types.js";
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { homedir } from "node:os";
12
+
13
+ /**
14
+ * Get API key from key file
15
+ */
16
+ function getKeyFromFile(keyName: string): string | undefined {
17
+ const keyPath = join(homedir(), ".pi-harness-runtime", "keys", `${keyName}.txt`);
18
+ if (existsSync(keyPath)) {
19
+ try {
20
+ return readFileSync(keyPath, "utf8").trim();
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+
28
+ /**
29
+ * Jev judgment result for content filtering
30
+ */
31
+ export type ContentJudgment = "SEND" | "BLOCK" | "UNKNOWN";
32
+
33
+ /**
34
+ * Result of content filtering decision
35
+ */
36
+ export interface FilterResult {
37
+ /** Whether to send to Telegram */
38
+ shouldSend: boolean;
39
+ /** Jev's judgment */
40
+ judgment: ContentJudgment;
41
+ /** Reason for blocking (if blocked) */
42
+ reason?: string;
43
+ /** Confidence score (0-1) */
44
+ confidence?: number;
45
+ }
46
+
47
+ /**
48
+ * Extract text content from notification payload
49
+ */
50
+ function extractContent(payload: NotificationPayload): string {
51
+ const parts: string[] = [];
52
+
53
+ if (payload.title) parts.push(payload.title);
54
+ if (payload.message) parts.push(payload.message);
55
+
56
+ if (payload.details) {
57
+ for (const [_key, value] of Object.entries(payload.details)) {
58
+ if (typeof value === "string" && value) {
59
+ parts.push(value);
60
+ }
61
+ }
62
+ }
63
+
64
+ return parts.join("\n");
65
+ }
66
+
67
+ /**
68
+ * Simple heuristic check for obvious credentials (fallback when Jev unavailable)
69
+ */
70
+ function heuristicCheck(content: string): FilterResult {
71
+ // Check for common credential patterns
72
+ const sensitivePatterns = [
73
+ /\d+:[-A-Za-z0-9_]{35,}/, // Telegram bot token
74
+ /sk-[a-zA-Z0-9]{20,}/, // OpenAI-style keys
75
+ /[a-zA-Z0-9_-]{32,}/, // Long API keys
76
+ /Bearer\s+[a-zA-Z0-9_-]{20,}/i, // Bearer tokens
77
+ /(api[_-]?key|secret|token|password|credential)[=:\s]+[^\s]{16,}/i,
78
+ /-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/,
79
+ /infisical[_-]?(client[_-])?(secret|id)/i,
80
+ ];
81
+
82
+ for (const pattern of sensitivePatterns) {
83
+ if (pattern.test(content)) {
84
+ let matchType = "api_key_or_secret";
85
+ if (/\d+:[-A-Za-z0-9_]{35,}/.test(content)) {
86
+ matchType = "telegram_bot_token";
87
+ } else if (/sk-[a-zA-Z0-9]{20,}/.test(content)) {
88
+ matchType = "openai_style_key";
89
+ } else if (/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/.test(content)) {
90
+ matchType = "private_key";
91
+ } else if (/infisical/i.test(content)) {
92
+ matchType = "infisical_credential";
93
+ }
94
+
95
+ return {
96
+ shouldSend: false,
97
+ judgment: "BLOCK",
98
+ reason: `Sensitive content detected: ${matchType}`,
99
+ confidence: 0.95,
100
+ };
101
+ }
102
+ }
103
+
104
+ return {
105
+ shouldSend: true,
106
+ judgment: "SEND",
107
+ confidence: 0.9,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Call Jev to analyze content for sensitive credentials
113
+ */
114
+ async function callJev(content: string): Promise<FilterResult | null> {
115
+ // Get API key from environment or key file
116
+ const apiKey = process.env.TYPESAFE_API_KEY || getKeyFromFile("jev-api-key") || getKeyFromFile("typesafe-api-key");
117
+
118
+ if (!apiKey) {
119
+ return null; // Jev not available
120
+ }
121
+
122
+ try {
123
+ const response = await fetch("https://api.typesafe.ai/v1/jev/decide", {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${apiKey}`,
128
+ },
129
+ body: JSON.stringify({
130
+ question:
131
+ "Does this message contain any API keys, tokens, secrets, passwords, or other sensitive credentials that should NOT be sent to a third-party service like Telegram? Answer only YES or NO.",
132
+ context: {
133
+ message: content,
134
+ action: "telegram_notification",
135
+ security_level: "high",
136
+ },
137
+ }),
138
+ });
139
+
140
+ if (!response.ok) {
141
+ return null;
142
+ }
143
+
144
+ const data = (await response.json()) as {
145
+ decision?: string;
146
+ confidence?: number;
147
+ reasoning?: string;
148
+ };
149
+
150
+ const decision = data.decision?.toUpperCase() ?? "UNKNOWN";
151
+ const isSensitive = decision === "YES" || decision === "BLOCK";
152
+
153
+ return {
154
+ shouldSend: !isSensitive,
155
+ judgment: isSensitive ? "BLOCK" : "SEND",
156
+ reason: data.reasoning,
157
+ confidence: data.confidence,
158
+ };
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Filter notification content before sending to Telegram
166
+ *
167
+ * @param payload - The notification payload
168
+ * @returns Filter result with decision
169
+ */
170
+ export async function filterTelegramContent(
171
+ payload: NotificationPayload,
172
+ ): Promise<FilterResult> {
173
+ const content = extractContent(payload);
174
+
175
+ // First, run heuristic check (always available)
176
+ const heuristicResult = heuristicCheck(content);
177
+ if (heuristicResult.judgment === "BLOCK") {
178
+ logFilterDecision("BLOCK", heuristicResult.reason ?? "Heuristic match", payload.jobId);
179
+ return heuristicResult;
180
+ }
181
+
182
+ // Try Jev if available
183
+ const jevResult = await callJev(content);
184
+ if (jevResult) {
185
+ if (jevResult.judgment === "BLOCK") {
186
+ logFilterDecision("BLOCK", jevResult.reason ?? "Jev judgment", payload.jobId);
187
+ }
188
+ return jevResult;
189
+ }
190
+
191
+ // Jev unavailable - use heuristic result
192
+ return heuristicResult;
193
+ }
194
+
195
+ /**
196
+ * Log filter decision securely (no content, just metadata)
197
+ */
198
+ function logFilterDecision(
199
+ decision: ContentJudgment,
200
+ reason: string,
201
+ jobId: string,
202
+ ): void {
203
+ console.warn(
204
+ `[TelegramFilter] ${decision} job=${jobId} reason=${reason} channel=telegram`,
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Mask a value safely for logging
210
+ */
211
+ function maskValue(value: string): string {
212
+ if (!value || value.length <= 8) return "****";
213
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
214
+ }
215
+
216
+ /**
217
+ * Get safe notification version (masked) for logging
218
+ */
219
+ export function getSafeNotificationLog(payload: NotificationPayload): string {
220
+ const maskedTitle = maskValue(payload.title);
221
+ const maskedMessage = maskValue(payload.message);
222
+ const maskedJobId = maskValue(payload.jobId);
223
+
224
+ return `event=${payload.event} job=${maskedJobId} title=${maskedTitle} message_len=${maskedMessage.length}`;
225
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/observability",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Comprehensive observability system with structured logging, tracing, metrics, and alerting",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/okf-indexer",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "OKF document indexer for SKILL.md files (RFC-0106)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/paperclip-integration",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Paperclip API integration: poll agent inbox, route tasks to pi.dev, Telegram alerts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/performance-optimizer",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Performance optimization analyzer for pi-harness runtime (RFC-0056)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/privilege-broker",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Privilege Broker — capability registry and command executor for autonomous runtime (RFC-0101 §7)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/project-analyzer",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Project analysis system for pi-harness-runtime - detects frameworks, rules, and project structure",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/project-bootstrap",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Project scaffolding from typed ProjectSpec",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/projection-engine",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/prompt-compiler",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Deterministic prompt compiler for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/prompt-versioning",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "type": "module",
5
5
  "description": "Version control for prompts with rollback support (RFC-0019)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/provider-adapter-sdk",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "SDK for building, testing, and registering AI provider adapters",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/provider-router",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Enhanced Provider Router for pi-harness-runtime — intelligent routing with capabilities, costs, and quotas",
5
5
  "type": "module",
6
6
  "main": "./dist/provider-router.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/provider-selector",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Multi-Provider Selector — route requests by cost, quality, and latency (RFC-0012)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/providers",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "pi-harness-runtime — providers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/qdrant-skills",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Qdrant vector search integration for Hermes-style skill system",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/quota-manager",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "pi-harness-runtime — quota-manager",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/rate-limiter",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "type": "module",
5
5
  "description": "Rate limiting with token bucket algorithm (RFC-0023)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/react-vite-plugin",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "React/Vite framework analysis — components, config, TypeScript setup",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/release-manager",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "type": "module",
5
5
  "description": "Release management for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/requirement-compiler",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "Compiles raw requirements into validated CompiledRequirement with ambiguity detection and risk tagging",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/runtime",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "pi-harness-runtime — runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/scheduler",
3
- "version": "1.1.164",
3
+ "version": "1.1.166",
4
4
  "description": "pi-harness-runtime — scheduler",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",