mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-10-03 23:06:51 +02:00
* feat(worker): record token, cache, and turn usage per agent * feat: replace model tiers with a single SHANNON_AI_MODEL across five providers * feat(cli): rebuild the setup wizard for provider and model selection * docs: document single-model selection and supported providers * feat(worker): use chat completions for OpenAI behind a custom base URL * feat: add SHANNON_AI_OPENAI_FORMAT to pick the wire API for OpenAI gateways * refactor(cli): drop endpoint path hints from the gateway format picker * feat(worker): enable pi in-session provider retry with retry-after backoff * refactor(worker): hand provider error classification to pi and drop the Anthropic ladders * refactor: remove the subscription retry preset and pipeline config section * fix(worker): validate Bedrock credentials with the same live probe as other providers * feat(worker): render the report from structured findings instead of agent-written markdown * fix(worker): dispose the credential probe session on every path * fix(worker): refuse to replace the assembled report with an empty one * refactor(worker): catch post-processing throws across the whole finalization block * revert(worker): drop the report zero-findings guard * docs(worker): correct the retry split and Bedrock credential claims * docs: regenerate llms-full.txt from current sources * feat(cli): build and run the npx flow from a clone * refactor(cli): flatten the setup summary output * feat(cli): reject runs with more than one provider configured * fix(worker): say a rejected bash call never ran * chore(cli): drop grok-4.3 and gpt-5.6-luna from the setup suggestions * feat(worker): capture structured finding locations for SARIF output * fix(worker): enumerate queue confidence so the report inherits it verbatim * feat(worker): give the reporting phase a mode-specific output schema * feat(worker): emit a SARIF 2.1.0 log for exploitative runs * fix(worker): correct SARIF locations and defer fingerprinting to the upload action * fix(worker): drop the confidence suffix from the analysis-mode summary list * feat(worker): give exploit findings a dedicated code location field * feat(worker): carry structured code locations from the vuln queue to the report * fix(worker): join code locations from the vuln queue instead of re-asking agents * fix(worker): spell out the finding_id to category mapping in the tool schema * feat: drop Google/Gemini as a supported AI provider * fix(worker): stop asking the report agent for code locations * docs: correct the provider list and drop the removed rate-limit settings * docs: add provider cyber safeguards and suggested models per provider * docs: document the SARIF output and the report rating thresholds
117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
// Copyright (C) 2025 Keygraph, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License version 3
|
|
// as published by the Free Software Foundation.
|
|
|
|
/**
|
|
* Workflow error formatting utilities.
|
|
* Pure functions with no side effects — safe for Temporal workflow sandbox.
|
|
*/
|
|
|
|
import { ErrorCode } from '../types/errors.js';
|
|
|
|
/**
|
|
* Maps an ApplicationFailure type string to a structured ErrorCode.
|
|
*
|
|
* Activities classify errors via classifyErrorForTemporal() and throw
|
|
* ApplicationFailure with a type string. This function maps those strings
|
|
* to stable ErrorCode values so consumers can switch on codes instead of
|
|
* string-matching error messages.
|
|
*/
|
|
const ERROR_TYPE_TO_CODE: Record<string, ErrorCode> = {
|
|
AuthenticationError: ErrorCode.AUTH_FAILED,
|
|
ConfigurationError: ErrorCode.CONFIG_VALIDATION_FAILED,
|
|
OutputValidationError: ErrorCode.OUTPUT_VALIDATION_FAILED,
|
|
AgentExecutionError: ErrorCode.AGENT_EXECUTION_FAILED,
|
|
GitError: ErrorCode.GIT_CHECKPOINT_FAILED,
|
|
InvalidTargetError: ErrorCode.TARGET_UNREACHABLE,
|
|
};
|
|
|
|
export function classifyErrorCode(error: unknown): ErrorCode | undefined {
|
|
let current: unknown = error;
|
|
while (current instanceof Error) {
|
|
if ('type' in current && typeof (current as { type: unknown }).type === 'string') {
|
|
const code = ERROR_TYPE_TO_CODE[(current as { type: string }).type];
|
|
if (code) return code;
|
|
}
|
|
current = (current as { cause?: unknown }).cause;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** Maps Temporal error type strings to actionable remediation hints. */
|
|
const REMEDIATION_HINTS: Record<string, string> = {
|
|
AuthenticationError: "Verify the selected provider's API key is valid and not expired.",
|
|
ConfigurationError: 'Check your CONFIG file path and contents.',
|
|
GitError: 'Check repository path and git state.',
|
|
InvalidTargetError: 'Verify the target URL is correct and accessible.',
|
|
};
|
|
|
|
/**
|
|
* Walk the .cause chain to find the innermost error with a .type property.
|
|
* Temporal wraps ApplicationFailure in ActivityFailure — the useful info is inside.
|
|
*
|
|
* Uses duck-typing because workflow code cannot import @temporalio/activity types.
|
|
*/
|
|
function unwrapActivityError(error: unknown): {
|
|
message: string;
|
|
type: string | null;
|
|
} {
|
|
let current: unknown = error;
|
|
let typed: { message: string; type: string } | null = null;
|
|
|
|
while (current instanceof Error) {
|
|
if ('type' in current && typeof (current as { type: unknown }).type === 'string') {
|
|
typed = {
|
|
message: current.message,
|
|
type: (current as { type: string }).type,
|
|
};
|
|
}
|
|
current = (current as { cause?: unknown }).cause;
|
|
}
|
|
|
|
if (typed) {
|
|
return typed;
|
|
}
|
|
|
|
return {
|
|
message: error instanceof Error ? error.message : String(error),
|
|
type: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format a structured error string from workflow catch context.
|
|
* Segments are delimited by | for multi-line rendering by WorkflowLogger.
|
|
*/
|
|
export function formatWorkflowError(error: unknown, currentPhase: string | null, currentAgent: string | null): string {
|
|
const unwrapped = unwrapActivityError(error);
|
|
|
|
// Phase context (first segment)
|
|
let phaseContext = 'Pipeline failed';
|
|
if (currentPhase && currentAgent && currentPhase !== currentAgent) {
|
|
phaseContext = `${currentPhase} failed (agent: ${currentAgent})`;
|
|
} else if (currentPhase) {
|
|
phaseContext = `${currentPhase} failed`;
|
|
}
|
|
|
|
const segments: string[] = [phaseContext];
|
|
|
|
if (unwrapped.type) {
|
|
segments.push(unwrapped.type);
|
|
}
|
|
|
|
// Sanitize pipe characters from message to preserve delimiter format
|
|
segments.push(unwrapped.message.replaceAll('|', '/'));
|
|
|
|
if (unwrapped.type) {
|
|
const hint = REMEDIATION_HINTS[unwrapped.type];
|
|
if (hint) {
|
|
segments.push(`Hint: ${hint}`);
|
|
}
|
|
}
|
|
|
|
return segments.join('|');
|
|
}
|