pi-harness-runtime 1.1.161 → 1.1.163
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/harness/telegram-auth.ts +150 -0
- package/harness/telegram-content-filter.ts +223 -0
- 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/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +17 -12
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/mask-secrets.d.ts.map +1 -1
- package/packages/notification/dist/mask-secrets.js +9 -0
- package/packages/notification/dist/mask-secrets.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +19 -12
- package/packages/notification/src/mask-secrets.ts +11 -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/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/telegram-webhook-server.ts +85 -6
- package/scripts/tsconfig.json +9 -0
package/README.md
CHANGED
|
@@ -844,6 +844,27 @@ USER A (you):
|
|
|
844
844
|
|
|
845
845
|
See [docs/Telegram-2-Way-Setup.md](docs/Telegram-2-Way-Setup.md) for full guide.
|
|
846
846
|
|
|
847
|
+
### User Authorization
|
|
848
|
+
|
|
849
|
+
Telegram callbacks are validated against an allowlist. Sources checked in order:
|
|
850
|
+
|
|
851
|
+
1. `TELEGRAM_ALLOWED_USERS` environment variable (comma-separated IDs)
|
|
852
|
+
2. `telegram-allowed-users.txt` key file (comma-separated IDs)
|
|
853
|
+
3. `telegram-chat-id.txt` (legacy fallback, single ID)
|
|
854
|
+
|
|
855
|
+
```bash
|
|
856
|
+
# Option 1: Environment variable
|
|
857
|
+
TELEGRAM_ALLOWED_USERS=123456789,987654321
|
|
858
|
+
|
|
859
|
+
# Option 2: Key file (~/.pi-harness-runtime/keys/)
|
|
860
|
+
echo "123456789,987654321" > ~/.pi-harness-runtime/keys/telegram-allowed-users.txt
|
|
861
|
+
|
|
862
|
+
# Option 3: Legacy single user
|
|
863
|
+
echo "123456789" > ~/.pi-harness-runtime/keys/telegram-chat-id.txt
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Unauthorized callback queries are rejected before processing.
|
|
867
|
+
|
|
847
868
|
## License
|
|
848
869
|
|
|
849
870
|
MIT © 2026 MooCoding
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Authorization Module
|
|
3
|
+
*
|
|
4
|
+
* Validates Telegram user access from multiple sources:
|
|
5
|
+
* - Environment variable (TELEGRAM_ALLOWED_USERS)
|
|
6
|
+
* - Key file (~/.pi-harness-runtime/keys/telegram-allowed-users.txt)
|
|
7
|
+
* - Legacy chat ID file (~/.pi-harness-runtime/keys/telegram-chat-id.txt)
|
|
8
|
+
* - Infisical (future)
|
|
9
|
+
*
|
|
10
|
+
* Priority: Environment > Key file > Legacy chat ID file
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
|
|
17
|
+
const KEYS_DIR = join(homedir(), ".pi-harness-runtime", "keys");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse comma-separated user IDs
|
|
21
|
+
*/
|
|
22
|
+
function parseUserIds(content: string): Set<number> {
|
|
23
|
+
const ids = new Set<number>();
|
|
24
|
+
const parts = content.split(",").map(s => s.trim()).filter(Boolean);
|
|
25
|
+
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
const num = parseInt(part, 10);
|
|
28
|
+
if (!isNaN(num) && num > 0) {
|
|
29
|
+
ids.add(num);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return ids;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load allowed users from environment variable
|
|
38
|
+
*/
|
|
39
|
+
function loadFromEnv(): Set<number> | null {
|
|
40
|
+
const envValue = process.env.TELEGRAM_ALLOWED_USERS;
|
|
41
|
+
if (!envValue) return null;
|
|
42
|
+
|
|
43
|
+
return parseUserIds(envValue);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load allowed users from key file
|
|
48
|
+
*/
|
|
49
|
+
function loadFromKeyFile(filename: string): Set<number> | null {
|
|
50
|
+
const filePath = join(KEYS_DIR, filename);
|
|
51
|
+
if (!existsSync(filePath)) return null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(filePath, "utf8").trim();
|
|
55
|
+
if (!content) return null;
|
|
56
|
+
return parseUserIds(content);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get allowed Telegram user IDs from all sources
|
|
64
|
+
*
|
|
65
|
+
* Priority:
|
|
66
|
+
* 1. TELEGRAM_ALLOWED_USERS environment variable
|
|
67
|
+
* 2. telegram-allowed-users.txt key file
|
|
68
|
+
* 3. telegram-chat-id.txt (legacy, single user)
|
|
69
|
+
*/
|
|
70
|
+
export function getAllowedUserIds(): Set<number> {
|
|
71
|
+
// 1. Check environment variable first (highest priority)
|
|
72
|
+
const fromEnv = loadFromEnv();
|
|
73
|
+
if (fromEnv && fromEnv.size > 0) {
|
|
74
|
+
return fromEnv;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. Check dedicated allowed users file
|
|
78
|
+
const fromKeyFile = loadFromKeyFile("telegram-allowed-users.txt");
|
|
79
|
+
if (fromKeyFile && fromKeyFile.size > 0) {
|
|
80
|
+
return fromKeyFile;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Check legacy chat ID file (single user)
|
|
84
|
+
const fromLegacy = loadFromKeyFile("telegram-chat-id.txt");
|
|
85
|
+
if (fromLegacy && fromLegacy.size > 0) {
|
|
86
|
+
return fromLegacy;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// No allowed users configured
|
|
90
|
+
return new Set();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a Telegram user ID is authorized
|
|
95
|
+
*/
|
|
96
|
+
export function isAuthorizedUser(userId: number): boolean {
|
|
97
|
+
const allowed = getAllowedUserIds();
|
|
98
|
+
// If no allowlist is configured, deny by default (fail closed)
|
|
99
|
+
if (allowed.size === 0) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return allowed.has(userId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validate callback query sender
|
|
107
|
+
* Returns true if authorized, false if unauthorized
|
|
108
|
+
*/
|
|
109
|
+
export function validateSender(senderId: number): {
|
|
110
|
+
authorized: boolean;
|
|
111
|
+
reason?: string;
|
|
112
|
+
} {
|
|
113
|
+
if (isAuthorizedUser(senderId)) {
|
|
114
|
+
return { authorized: true };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fail closed - deny unauthorized users
|
|
118
|
+
return {
|
|
119
|
+
authorized: false,
|
|
120
|
+
reason: "unauthorized_user",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get allowlist source for diagnostics
|
|
126
|
+
*/
|
|
127
|
+
export function getAllowlistSource(): string {
|
|
128
|
+
if (process.env.TELEGRAM_ALLOWED_USERS) {
|
|
129
|
+
return "environment";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const allowedPath = join(KEYS_DIR, "telegram-allowed-users.txt");
|
|
133
|
+
if (existsSync(allowedPath)) {
|
|
134
|
+
return "key_file:telegram-allowed-users.txt";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const legacyPath = join(KEYS_DIR, "telegram-chat-id.txt");
|
|
138
|
+
if (existsSync(legacyPath)) {
|
|
139
|
+
return "legacy:telegram-chat-id.txt";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return "none";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get count of allowed users (for diagnostics, not values)
|
|
147
|
+
*/
|
|
148
|
+
export function getAllowedUserCount(): number {
|
|
149
|
+
return getAllowedUserIds().size;
|
|
150
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Content Filter — Jev-Powered Sensitive Content Detection
|
|
3
|
+
*
|
|
4
|
+
* Before sending to Telegram, Jev analyzes the content to determine
|
|
5
|
+
* if it contains sensitive credentials that should NOT be transmitted.
|
|
6
|
+
*
|
|
7
|
+
* If Jev is unavailable or returns BLOCK, the message is:
|
|
8
|
+
* - Not sent to Telegram
|
|
9
|
+
* - Logged with safe metadata (no secrets)
|
|
10
|
+
* - Optionally redirected to secure alternative (TUI only)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { NotificationPayload } from "../packages/notification/dist/types.js";
|
|
14
|
+
import { maskString } from "../packages/notification/dist/mask-secrets.js";
|
|
15
|
+
import { getKey } from "./key-loader.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Jev judgment result for content filtering
|
|
19
|
+
*/
|
|
20
|
+
export type ContentJudgment = "SEND" | "BLOCK" | "UNKNOWN";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Result of content filtering decision
|
|
24
|
+
*/
|
|
25
|
+
export interface FilterResult {
|
|
26
|
+
/** Whether to send to Telegram */
|
|
27
|
+
shouldSend: boolean;
|
|
28
|
+
/** Jev's judgment */
|
|
29
|
+
judgment: ContentJudgment;
|
|
30
|
+
/** Reason for blocking (if blocked) */
|
|
31
|
+
reason?: string;
|
|
32
|
+
/** Confidence score (0-1) */
|
|
33
|
+
confidence?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract text content from notification payload
|
|
38
|
+
*/
|
|
39
|
+
function extractContent(payload: NotificationPayload): string {
|
|
40
|
+
const parts: string[] = [];
|
|
41
|
+
|
|
42
|
+
// Title
|
|
43
|
+
if (payload.title) {
|
|
44
|
+
parts.push(payload.title);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Main message
|
|
48
|
+
if (payload.message) {
|
|
49
|
+
parts.push(payload.message);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Details
|
|
53
|
+
if (payload.details) {
|
|
54
|
+
for (const [key, value] of Object.entries(payload.details)) {
|
|
55
|
+
if (typeof value === "string" && value) {
|
|
56
|
+
parts.push(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return parts.join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Simple heuristic check for obvious credentials (fallback when Jev unavailable)
|
|
66
|
+
*/
|
|
67
|
+
function heuristicCheck(content: string): FilterResult {
|
|
68
|
+
// Check for common credential patterns
|
|
69
|
+
const sensitivePatterns = [
|
|
70
|
+
/\d+:[-A-Za-z0-9_]{35,}/, // Telegram bot token
|
|
71
|
+
/sk-[a-zA-Z0-9]{20,}/, // OpenAI-style keys
|
|
72
|
+
/[a-zA-Z0-9_-]{32,}/, // Long API keys
|
|
73
|
+
/Bearer\s+[a-zA-Z0-9_-]{20,}/i, // Bearer tokens
|
|
74
|
+
/(api[_-]?key|secret|token|password|credential)[=:\s]+[^\s]{16,}/i,
|
|
75
|
+
/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/,
|
|
76
|
+
/infisical[_-]?(client[_-])?(secret|id)/i,
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
let matchFound = false;
|
|
80
|
+
let matchType = "";
|
|
81
|
+
|
|
82
|
+
for (const pattern of sensitivePatterns) {
|
|
83
|
+
if (pattern.test(content)) {
|
|
84
|
+
matchFound = true;
|
|
85
|
+
// Determine what type of credential was found
|
|
86
|
+
if (/\d+:[-A-Za-z0-9_]{35,}/.test(content)) {
|
|
87
|
+
matchType = "telegram_bot_token";
|
|
88
|
+
} else if (/sk-[a-zA-Z0-9]{20,}/.test(content)) {
|
|
89
|
+
matchType = "openai_style_key";
|
|
90
|
+
} else if (/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/.test(content)) {
|
|
91
|
+
matchType = "private_key";
|
|
92
|
+
} else if (/infisical/i.test(content)) {
|
|
93
|
+
matchType = "infisical_credential";
|
|
94
|
+
} else {
|
|
95
|
+
matchType = "api_key_or_secret";
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (matchFound) {
|
|
102
|
+
return {
|
|
103
|
+
shouldSend: false,
|
|
104
|
+
judgment: "BLOCK",
|
|
105
|
+
reason: `Sensitive content detected: ${matchType}`,
|
|
106
|
+
confidence: 0.95,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
shouldSend: true,
|
|
112
|
+
judgment: "SEND",
|
|
113
|
+
confidence: 0.9,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Call Jev to analyze content for sensitive credentials
|
|
119
|
+
*/
|
|
120
|
+
async function callJev(content: string): Promise<FilterResult | null> {
|
|
121
|
+
const apiKey = getKey("typesafe-api-key") || process.env.TYPESAFE_API_KEY;
|
|
122
|
+
|
|
123
|
+
if (!apiKey) {
|
|
124
|
+
return null; // Jev not available
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch("https://api.typesafe.ai/v1/jev/decide", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Authorization: `Bearer ${apiKey}`,
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
question:
|
|
136
|
+
"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.",
|
|
137
|
+
context: {
|
|
138
|
+
message: content,
|
|
139
|
+
action: "telegram_notification",
|
|
140
|
+
security_level: "high",
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const data = (await response.json()) as {
|
|
150
|
+
decision?: string;
|
|
151
|
+
confidence?: number;
|
|
152
|
+
reasoning?: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const decision = data.decision?.toUpperCase() ?? "UNKNOWN";
|
|
156
|
+
const isSensitive = decision === "YES" || decision === "BLOCK";
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
shouldSend: !isSensitive,
|
|
160
|
+
judgment: isSensitive ? "BLOCK" : "SEND",
|
|
161
|
+
reason: data.reasoning,
|
|
162
|
+
confidence: data.confidence,
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Filter notification content before sending to Telegram
|
|
171
|
+
*
|
|
172
|
+
* @param payload - The notification payload
|
|
173
|
+
* @returns Filter result with decision
|
|
174
|
+
*/
|
|
175
|
+
export async function filterTelegramContent(
|
|
176
|
+
payload: NotificationPayload,
|
|
177
|
+
): Promise<FilterResult> {
|
|
178
|
+
// Extract content to analyze
|
|
179
|
+
const content = extractContent(payload);
|
|
180
|
+
|
|
181
|
+
// First, run heuristic check (always available)
|
|
182
|
+
const heuristicResult = heuristicCheck(content);
|
|
183
|
+
if (heuristicResult.judgment === "BLOCK") {
|
|
184
|
+
logFilterDecision("BLOCK", heuristicResult.reason ?? "Heuristic match", payload.jobId);
|
|
185
|
+
return heuristicResult;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Try Jev if available
|
|
189
|
+
const jevResult = await callJev(content);
|
|
190
|
+
if (jevResult) {
|
|
191
|
+
if (jevResult.judgment === "BLOCK") {
|
|
192
|
+
logFilterDecision("BLOCK", jevResult.reason ?? "Jev judgment", payload.jobId);
|
|
193
|
+
}
|
|
194
|
+
return jevResult;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Jev unavailable - use heuristic result
|
|
198
|
+
return heuristicResult;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Log filter decision securely (no content, just metadata)
|
|
203
|
+
*/
|
|
204
|
+
function logFilterDecision(
|
|
205
|
+
decision: ContentJudgment,
|
|
206
|
+
reason: string,
|
|
207
|
+
jobId: string,
|
|
208
|
+
): void {
|
|
209
|
+
console.warn(
|
|
210
|
+
`[TelegramFilter] ${decision} job=${jobId} reason=${reason} channel=telegram`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get safe notification version (masked) for logging
|
|
216
|
+
*/
|
|
217
|
+
export function getSafeNotificationLog(payload: NotificationPayload): string {
|
|
218
|
+
const maskedTitle = maskString(payload.title);
|
|
219
|
+
const maskedMessage = maskString(payload.message);
|
|
220
|
+
const maskedJobId = maskString(payload.jobId);
|
|
221
|
+
|
|
222
|
+
return `event=${payload.event} job=${maskedJobId} title=${maskedTitle} message_len=${maskedMessage.length}`;
|
|
223
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.163",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|