@love-moon/conductor-cli 0.7.6 → 0.8.0
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/CHANGELOG.md +42 -0
- package/bin/conductor-task.js +124 -0
- package/bin/conductor.js +3 -2
- package/package.json +5 -5
- package/src/daemon.js +1126 -64
- package/src/entity-helpers.js +27 -2
- package/src/handoff-log-mask.js +43 -0
package/src/entity-helpers.js
CHANGED
|
@@ -192,8 +192,14 @@ export async function resolveProject(apis, options = {}) {
|
|
|
192
192
|
if (!apis.projects || typeof apis.projects.resolveProject !== "function") {
|
|
193
193
|
throw new Error("ProjectsApi.resolveProject is not available in conductor-sdk");
|
|
194
194
|
}
|
|
195
|
+
const explicitProject = String(options.project ?? "").trim();
|
|
196
|
+
if (explicitProject) {
|
|
197
|
+
if (typeof apis.projects.getProject !== "function") {
|
|
198
|
+
throw new Error("ProjectsApi.getProject is not available in conductor-sdk");
|
|
199
|
+
}
|
|
200
|
+
return apis.projects.getProject(explicitProject);
|
|
201
|
+
}
|
|
195
202
|
return apis.projects.resolveProject({
|
|
196
|
-
project: options.project,
|
|
197
203
|
env: options.env || process.env,
|
|
198
204
|
cwd: options.cwd || process.cwd(),
|
|
199
205
|
});
|
|
@@ -332,11 +338,30 @@ export function emitDryRun(stream, json, payload) {
|
|
|
332
338
|
}
|
|
333
339
|
}
|
|
334
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Pull a human-readable reason out of a backend error payload. Backend routes
|
|
343
|
+
* respond with `{ error }` (occasionally `{ message }`), which the SDK attaches
|
|
344
|
+
* to BackendApiError.details. Surfacing it turns an opaque "Backend responded
|
|
345
|
+
* with 409" into the actual cause (e.g. "Project daemon X is offline").
|
|
346
|
+
*/
|
|
347
|
+
function backendErrorDetail(error) {
|
|
348
|
+
const details = error && typeof error === "object" ? error.details : null;
|
|
349
|
+
if (!details) return null;
|
|
350
|
+
if (typeof details === "string") return details.trim() || null;
|
|
351
|
+
if (typeof details === "object") {
|
|
352
|
+
const reason = details.error ?? details.message;
|
|
353
|
+
if (typeof reason === "string" && reason.trim()) return reason.trim();
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
335
358
|
/**
|
|
336
359
|
* Translate an unknown error into a printable + exit-coded form.
|
|
337
360
|
*/
|
|
338
361
|
export function reportError(consoleErr, error) {
|
|
339
362
|
const message = error instanceof Error ? error.message : String(error);
|
|
340
|
-
|
|
363
|
+
const detail = backendErrorDetail(error);
|
|
364
|
+
const line = detail && detail !== message ? `${message}: ${detail}` : message;
|
|
365
|
+
consoleErr.error(`Error: ${line}`);
|
|
341
366
|
return exitCodeForError(error);
|
|
342
367
|
}
|
package/src/handoff-log-mask.js
CHANGED
|
@@ -23,6 +23,49 @@ export function maskHandoffUrlForLogs(value) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Secrets other than the handoff token can reach the same log/summary surface
|
|
27
|
+
// once we start capturing a crashing child's output: the daemon puts
|
|
28
|
+
// `CONDUCTOR_AGENT_TOKEN` into the fire's environment AND into tmux's argv
|
|
29
|
+
// (`-e CONDUCTOR_AGENT_TOKEN=…`), and the fire inherits provider keys such as
|
|
30
|
+
// ANTHROPIC_API_KEY / OPENAI_API_KEY. Backends routinely echo those back when
|
|
31
|
+
// they fail ("invalid API key sk-ant-…", usage dumps, env dumps).
|
|
32
|
+
//
|
|
33
|
+
// A single-pattern denylist is the wrong shape for that, so this is a
|
|
34
|
+
// redaction *pass*: exact known secret values first (the strongest signal —
|
|
35
|
+
// the daemon holds its own token at runtime), then structural patterns.
|
|
36
|
+
const SECRET_ASSIGNMENT_RE =
|
|
37
|
+
/\b([A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL)[A-Z0-9_]*)\s*[=:]\s*("[^"]*"|'[^']*'|\S+)/gi;
|
|
38
|
+
const BEARER_RE = /\b(Bearer|Basic)\s+([A-Za-z0-9._~+/=-]{8,})/gi;
|
|
39
|
+
// Provider key shapes (OpenAI/Anthropic `sk-…`, Google `AIza…`, GitHub `ghp_…`).
|
|
40
|
+
const PROVIDER_KEY_RE = /\b(sk-[A-Za-z0-9_-]{12,}|AIza[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{16,})\b/g;
|
|
41
|
+
|
|
42
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Redact secrets from text that is about to be logged or persisted into a task
|
|
46
|
+
* status summary.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} value
|
|
49
|
+
* @param {string[]} [knownSecrets] exact literal values to strip (e.g. the
|
|
50
|
+
* daemon's own AGENT_TOKEN). Short values are ignored so a 1-2 char token
|
|
51
|
+
* cannot blank out the whole message.
|
|
52
|
+
*/
|
|
53
|
+
export function redactSecretsForLogs(value, knownSecrets = []) {
|
|
54
|
+
if (typeof value !== "string" || !value) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
let out = maskHandoffUrlForLogs(value);
|
|
58
|
+
for (const secret of knownSecrets) {
|
|
59
|
+
if (typeof secret === "string" && secret.length >= 8) {
|
|
60
|
+
out = out.replace(new RegExp(escapeRegExp(secret), "g"), "<redacted>");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out = out.replace(SECRET_ASSIGNMENT_RE, (_, key) => `${key}=<redacted>`);
|
|
64
|
+
out = out.replace(BEARER_RE, (_, scheme) => `${scheme} <redacted>`);
|
|
65
|
+
out = out.replace(PROVIDER_KEY_RE, "<redacted>");
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
// Scrub any handoff URL embedded in an error message before surfacing it via
|
|
27
70
|
// logs or task status summaries. Belt-and-suspenders for the case where an
|
|
28
71
|
// internal error stringifies the outbox payload.
|