salmon-loop 0.3.2 → 0.5.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/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +12 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +5 -4
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +90 -7
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -147,6 +147,14 @@ export function emitLlmStreamDelta(params) {
|
|
|
147
147
|
timestamp,
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Clean up stream state without emitting end events.
|
|
152
|
+
* Call this when a stream errors and emitLlmStreamEnd won't be reached.
|
|
153
|
+
*/
|
|
154
|
+
export function cleanupLlmStream(streamId) {
|
|
155
|
+
STREAM_CANONICAL_EMITTERS.delete(streamId);
|
|
156
|
+
STREAM_SANITIZATION_STATE.delete(streamId);
|
|
157
|
+
}
|
|
150
158
|
export function emitLlmStreamEnd(params) {
|
|
151
159
|
const { emit, policy, kind, step, streamId, finishReason } = params;
|
|
152
160
|
if (!emit)
|
package/dist/core/llm/redact.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../utils/serialize.js';
|
|
1
2
|
const SECRET_KEY_REGEX = /(api[-_]?key|authorization|token|secret|password|cookie)/i;
|
|
2
3
|
const STRING_SECRET_PATTERNS = [
|
|
3
4
|
{
|
|
@@ -17,9 +18,6 @@ const STRING_SECRET_PATTERNS = [
|
|
|
17
18
|
replacement: '$1=[REDACTED]',
|
|
18
19
|
},
|
|
19
20
|
];
|
|
20
|
-
function isRecord(value) {
|
|
21
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
22
|
-
}
|
|
23
21
|
function truncate(value, max = 500) {
|
|
24
22
|
if (value.length <= max)
|
|
25
23
|
return value;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for retrying asynchronous operations with exponential backoff.
|
|
3
|
+
*/
|
|
1
4
|
import { LIMITS } from '../config/limits.js';
|
|
5
|
+
import { getLogger } from '../observability/logger.js';
|
|
2
6
|
const DEFAULT_OPTIONS = {
|
|
3
7
|
maxRetries: LIMITS.retry.api.maxAttempts,
|
|
4
8
|
initialDelayMs: LIMITS.retry.api.initialDelayMs,
|
|
@@ -42,8 +46,9 @@ export async function withRetry(fn, options = {}) {
|
|
|
42
46
|
try {
|
|
43
47
|
await opts.onRetry({ attempt: attempt + 1, delayMs: effectiveDelay, error });
|
|
44
48
|
}
|
|
45
|
-
catch {
|
|
49
|
+
catch (retryHandlerError) {
|
|
46
50
|
// Ignore onRetry handler failures.
|
|
51
|
+
getLogger().debug(`[RetryUtils] onRetry handler failed: ${retryHandlerError instanceof Error ? retryHandlerError.message : String(retryHandlerError)}`);
|
|
47
52
|
}
|
|
48
53
|
await new Promise((resolve, reject) => {
|
|
49
54
|
const timer = setTimeout(() => {
|
|
@@ -91,8 +96,9 @@ export async function* withStreamRetry(streamFactory, options = {}) {
|
|
|
91
96
|
try {
|
|
92
97
|
await opts.onRetry({ attempt: attempt + 1, delayMs: effectiveDelay, error });
|
|
93
98
|
}
|
|
94
|
-
catch {
|
|
99
|
+
catch (retryHandlerError) {
|
|
95
100
|
// Ignore onRetry handler failures.
|
|
101
|
+
getLogger().debug(`[RetryUtils] onRetry handler failed (stream): ${retryHandlerError instanceof Error ? retryHandlerError.message : String(retryHandlerError)}`);
|
|
96
102
|
}
|
|
97
103
|
await new Promise((resolve, reject) => {
|
|
98
104
|
const timer = setTimeout(() => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../observability/logger.js';
|
|
1
2
|
function normalizeToolInput(raw) {
|
|
2
3
|
if (typeof raw !== 'string')
|
|
3
4
|
return raw;
|
|
@@ -12,14 +13,15 @@ function normalizeToolInput(raw) {
|
|
|
12
13
|
try {
|
|
13
14
|
parsed = JSON.parse(nested);
|
|
14
15
|
}
|
|
15
|
-
catch {
|
|
16
|
-
|
|
16
|
+
catch (error) {
|
|
17
|
+
getLogger().debug(`[StreamUtils] Failed to parse nested JSON string: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
return parsed;
|
|
21
22
|
}
|
|
22
|
-
catch {
|
|
23
|
+
catch (error) {
|
|
24
|
+
getLogger().debug(`[StreamUtils] Failed to normalize tool input JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
25
|
return raw;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AiSdkLLM } from './ai-sdk.js';
|
|
2
|
+
import { StubLLM } from './openai.js';
|
|
3
|
+
/**
|
|
4
|
+
* Model alias → concrete model ID mapping.
|
|
5
|
+
*
|
|
6
|
+
* These follow the Anthropic model naming conventions.
|
|
7
|
+
* Providers that don't support these IDs will fall back to StubLLM.
|
|
8
|
+
*/
|
|
9
|
+
const MODEL_ALIAS_MAP = {
|
|
10
|
+
haiku: 'claude-3-5-haiku-20241022',
|
|
11
|
+
sonnet: 'claude-sonnet-4-20250514',
|
|
12
|
+
opus: 'claude-opus-4-20250514',
|
|
13
|
+
};
|
|
14
|
+
function resolveModelId(alias) {
|
|
15
|
+
return MODEL_ALIAS_MAP[alias] ?? alias;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a SubAgentLlmFactory that produces model-specific LLM instances
|
|
19
|
+
* from a base provider configuration.
|
|
20
|
+
*
|
|
21
|
+
* The factory reuses the parent provider's connection settings (API key,
|
|
22
|
+
* base URL, headers) and only overrides the model ID.
|
|
23
|
+
*/
|
|
24
|
+
export function createSubAgentLlmFactory(baseProvider) {
|
|
25
|
+
return (modelAlias) => {
|
|
26
|
+
// 'inherit' means use the parent LLM — don't create a new instance.
|
|
27
|
+
if (!modelAlias || modelAlias === 'inherit')
|
|
28
|
+
return undefined;
|
|
29
|
+
const modelId = resolveModelId(modelAlias);
|
|
30
|
+
if (baseProvider.type === 'openai-compatible' || baseProvider.type === 'openai') {
|
|
31
|
+
const clientPackage = baseProvider.clientPackage === '@ai-sdk/openai'
|
|
32
|
+
? '@ai-sdk/openai'
|
|
33
|
+
: '@ai-sdk/openai-compatible';
|
|
34
|
+
if (!baseProvider.api.apiKey) {
|
|
35
|
+
return new StubLLM();
|
|
36
|
+
}
|
|
37
|
+
return new AiSdkLLM({
|
|
38
|
+
clientPackage,
|
|
39
|
+
providerName: baseProvider.id,
|
|
40
|
+
apiKey: baseProvider.api.apiKey,
|
|
41
|
+
baseUrl: baseProvider.api.baseUrl,
|
|
42
|
+
modelId,
|
|
43
|
+
headers: baseProvider.api.headers,
|
|
44
|
+
timeoutMs: baseProvider.api.timeoutMs,
|
|
45
|
+
capabilities: baseProvider.capabilities,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=sub-agent-factory.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolCallingStubLLM — A deterministic LLM stub that emits tool_calls
|
|
3
|
+
* to drive the chatWithTools loop in tests and evaluation harnesses.
|
|
4
|
+
*
|
|
5
|
+
* Unlike StubLLM (toolCalling: false, no tool_calls), this stub populates
|
|
6
|
+
* the assistant.tool_calls field so that chatWithTools executes tools and
|
|
7
|
+
* continues the loop.
|
|
8
|
+
*/
|
|
9
|
+
export class ToolCallingStubLLM {
|
|
10
|
+
toolCalling = true;
|
|
11
|
+
turns;
|
|
12
|
+
callCount = 0;
|
|
13
|
+
constructor(turns) {
|
|
14
|
+
this.turns = turns;
|
|
15
|
+
}
|
|
16
|
+
getCapabilities() {
|
|
17
|
+
return {
|
|
18
|
+
toolCalling: true,
|
|
19
|
+
responseFormatJsonObject: false,
|
|
20
|
+
streaming: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async chat(_messages) {
|
|
24
|
+
const idx = this.callCount;
|
|
25
|
+
const turn = this.turns[idx] ?? { content: '[stub: no more turns]' };
|
|
26
|
+
this.callCount++;
|
|
27
|
+
return {
|
|
28
|
+
role: 'assistant',
|
|
29
|
+
content: turn.content ?? '',
|
|
30
|
+
tool_calls: turn.toolCalls,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
getCallCount() {
|
|
34
|
+
return this.callCount;
|
|
35
|
+
}
|
|
36
|
+
async createPlan(_context, instruction) {
|
|
37
|
+
return {
|
|
38
|
+
goal: `Stub plan for: ${instruction}`,
|
|
39
|
+
files: [],
|
|
40
|
+
changes: [],
|
|
41
|
+
verify: 'echo ok',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async createPatch() {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=tool-calling-stub.js.map
|
package/dist/core/llm/utils.js
CHANGED
|
@@ -13,21 +13,32 @@ export function formatContextForPrompt(context, options = {}) {
|
|
|
13
13
|
export function parsePlanFromLLMContent(content) {
|
|
14
14
|
const trimmed = String(content ?? '').trim();
|
|
15
15
|
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
16
|
-
|
|
16
|
+
const preview = trimmed.slice(0, 80);
|
|
17
|
+
throw new Error(`${text.llm.planInvalidJson} — Content must start with { and end with }. Got: ${preview}${trimmed.length > 80 ? '…' : ''}`);
|
|
17
18
|
}
|
|
18
19
|
let parsed;
|
|
19
20
|
try {
|
|
20
21
|
parsed = JSON.parse(trimmed);
|
|
21
22
|
}
|
|
22
|
-
catch {
|
|
23
|
-
|
|
23
|
+
catch (jsonError) {
|
|
24
|
+
const errorMsg = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
25
|
+
throw new Error(`${text.llm.planInvalidJson} — JSON parse error: ${errorMsg}`);
|
|
24
26
|
}
|
|
25
27
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
26
|
-
throw new Error(text.llm.planInvalidJson);
|
|
28
|
+
throw new Error(`${text.llm.planInvalidJson} — Expected object, got ${Array.isArray(parsed) ? 'array' : typeof parsed}`);
|
|
27
29
|
}
|
|
28
30
|
const plan = parsed;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
const missingKeys = [];
|
|
32
|
+
if (!plan.goal)
|
|
33
|
+
missingKeys.push('goal');
|
|
34
|
+
if (!Array.isArray(plan.files))
|
|
35
|
+
missingKeys.push('files (must be array)');
|
|
36
|
+
if (!Array.isArray(plan.changes))
|
|
37
|
+
missingKeys.push('changes (must be array)');
|
|
38
|
+
if (!plan.verify)
|
|
39
|
+
missingKeys.push('verify');
|
|
40
|
+
if (missingKeys.length > 0) {
|
|
41
|
+
throw new Error(`${text.llm.planInvalid} — Missing or invalid keys: ${missingKeys.join(', ')}`);
|
|
31
42
|
}
|
|
32
43
|
return plan;
|
|
33
44
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
2
3
|
import { jsonSchemaToZod } from '../schema/json-schema-to-zod.js';
|
|
3
4
|
const SAFE_TOKEN_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
4
5
|
function normalizeToken(value) {
|
|
@@ -16,11 +17,11 @@ function safeToken(value, fallback) {
|
|
|
16
17
|
return fallback;
|
|
17
18
|
}
|
|
18
19
|
function isPromptOptions(value) {
|
|
19
|
-
return Boolean(value &&
|
|
20
|
-
typeof value === 'object' &&
|
|
20
|
+
return Boolean(isRecord(value) &&
|
|
21
21
|
'serverName' in value &&
|
|
22
22
|
'client' in value &&
|
|
23
|
-
|
|
23
|
+
isRecord(value.client) &&
|
|
24
|
+
typeof value.client.listPrompts === 'function');
|
|
24
25
|
}
|
|
25
26
|
function buildFallbackSchemaFromArguments(args = []) {
|
|
26
27
|
const shape = {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../../observability/logger.js';
|
|
1
2
|
import { ResourceCache } from '../cache/resource-cache.js';
|
|
2
3
|
export class ResourceContextProviderError extends Error {
|
|
3
4
|
diagnostic;
|
|
@@ -252,7 +253,8 @@ function normalizeText(text, mimeType) {
|
|
|
252
253
|
try {
|
|
253
254
|
return JSON.stringify(JSON.parse(text), null, 2);
|
|
254
255
|
}
|
|
255
|
-
catch {
|
|
256
|
+
catch (error) {
|
|
257
|
+
getLogger().debug(`[ResourceContextProvider] Failed to normalize JSON text: ${error instanceof Error ? error.message : String(error)}`);
|
|
256
258
|
return text;
|
|
257
259
|
}
|
|
258
260
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { LIMITS } from '../../config/limits.js';
|
|
3
3
|
import { Phase } from '../../types/runtime.js';
|
|
4
|
+
import { safeStringify } from '../../utils/serialize.js';
|
|
4
5
|
import { classifyMcpTool } from '../policy/classifier.js';
|
|
5
6
|
import { jsonSchemaToZod } from '../schema/json-schema-to-zod.js';
|
|
6
7
|
const MCP_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
@@ -111,10 +112,9 @@ export function wrapMcpToolResult(result) {
|
|
|
111
112
|
}
|
|
112
113
|
export function mcpToolToToolSpec(input) {
|
|
113
114
|
const override = findOverride(input.server.capabilities, input.tool.name);
|
|
114
|
-
const classification = classifyMcpTool({
|
|
115
|
-
tool: input.tool,
|
|
115
|
+
const classification = classifyMcpTool(input.tool, {
|
|
116
116
|
trust: input.server.trust,
|
|
117
|
-
override,
|
|
117
|
+
override: override ? { sideEffects: override } : undefined,
|
|
118
118
|
});
|
|
119
119
|
const phase = input.server.capabilities.tools.phases[0] ?? Phase.VERIFY;
|
|
120
120
|
const grantDecision = input.policy.decideTool({
|
|
@@ -169,7 +169,7 @@ export async function registerMcpV2Tools(input) {
|
|
|
169
169
|
if (!catalog)
|
|
170
170
|
continue;
|
|
171
171
|
for (const tool of catalog.tools) {
|
|
172
|
-
const classification = classifyMcpTool(
|
|
172
|
+
const classification = classifyMcpTool(tool, { trust: server.trust });
|
|
173
173
|
const decision = input.policy.decideTool({
|
|
174
174
|
server: server.name,
|
|
175
175
|
toolName: tool.name,
|
|
@@ -267,16 +267,7 @@ function summarizeMcpAuthorization(input, args, riskLevel, sideEffects) {
|
|
|
267
267
|
: { kind: 'classified', reason: input.classification.reason },
|
|
268
268
|
args,
|
|
269
269
|
};
|
|
270
|
-
return safeStringify(payload);
|
|
271
|
-
}
|
|
272
|
-
function safeStringify(value, maxLength = 1200) {
|
|
273
|
-
try {
|
|
274
|
-
const raw = JSON.stringify(value);
|
|
275
|
-
return raw.length <= maxLength ? raw : `${raw.slice(0, maxLength)}...`;
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
return '[Unserializable]';
|
|
279
|
-
}
|
|
270
|
+
return safeStringify(payload, { maxLength: 1200 });
|
|
280
271
|
}
|
|
281
272
|
function coerceRecord(input) {
|
|
282
273
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../../observability/logger.js';
|
|
1
2
|
import { withPromptServer } from './prompt-catalog.js';
|
|
2
3
|
import { withResourceServer, withResourceTemplateServer } from './resource-catalog.js';
|
|
3
4
|
import { withToolServer } from './tool-catalog.js';
|
|
@@ -5,7 +6,8 @@ async function safeList(fn, fallback) {
|
|
|
5
6
|
try {
|
|
6
7
|
return await fn();
|
|
7
8
|
}
|
|
8
|
-
catch {
|
|
9
|
+
catch (error) {
|
|
10
|
+
getLogger().debug(`[McpDiscovery] safeList call failed, returning fallback: ${error instanceof Error ? error.message : String(error)}`);
|
|
9
11
|
return fallback;
|
|
10
12
|
}
|
|
11
13
|
}
|
|
@@ -2,6 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { CallToolResultSchema, PromptListChangedNotificationSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ToolListChangedNotificationSchema, GetPromptResultSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import { LIMITS } from '../../config/limits.js';
|
|
4
4
|
import { getLogger } from '../../observability/logger.js';
|
|
5
|
+
import { errorMessage } from '../../utils/error.js';
|
|
5
6
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
6
7
|
import { discoverMcpCatalog } from '../catalog/discovery.js';
|
|
7
8
|
import { McpNotificationRouter } from '../catalog/notification-router.js';
|
|
@@ -93,7 +94,7 @@ export class McpConnectionManager {
|
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
95
96
|
entry.status = 'degraded';
|
|
96
|
-
entry.error =
|
|
97
|
+
entry.error = errorMessage(error);
|
|
97
98
|
getLogger().warn(`Failed to connect MCP server ${server.name}: ${entry.error}`);
|
|
98
99
|
}
|
|
99
100
|
return this.view(entry);
|
|
@@ -183,7 +184,7 @@ export class McpConnectionManager {
|
|
|
183
184
|
entry.subscribedResources.add(uri);
|
|
184
185
|
}
|
|
185
186
|
catch (error) {
|
|
186
|
-
const message =
|
|
187
|
+
const message = errorMessage(error);
|
|
187
188
|
getLogger().warn(`MCP server ${entry.server.name} resource subscription failed for ${uri}: ${message}`);
|
|
188
189
|
}
|
|
189
190
|
}
|
|
@@ -207,8 +208,9 @@ export class McpConnectionManager {
|
|
|
207
208
|
try {
|
|
208
209
|
await entry.client.unsubscribeResource({ uri }, { timeout: LIMITS.defaultToolTimeoutMs });
|
|
209
210
|
}
|
|
210
|
-
catch {
|
|
211
|
+
catch (error) {
|
|
211
212
|
// best-effort unsubscribe during shutdown
|
|
213
|
+
getLogger().debug(`[McpConnectionManager] Failed to unsubscribe resource ${uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
entry.subscribedResources.clear();
|
|
@@ -223,8 +225,9 @@ export class McpConnectionManager {
|
|
|
223
225
|
}
|
|
224
226
|
await entry.client.close();
|
|
225
227
|
}
|
|
226
|
-
catch {
|
|
228
|
+
catch (error) {
|
|
227
229
|
// best-effort shutdown
|
|
230
|
+
getLogger().debug(`[McpConnectionManager] Error during connection shutdown: ${error instanceof Error ? error.message : String(error)}`);
|
|
228
231
|
}
|
|
229
232
|
}
|
|
230
233
|
view(entry) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PassThrough } from 'node:stream';
|
|
2
2
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
3
|
import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
|
|
4
|
+
import { getLogger } from '../../observability/logger.js';
|
|
4
5
|
import { spawnInteractiveProcess } from '../../runtime/process-runner.js';
|
|
5
6
|
export function buildStrictStdioEnvironment(env) {
|
|
6
7
|
return { ...env };
|
|
@@ -81,16 +82,18 @@ export class StrictStdioClientTransport {
|
|
|
81
82
|
try {
|
|
82
83
|
child.stdin?.end?.();
|
|
83
84
|
}
|
|
84
|
-
catch {
|
|
85
|
+
catch (error) {
|
|
85
86
|
// best-effort shutdown
|
|
87
|
+
getLogger().debug(`[TransportFactory] Error ending stdin: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
88
|
}
|
|
87
89
|
await Promise.race([closePromise, unrefTimeout(2_000)]);
|
|
88
90
|
if (child.exitCode === null) {
|
|
89
91
|
try {
|
|
90
92
|
child.kill('SIGTERM');
|
|
91
93
|
}
|
|
92
|
-
catch {
|
|
94
|
+
catch (error) {
|
|
93
95
|
// best-effort shutdown
|
|
96
|
+
getLogger().debug(`[TransportFactory] Error sending SIGTERM: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
97
|
}
|
|
95
98
|
await Promise.race([closePromise, unrefTimeout(2_000)]);
|
|
96
99
|
}
|
|
@@ -98,8 +101,9 @@ export class StrictStdioClientTransport {
|
|
|
98
101
|
try {
|
|
99
102
|
child.kill('SIGKILL');
|
|
100
103
|
}
|
|
101
|
-
catch {
|
|
104
|
+
catch (error) {
|
|
102
105
|
// best-effort shutdown
|
|
106
|
+
getLogger().debug(`[TransportFactory] Error sending SIGKILL: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
this.readBuffer.clear();
|
|
@@ -120,7 +120,7 @@ export class McpSamplingProvider {
|
|
|
120
120
|
throw new Error('MCP_SAMPLING_TOKEN_LIMIT_EXCEEDED');
|
|
121
121
|
}
|
|
122
122
|
const result = await this.llm.chat([{ role: 'user', content: input.prompt }]);
|
|
123
|
-
return typeof result === 'string' ? result : String(result
|
|
123
|
+
return typeof result === 'string' ? result : String(result.content ?? '');
|
|
124
124
|
}
|
|
125
125
|
async createMessage(params, options = {}) {
|
|
126
126
|
const sanitizedParams = sanitizeForAudit(params, this.maxDepth);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
3
4
|
export function jsonSchemaToZod(jsonSchema) {
|
|
4
5
|
return jsonSchemaToZodWithContext(jsonSchema, jsonSchema);
|
|
5
6
|
}
|
|
@@ -499,7 +500,7 @@ function propertyNamesToZod(jsonSchema, rootSchema) {
|
|
|
499
500
|
return jsonSchemaToZodWithContext(jsonSchema, rootSchema);
|
|
500
501
|
}
|
|
501
502
|
function isPlainObject(value) {
|
|
502
|
-
return
|
|
503
|
+
return isRecord(value);
|
|
503
504
|
}
|
|
504
505
|
function isMultipleOf(value, divisor) {
|
|
505
506
|
const quotient = value / divisor;
|
|
@@ -77,11 +77,12 @@ export function buildRelevantMemoryCandidates(context) {
|
|
|
77
77
|
tags: ['rules', 'project'],
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
const userPrefs = trimToUndefined(knowledge?.user_preferences);
|
|
81
|
+
if (userPrefs) {
|
|
81
82
|
candidates.push({
|
|
82
83
|
path: '.salmonloop/knowledge/user_preferences',
|
|
83
84
|
title: 'User preferences',
|
|
84
|
-
summary:
|
|
85
|
+
summary: userPrefs,
|
|
85
86
|
tags: ['preferences', 'user'],
|
|
86
87
|
});
|
|
87
88
|
}
|
|
@@ -96,11 +97,12 @@ export function buildRelevantMemoryCandidates(context) {
|
|
|
96
97
|
tags: ['architecture', ...(decision.related_files ?? []).map((file) => file.toLowerCase())],
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
const aiInstructions = trimToUndefined(metadata?.aiInstructions);
|
|
101
|
+
if (aiInstructions) {
|
|
100
102
|
candidates.push({
|
|
101
103
|
path: '.salmonloop/project/ai-instructions',
|
|
102
104
|
title: 'Project AI instructions',
|
|
103
|
-
summary:
|
|
105
|
+
summary: aiInstructions,
|
|
104
106
|
tags: ['instructions', 'project'],
|
|
105
107
|
});
|
|
106
108
|
}
|
|
@@ -28,8 +28,9 @@ async function writeJsonAtomic(targetPath, data) {
|
|
|
28
28
|
try {
|
|
29
29
|
await rename(tmpPath, targetPath);
|
|
30
30
|
}
|
|
31
|
-
catch {
|
|
31
|
+
catch (error) {
|
|
32
32
|
// Retry once with a fresh temp file to reduce transient rename failures.
|
|
33
|
+
getLogger().debug(`[AuditFile] Atomic rename failed, retrying: ${error instanceof Error ? error.message : String(error)}`);
|
|
33
34
|
const retryTmpPath = path.join(dir, `.${base}.tmp-${process.pid}-${Date.now()}-retry`);
|
|
34
35
|
await writeFile(retryTmpPath, payload);
|
|
35
36
|
await rename(retryTmpPath, targetPath);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from './logger.js';
|
|
1
2
|
const auditTrail = [];
|
|
2
3
|
const auditContext = {};
|
|
3
4
|
const DEFAULT_BUFFER_LIMITS = {
|
|
@@ -19,7 +20,8 @@ function estimateEventSize(event) {
|
|
|
19
20
|
try {
|
|
20
21
|
return Buffer.byteLength(JSON.stringify(event), 'utf-8');
|
|
21
22
|
}
|
|
22
|
-
catch {
|
|
23
|
+
catch (error) {
|
|
24
|
+
getLogger().debug(`[AuditTrail] Failed to estimate event size: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
25
|
return 0;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord } from '../utils/serialize.js';
|
|
1
2
|
import { getAuditTrail } from './audit-trail.js';
|
|
2
3
|
function safeString(value) {
|
|
3
4
|
if (typeof value !== 'string')
|
|
@@ -23,13 +24,13 @@ export function extractAuthorizationDecisionsFromAuditTrail(auditTrail) {
|
|
|
23
24
|
continue;
|
|
24
25
|
if (event.action !== 'authorization.decision')
|
|
25
26
|
continue;
|
|
26
|
-
|
|
27
|
-
if (!details || typeof details !== 'object')
|
|
27
|
+
if (!event.details || typeof event.details !== 'object')
|
|
28
28
|
continue;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
29
|
+
const d = asRecord(event.details);
|
|
30
|
+
const callId = safeString(d.callId);
|
|
31
|
+
const toolName = safeString(d.toolName);
|
|
32
|
+
const phase = safeString(d.phase) ?? safeString(event.phase);
|
|
33
|
+
const outcome = safeString(d.outcome);
|
|
33
34
|
if (!callId || !toolName || !phase || !outcome)
|
|
34
35
|
continue;
|
|
35
36
|
decisions.push({
|
|
@@ -37,12 +38,12 @@ export function extractAuthorizationDecisionsFromAuditTrail(auditTrail) {
|
|
|
37
38
|
toolName,
|
|
38
39
|
phase: phase,
|
|
39
40
|
outcome: outcome,
|
|
40
|
-
source: (safeString(
|
|
41
|
-
reason: safeString(
|
|
42
|
-
ttlMs: safeNumber(
|
|
43
|
-
persist: safeString(
|
|
44
|
-
riskLevel: safeString(
|
|
45
|
-
sideEffects: safeStringArray(
|
|
41
|
+
source: (safeString(d.source) ?? 'unknown'),
|
|
42
|
+
reason: safeString(d.reason),
|
|
43
|
+
ttlMs: safeNumber(d.ttlMs),
|
|
44
|
+
persist: safeString(d.persist),
|
|
45
|
+
riskLevel: safeString(d.riskLevel),
|
|
46
|
+
sideEffects: safeStringArray(d.sideEffects),
|
|
46
47
|
timestamp: safeString(event.timestamp) ?? new Date().toISOString(),
|
|
47
48
|
});
|
|
48
49
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { text } from '../../locales/index.js';
|
|
2
|
+
import { isRecord } from '../utils/serialize.js';
|
|
2
3
|
import { getAuditTrail } from './audit-trail.js';
|
|
3
4
|
import { REDACTED_ERROR_TOKEN } from './error-envelope.js';
|
|
4
5
|
function mapLlmCodeToMessage(code) {
|
|
@@ -193,7 +194,7 @@ export function mapErrorForAudit(input) {
|
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
196
|
function buildLangfuseHttpFailed(details) {
|
|
196
|
-
const status = typeof details
|
|
197
|
+
const status = isRecord(details) && typeof details.status === 'number' ? details.status : undefined;
|
|
197
198
|
if (!status)
|
|
198
199
|
return undefined;
|
|
199
200
|
if (status === 401 || status === 403) {
|
|
@@ -307,8 +307,9 @@ export class Logger {
|
|
|
307
307
|
// Only remove from queue if write was successful
|
|
308
308
|
this.logQueue.splice(0, contentToFlush.length);
|
|
309
309
|
}
|
|
310
|
-
catch {
|
|
310
|
+
catch (error) {
|
|
311
311
|
// Keep logs in queue for next retry
|
|
312
|
+
this.debug(`[Logger] Failed to flush logs to file: ${error instanceof Error ? error.message : String(error)}`);
|
|
312
313
|
}
|
|
313
314
|
finally {
|
|
314
315
|
this.isFlushing = false;
|
|
@@ -177,6 +177,30 @@ export class Monitor {
|
|
|
177
177
|
/**
|
|
178
178
|
* Generate metrics report
|
|
179
179
|
*/
|
|
180
|
+
getStructuredReport() {
|
|
181
|
+
const durations = [...this.applyBackMetrics.durations].sort((a, b) => a - b);
|
|
182
|
+
return {
|
|
183
|
+
checkpoint: {
|
|
184
|
+
createAttempts: this.checkpointMetrics.createAttempts,
|
|
185
|
+
createFailures: this.checkpointMetrics.createFailures,
|
|
186
|
+
createFailureRate: this.getCheckpointCreateFailureRate(),
|
|
187
|
+
cleanupAttempts: this.checkpointMetrics.cleanupAttempts,
|
|
188
|
+
cleanupFailures: this.checkpointMetrics.cleanupFailures,
|
|
189
|
+
},
|
|
190
|
+
applyBack: {
|
|
191
|
+
attempts: this.applyBackMetrics.attempts,
|
|
192
|
+
failures: this.applyBackMetrics.failures,
|
|
193
|
+
avgDurationMs: this.getApplyBackAvgDuration(),
|
|
194
|
+
p50DurationMs: durations.length > 0 ? durations[Math.floor(durations.length * 0.5)] : 0,
|
|
195
|
+
p95DurationMs: durations.length > 0 ? durations[Math.floor(durations.length * 0.95)] : 0,
|
|
196
|
+
},
|
|
197
|
+
errors: this.errorHistory.toArray().map((e) => ({
|
|
198
|
+
type: e.type,
|
|
199
|
+
message: e.message,
|
|
200
|
+
timestamp: e.timestamp.toISOString(),
|
|
201
|
+
})),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
180
204
|
getMetricsReport() {
|
|
181
205
|
let report = `\n${text.monitor.metricsTitle}\n`;
|
|
182
206
|
report += `\n${text.monitor.checkpointCreation}\n`;
|
|
@@ -7,6 +7,7 @@ export function buildRunOutcomeReport(result) {
|
|
|
7
7
|
safeHint: result.safeHint,
|
|
8
8
|
remediationSteps: result.remediationSteps,
|
|
9
9
|
attempts: result.attempts,
|
|
10
|
+
durationMs: result.durationMs,
|
|
10
11
|
failurePhase: result.failurePhase,
|
|
11
12
|
errorCode: result.errorCode,
|
|
12
13
|
changedFiles: result.changedFiles,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord } from '../utils/serialize.js';
|
|
1
2
|
import { getAuditTrail } from './audit-trail.js';
|
|
2
3
|
function safeFiniteNumber(value) {
|
|
3
4
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
@@ -12,11 +13,11 @@ export function extractTokenUsageFromAuditTrail(auditTrail) {
|
|
|
12
13
|
continue;
|
|
13
14
|
if (event.action !== 'llm.usage')
|
|
14
15
|
continue;
|
|
15
|
-
|
|
16
|
-
if (!details || typeof details !== 'object')
|
|
16
|
+
if (!event.details || typeof event.details !== 'object')
|
|
17
17
|
continue;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const d = asRecord(event.details);
|
|
19
|
+
const promptTokens = safeFiniteNumber(d.promptTokens);
|
|
20
|
+
const completionTokens = safeFiniteNumber(d.completionTokens);
|
|
20
21
|
if (typeof promptTokens === 'number')
|
|
21
22
|
inputTokens += promptTokens;
|
|
22
23
|
if (typeof completionTokens === 'number')
|