pi-harness-runtime 1.1.158 → 1.1.159
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/harness/infisical-config.ts +395 -0
- package/harness/infisical-setup.ts +270 -0
- package/harness/key-loader.ts +129 -29
- package/harness/secret-provider.ts +437 -0
- package/harness/secret-schema.ts +163 -0
- package/harness/varlock-worker.ts +379 -0
- package/index.ts +12 -3
- 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/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/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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Schema Module
|
|
3
|
+
*
|
|
4
|
+
* Generates Varlock schema files and manages secret name mappings.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { InfisicalConfig, ServiceSecretMapping, ServiceName } from "./infisical-config.js";
|
|
8
|
+
|
|
9
|
+
export type { ServiceName, ServiceSecretMapping } from "./infisical-config.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard secret keys for each service
|
|
13
|
+
*/
|
|
14
|
+
export const SERVICE_SECRET_KEYS: Record<ServiceName, string[]> = {
|
|
15
|
+
infisical: [], // No secrets for the Infisical provider itself
|
|
16
|
+
jev: ["TYPESAFE_API_KEY", "OPENROUTER_API_KEY"],
|
|
17
|
+
honcho: ["HONCHO_API_KEY"],
|
|
18
|
+
qdrant: ["QDRANT_CLUSTER_ENDPOINT", "QDRANT_API_KEY", "QDRANT_COLLECTION"],
|
|
19
|
+
telegram: ["TELEGRAM_BOT_TOKEN", "TELEGRAM_CHAT_ID", "TELEGRAM_WEBHOOK_SECRET"],
|
|
20
|
+
embeddings: ["OPENAI_API_KEY"],
|
|
21
|
+
planner: ["PLANNER_API_KEY", "PLANNER_BASE_URL", "PLANNER_MODEL"],
|
|
22
|
+
minimax: ["MINIMAX_API_KEY", "MINIMAX_BASE_URL", "MINIMAX_MODEL"],
|
|
23
|
+
glm: ["GLM_API_KEY", "GLM_BASE_URL", "GLM_MODEL"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate Varlock schema for Infisical config
|
|
28
|
+
*/
|
|
29
|
+
export function generateSchema(config: InfisicalConfig): string {
|
|
30
|
+
const lines: string[] = [
|
|
31
|
+
"# @plugin(@varlock/infisical-plugin)",
|
|
32
|
+
`# @initInfisical(projectId=${config.projectId}, environment=${config.environment}, siteUrl=${getSiteUrl(config)}, secretPath=${config.secretPath}, clientId=$INFISICAL_CLIENT_ID, clientSecret=$INFISICAL_CLIENT_SECRET, cacheTtl=false)`,
|
|
33
|
+
"# ---",
|
|
34
|
+
"",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Internal bootstrap variables (not resolved secrets)
|
|
38
|
+
lines.push("# @type=string @internal");
|
|
39
|
+
lines.push("INFISICAL_PROJECT_ID=");
|
|
40
|
+
lines.push("# @type=string @internal");
|
|
41
|
+
lines.push(`INFISICAL_ENVIRONMENT=${config.environment}`);
|
|
42
|
+
lines.push("# @type=string @internal");
|
|
43
|
+
lines.push(`INFISICAL_SITE_URL=${getSiteUrl(config)}`);
|
|
44
|
+
lines.push("# @type=infisicalClientId @internal");
|
|
45
|
+
lines.push("INFISICAL_CLIENT_ID=");
|
|
46
|
+
lines.push("# @type=infisicalClientSecret @sensitive @internal");
|
|
47
|
+
lines.push("INFISICAL_CLIENT_SECRET=");
|
|
48
|
+
lines.push("");
|
|
49
|
+
|
|
50
|
+
// Add secrets for each enabled service
|
|
51
|
+
for (const serviceMapping of config.services) {
|
|
52
|
+
const service = serviceMapping.service;
|
|
53
|
+
const keys = SERVICE_SECRET_KEYS[service] ?? [];
|
|
54
|
+
const mapping = serviceMapping.keys;
|
|
55
|
+
|
|
56
|
+
lines.push(`# Service: ${service}`);
|
|
57
|
+
lines.push("");
|
|
58
|
+
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
// Map to Infisical secret name
|
|
61
|
+
const infisicalName = mapping[key] ?? key;
|
|
62
|
+
const isSensitive = isSensitiveKey(key);
|
|
63
|
+
|
|
64
|
+
lines.push(
|
|
65
|
+
`# @type=string ${serviceMapping.required ? "@required" : ""} ${isSensitive ? "@sensitive" : ""}`
|
|
66
|
+
);
|
|
67
|
+
lines.push(`${key}=infisical("${infisicalName}")`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get site URL from config
|
|
78
|
+
*/
|
|
79
|
+
function getSiteUrl(config: InfisicalConfig): string {
|
|
80
|
+
switch (config.site) {
|
|
81
|
+
case "us":
|
|
82
|
+
return "https://app.infisical.com";
|
|
83
|
+
case "eu":
|
|
84
|
+
return "https://eu.infisical.com";
|
|
85
|
+
case "custom":
|
|
86
|
+
return config.customSiteUrl ?? "https://app.infisical.com";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a key is sensitive
|
|
92
|
+
*/
|
|
93
|
+
function isSensitiveKey(key: string): boolean {
|
|
94
|
+
const sensitivePatterns = [
|
|
95
|
+
/API[_-]?KEY$/i,
|
|
96
|
+
/SECRET$/i,
|
|
97
|
+
/TOKEN$/i,
|
|
98
|
+
/PASSWORD$/i,
|
|
99
|
+
/CREDENTIAL$/i,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
return sensitivePatterns.some(pattern => pattern.test(key));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validate schema doesn't contain dangerous content
|
|
107
|
+
*/
|
|
108
|
+
export function validateSchemaContent(schema: string): { valid: boolean; error?: string } {
|
|
109
|
+
// Check for obvious injection patterns
|
|
110
|
+
const dangerous = [
|
|
111
|
+
/\$\{.*\}/, // Variable interpolation
|
|
112
|
+
/`[^`]*`/, // Backtick commands
|
|
113
|
+
/\|\s*\w+/, // Pipe to command
|
|
114
|
+
/;\s*\w+/, // Command chaining
|
|
115
|
+
/&&\s*\w+/, // And chaining
|
|
116
|
+
/\|\|\s*\w+/, // Or chaining
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
for (const pattern of dangerous) {
|
|
120
|
+
if (pattern.test(schema)) {
|
|
121
|
+
return {
|
|
122
|
+
valid: false,
|
|
123
|
+
error: `Schema contains potentially dangerous pattern: ${pattern.toString()}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { valid: true };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get default secret name mapping for a service
|
|
133
|
+
*/
|
|
134
|
+
export function getDefaultMapping(service: ServiceName): Record<string, string> {
|
|
135
|
+
const keys = SERVICE_SECRET_KEYS[service] ?? [];
|
|
136
|
+
const mapping: Record<string, string> = {};
|
|
137
|
+
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
// Default mapping: same name in Infisical
|
|
140
|
+
mapping[key] = key;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return mapping;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a service has all required keys
|
|
148
|
+
*/
|
|
149
|
+
export function validateServiceMapping(
|
|
150
|
+
service: ServiceName,
|
|
151
|
+
mapping: Record<string, string>
|
|
152
|
+
): { valid: boolean; missing?: string[] } {
|
|
153
|
+
const requiredKeys = SERVICE_SECRET_KEYS[service] ?? [];
|
|
154
|
+
const missing: string[] = [];
|
|
155
|
+
|
|
156
|
+
for (const key of requiredKeys) {
|
|
157
|
+
if (!mapping[key]) {
|
|
158
|
+
missing.push(key);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return missing.length > 0 ? { valid: false, missing } : { valid: true };
|
|
163
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Varlock Worker Module
|
|
3
|
+
*
|
|
4
|
+
* Spawns Varlock CLI as an async child process for secret resolution.
|
|
5
|
+
* All secrets stay in private pipes - never touch main process stdout.
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - Uses shell: false to prevent injection
|
|
9
|
+
* - Captures output in memory only
|
|
10
|
+
* - Enforces deadlines and termination
|
|
11
|
+
* - Never logs secret values
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Varlock worker options
|
|
21
|
+
*/
|
|
22
|
+
export interface VarlockWorkerOptions {
|
|
23
|
+
/** Path to Varlock schema file (.env.schema) */
|
|
24
|
+
schemaPath: string;
|
|
25
|
+
|
|
26
|
+
/** Working directory for Varlock */
|
|
27
|
+
cwd?: string;
|
|
28
|
+
|
|
29
|
+
/** Environment variables to pass (bootstrap vars only) */
|
|
30
|
+
env?: Record<string, string>;
|
|
31
|
+
|
|
32
|
+
/** Maximum time to wait for resolution (ms) */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
|
|
35
|
+
/** Skip cache for this resolution */
|
|
36
|
+
skipCache?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolved secret value
|
|
41
|
+
*/
|
|
42
|
+
export interface ResolvedSecret {
|
|
43
|
+
key: string;
|
|
44
|
+
value: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolution result
|
|
49
|
+
*/
|
|
50
|
+
export interface ResolutionResult {
|
|
51
|
+
success: boolean;
|
|
52
|
+
secrets?: ResolvedSecret[];
|
|
53
|
+
error?: string;
|
|
54
|
+
errorCode?: "timeout" | "exit_error" | "parse_error" | "no_binary" | "cancelled";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Error categories for safe logging
|
|
59
|
+
*/
|
|
60
|
+
export type ErrorCategory =
|
|
61
|
+
| "no_binary"
|
|
62
|
+
| "schema_not_found"
|
|
63
|
+
| "timeout"
|
|
64
|
+
| "exit_error"
|
|
65
|
+
| "parse_error"
|
|
66
|
+
| "cancelled"
|
|
67
|
+
| "unknown";
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolution error codes (subset of ErrorCategory that can be returned)
|
|
72
|
+
*/
|
|
73
|
+
export type ResolutionErrorCode =
|
|
74
|
+
| "no_binary"
|
|
75
|
+
| "timeout"
|
|
76
|
+
| "exit_error"
|
|
77
|
+
| "parse_error"
|
|
78
|
+
| "cancelled";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Find Varlock CLI binary
|
|
82
|
+
*/
|
|
83
|
+
function findVarlockBinary(): string | null {
|
|
84
|
+
// Check common locations
|
|
85
|
+
const possiblePaths = [
|
|
86
|
+
// Global npm
|
|
87
|
+
join(homedir(), ".nvm", "versions", "node", process.version.split(".")[0].slice(1), "lib", "node_modules", "varlock", "bin", "cli.js"),
|
|
88
|
+
// User npm
|
|
89
|
+
join(homedir(), ".npm-global", "bin", "varlock"),
|
|
90
|
+
// Local node_modules
|
|
91
|
+
join(process.cwd(), "node_modules", ".bin", "varlock"),
|
|
92
|
+
// pi-harness-runtime node_modules
|
|
93
|
+
join(__dirname, "..", "node_modules", ".bin", "varlock"),
|
|
94
|
+
// Direct global
|
|
95
|
+
"/usr/local/bin/varlock",
|
|
96
|
+
"/usr/bin/varlock",
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
for (const path of possiblePaths) {
|
|
100
|
+
if (existsSync(path)) {
|
|
101
|
+
return path;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Map error to safe category for logging
|
|
110
|
+
*/
|
|
111
|
+
function categorizeError(error: unknown): ErrorCategory {
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
const msg = error.message.toLowerCase();
|
|
114
|
+
|
|
115
|
+
if (msg.includes("enoent") || msg.includes("not found")) {
|
|
116
|
+
return "no_binary";
|
|
117
|
+
}
|
|
118
|
+
if (msg.includes("timeout")) {
|
|
119
|
+
return "timeout";
|
|
120
|
+
}
|
|
121
|
+
if (msg.includes("enoent") || msg.includes("no such file")) {
|
|
122
|
+
return "schema_not_found";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return "unknown";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse Varlock JSON output
|
|
131
|
+
*/
|
|
132
|
+
function parseVarlockOutput(stdout: string, maxSize: number = 1024 * 1024): ResolvedSecret[] | null {
|
|
133
|
+
// Enforce size limit
|
|
134
|
+
if (stdout.length > maxSize) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(stdout);
|
|
140
|
+
|
|
141
|
+
// Varlock JSON format: { "KEY": "value", ... }
|
|
142
|
+
// Only return string-to-string mappings
|
|
143
|
+
const secrets: ResolvedSecret[] = [];
|
|
144
|
+
|
|
145
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
146
|
+
if (typeof key === "string" && typeof value === "string") {
|
|
147
|
+
secrets.push({ key, value });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return secrets;
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve secrets using Varlock CLI
|
|
159
|
+
*
|
|
160
|
+
* @param options - Worker options
|
|
161
|
+
* @param signal - AbortSignal for cancellation
|
|
162
|
+
* @returns Resolution result
|
|
163
|
+
*/
|
|
164
|
+
export async function resolveSecrets(
|
|
165
|
+
options: VarlockWorkerOptions,
|
|
166
|
+
signal?: AbortSignal
|
|
167
|
+
): Promise<ResolutionResult> {
|
|
168
|
+
const {
|
|
169
|
+
schemaPath,
|
|
170
|
+
cwd = process.cwd(),
|
|
171
|
+
env = {},
|
|
172
|
+
timeoutMs = 60000,
|
|
173
|
+
skipCache = true,
|
|
174
|
+
} = options;
|
|
175
|
+
|
|
176
|
+
const varlockPath = findVarlockBinary();
|
|
177
|
+
|
|
178
|
+
if (!varlockPath) {
|
|
179
|
+
console.error("[varlock-worker] Varlock binary not found");
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: "Varlock binary not found",
|
|
183
|
+
errorCode: "no_binary",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Build arguments
|
|
188
|
+
const args = [
|
|
189
|
+
"load",
|
|
190
|
+
"--format", "json",
|
|
191
|
+
"--path", schemaPath,
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
if (skipCache) {
|
|
195
|
+
args.push("--skip-cache");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create promise with deadline
|
|
199
|
+
const result = await Promise.race([
|
|
200
|
+
new Promise<ResolutionResult>((resolve) => {
|
|
201
|
+
const child = spawn(varlockPath, args, {
|
|
202
|
+
cwd,
|
|
203
|
+
env: {
|
|
204
|
+
...process.env,
|
|
205
|
+
...env,
|
|
206
|
+
},
|
|
207
|
+
shell: false,
|
|
208
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
let stdout = "";
|
|
212
|
+
let stderr = "";
|
|
213
|
+
|
|
214
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
215
|
+
stdout += chunk.toString();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
219
|
+
stderr += chunk.toString();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
child.on("close", (code) => {
|
|
223
|
+
if (code === 0) {
|
|
224
|
+
const secrets = parseVarlockOutput(stdout);
|
|
225
|
+
if (secrets) {
|
|
226
|
+
resolve({
|
|
227
|
+
success: true,
|
|
228
|
+
secrets,
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
resolve({
|
|
232
|
+
success: false,
|
|
233
|
+
error: "Failed to parse Varlock output",
|
|
234
|
+
errorCode: "parse_error",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
// Log stderr without secrets
|
|
239
|
+
console.error("[varlock-worker] Varlock exited with code:", code);
|
|
240
|
+
if (stderr) {
|
|
241
|
+
// Only log first line of stderr, no secrets
|
|
242
|
+
const firstLine = stderr.split("\n")[0];
|
|
243
|
+
console.error("[varlock-worker] Error:", firstLine?.slice(0, 200));
|
|
244
|
+
}
|
|
245
|
+
resolve({
|
|
246
|
+
success: false,
|
|
247
|
+
error: `Varlock exited with code ${code}`,
|
|
248
|
+
errorCode: "exit_error",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
child.on("error", (err) => {
|
|
254
|
+
const category = categorizeError(err);
|
|
255
|
+
console.error(`[varlock-worker] ${category}:`, err instanceof Error ? err.message : String(err));
|
|
256
|
+
// Map to allowed error codes
|
|
257
|
+
const errorCode: ResolutionErrorCode =
|
|
258
|
+
category === "schema_not_found" || category === "unknown"
|
|
259
|
+
? "exit_error"
|
|
260
|
+
: category as ResolutionErrorCode;
|
|
261
|
+
resolve({
|
|
262
|
+
success: false,
|
|
263
|
+
error: `${category}: ${err instanceof Error ? err.message : String(err)}`,
|
|
264
|
+
errorCode,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Handle cancellation
|
|
269
|
+
signal?.addEventListener("abort", () => {
|
|
270
|
+
console.log("[varlock-worker] Cancelled, terminating child");
|
|
271
|
+
child.kill("SIGTERM");
|
|
272
|
+
});
|
|
273
|
+
}),
|
|
274
|
+
// Timeout
|
|
275
|
+
new Promise<ResolutionResult>((resolve) =>
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
resolve({
|
|
278
|
+
success: false,
|
|
279
|
+
error: "Resolution timed out",
|
|
280
|
+
errorCode: "timeout",
|
|
281
|
+
});
|
|
282
|
+
}, timeoutMs)
|
|
283
|
+
),
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get Varlock version
|
|
291
|
+
*/
|
|
292
|
+
export async function getVarlockVersion(): Promise<string | null> {
|
|
293
|
+
const varlockPath = findVarlockBinary();
|
|
294
|
+
|
|
295
|
+
if (!varlockPath) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return new Promise((resolve) => {
|
|
300
|
+
const child = spawn(varlockPath, ["--version"], {
|
|
301
|
+
shell: false,
|
|
302
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
let version = "";
|
|
306
|
+
|
|
307
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
308
|
+
version += chunk.toString();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
child.on("close", (code) => {
|
|
312
|
+
if (code === 0) {
|
|
313
|
+
resolve(version.trim());
|
|
314
|
+
} else {
|
|
315
|
+
resolve(null);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
child.on("error", () => {
|
|
320
|
+
resolve(null);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Timeout
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
child.kill();
|
|
326
|
+
resolve(null);
|
|
327
|
+
}, 5000);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Validate schema file syntax using Varlock
|
|
333
|
+
*/
|
|
334
|
+
export async function validateSchema(schemaPath: string): Promise<{ valid: boolean; error?: string }> {
|
|
335
|
+
const varlockPath = findVarlockBinary();
|
|
336
|
+
|
|
337
|
+
if (!varlockPath) {
|
|
338
|
+
return { valid: false, error: "Varlock binary not found" };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
const child = spawn(varlockPath, ["validate", "--path", schemaPath], {
|
|
343
|
+
shell: false,
|
|
344
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
let stderr = "";
|
|
348
|
+
|
|
349
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
350
|
+
stderr += chunk.toString();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
child.on("close", (code) => {
|
|
354
|
+
if (code === 0) {
|
|
355
|
+
resolve({ valid: true });
|
|
356
|
+
} else {
|
|
357
|
+
// Return first error line only
|
|
358
|
+
const errorLine = stderr.split("\n")[0];
|
|
359
|
+
resolve({
|
|
360
|
+
valid: false,
|
|
361
|
+
error: errorLine?.slice(0, 500) ?? "Validation failed",
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
child.on("error", (err) => {
|
|
367
|
+
resolve({
|
|
368
|
+
valid: false,
|
|
369
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// 10 second validation timeout
|
|
374
|
+
setTimeout(() => {
|
|
375
|
+
child.kill();
|
|
376
|
+
resolve({ valid: false, error: "Validation timed out" });
|
|
377
|
+
}, 10000);
|
|
378
|
+
});
|
|
379
|
+
}
|
package/index.ts
CHANGED
|
@@ -903,9 +903,18 @@ async function initHermesSkills(pi: ExtensionAPI): Promise<void> {
|
|
|
903
903
|
|
|
904
904
|
// Register harness services diagnostic commands
|
|
905
905
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
906
|
+
const { initServiceCommands } = await import("./harness/service-commands.js");
|
|
907
|
+
initServiceCommands(pi);
|
|
908
|
+
logStartup("[pi-harness] Registered service diagnostic commands");
|
|
909
|
+
|
|
910
|
+
// Register Infisical setup command
|
|
911
|
+
try {
|
|
912
|
+
const { registerInfisicalSetup } = await import("./harness/infisical-setup.js");
|
|
913
|
+
registerInfisicalSetup(pi);
|
|
914
|
+
logStartup("[pi-harness] Registered /setup-infisical command");
|
|
915
|
+
} catch (err) {
|
|
916
|
+
logStartup("[pi-harness] Infisical setup not available:", err instanceof Error ? err.message : String(err));
|
|
917
|
+
}
|
|
909
918
|
} catch (err) {
|
|
910
919
|
logStartup(`[pi-harness] Failed to register service commands:`, err instanceof Error ? err.message : String(err));
|
|
911
920
|
}
|
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.159",
|
|
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",
|