salmon-loop 0.4.1 → 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/provider.js +2 -10
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/run/handler.js +3 -1
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +10 -0
- 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/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/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 +30 -28
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +18 -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 +4 -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/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/paths.js +2 -2
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
- 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 +3 -1
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/explore.js +5 -2
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -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 +13 -8
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/errors.js +5 -4
- 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 +3 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +4 -2
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -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/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +3 -1
- package/dist/core/prompts/registry.js +1 -1
- 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 +3 -1
- 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 +3 -2
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +2 -1
- 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/compression.js +3 -1
- package/dist/core/session/manager.js +2 -1
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +11 -4
- package/dist/core/skills/permissions.js +2 -2
- 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 +2 -1
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +18 -17
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/core/manager.js +24 -1
- package/dist/core/sub-agent/registry-defaults.js +2 -2
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/tools/task-spawn.js +7 -4
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +3 -1
- 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/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 +76 -1
- 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 +12 -4
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +3 -1
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/permissions/permission-rules.js +3 -1
- package/dist/core/tools/router.js +88 -5
- package/dist/core/tools/session.js +10 -5
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +5 -2
- 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/languages/python/index.js +154 -0
- package/dist/locales/en.js +6 -0
- package/package.json +2 -1
|
@@ -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
|
}
|
|
@@ -23,6 +23,9 @@ function resolveModelId(alias) {
|
|
|
23
23
|
*/
|
|
24
24
|
export function createSubAgentLlmFactory(baseProvider) {
|
|
25
25
|
return (modelAlias) => {
|
|
26
|
+
// 'inherit' means use the parent LLM — don't create a new instance.
|
|
27
|
+
if (!modelAlias || modelAlias === 'inherit')
|
|
28
|
+
return undefined;
|
|
26
29
|
const modelId = resolveModelId(modelAlias);
|
|
27
30
|
if (baseProvider.type === 'openai-compatible' || baseProvider.type === 'openai') {
|
|
28
31
|
const clientPackage = baseProvider.clientPackage === '@ai-sdk/openai'
|
|
@@ -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,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
|
}
|
|
@@ -208,8 +208,9 @@ export class McpConnectionManager {
|
|
|
208
208
|
try {
|
|
209
209
|
await entry.client.unsubscribeResource({ uri }, { timeout: LIMITS.defaultToolTimeoutMs });
|
|
210
210
|
}
|
|
211
|
-
catch {
|
|
211
|
+
catch (error) {
|
|
212
212
|
// best-effort unsubscribe during shutdown
|
|
213
|
+
getLogger().debug(`[McpConnectionManager] Failed to unsubscribe resource ${uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
entry.subscribedResources.clear();
|
|
@@ -224,8 +225,9 @@ export class McpConnectionManager {
|
|
|
224
225
|
}
|
|
225
226
|
await entry.client.close();
|
|
226
227
|
}
|
|
227
|
-
catch {
|
|
228
|
+
catch (error) {
|
|
228
229
|
// best-effort shutdown
|
|
230
|
+
getLogger().debug(`[McpConnectionManager] Error during connection shutdown: ${error instanceof Error ? error.message : String(error)}`);
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
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();
|
|
@@ -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
|
}
|
|
@@ -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,
|
|
@@ -93,19 +93,16 @@ class DefaultPermissionGate {
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
mapAuthorizationDecision(decision) {
|
|
96
|
+
const source = decision.source === 'auto' || decision.source === 'allowlist' ? 'policy' : decision.source;
|
|
96
97
|
if (decision.outcome === 'deny') {
|
|
97
|
-
return {
|
|
98
|
-
kind: 'deny',
|
|
99
|
-
reason: decision.reason ?? 'denied',
|
|
100
|
-
source: decision.source === 'auto' || decision.source === 'allowlist'
|
|
101
|
-
? 'policy'
|
|
102
|
-
: decision.source,
|
|
103
|
-
};
|
|
98
|
+
return { kind: 'deny', reason: decision.reason ?? 'denied', source };
|
|
104
99
|
}
|
|
105
100
|
return {
|
|
106
101
|
kind: 'allow',
|
|
107
102
|
reason: decision.reason,
|
|
108
|
-
source
|
|
103
|
+
source,
|
|
104
|
+
ttlMs: decision.ttlMs,
|
|
105
|
+
persist: decision.persist,
|
|
109
106
|
};
|
|
110
107
|
}
|
|
111
108
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes, createHash } from 'crypto';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { mkdir, readFile, rename, stat, writeFile } from '../adapters/fs/node-fs.js';
|
|
4
|
+
import { getLogger } from '../observability/logger.js';
|
|
4
5
|
const SESSION_ID_RE = /^[a-zA-Z0-9_-]{6,64}$/;
|
|
5
6
|
export function assertValidSessionId(sessionId) {
|
|
6
7
|
if (!SESSION_ID_RE.test(sessionId)) {
|
|
@@ -31,8 +32,8 @@ async function resolveGitDir(repoRoot) {
|
|
|
31
32
|
if (st.isDirectory())
|
|
32
33
|
return dotGit;
|
|
33
34
|
}
|
|
34
|
-
catch {
|
|
35
|
-
|
|
35
|
+
catch (error) {
|
|
36
|
+
getLogger().debug(`[PlanStorage] .git stat failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
37
|
}
|
|
37
38
|
try {
|
|
38
39
|
const raw = await readFile(dotGit, 'utf-8');
|
|
@@ -43,7 +44,8 @@ async function resolveGitDir(repoRoot) {
|
|
|
43
44
|
const gitdir = m[1].trim();
|
|
44
45
|
return path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
|
|
45
46
|
}
|
|
46
|
-
catch {
|
|
47
|
+
catch (error) {
|
|
48
|
+
getLogger().debug(`[PlanStorage] .git read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
49
|
return null;
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -61,7 +63,8 @@ export async function ensureSalmonloopIgnored(repoRoot) {
|
|
|
61
63
|
try {
|
|
62
64
|
existing = await readFile(excludePath, 'utf-8');
|
|
63
65
|
}
|
|
64
|
-
catch {
|
|
66
|
+
catch (error) {
|
|
67
|
+
getLogger().debug(`[PlanStorage] Failed to read git exclude file: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
68
|
existing = '';
|
|
66
69
|
}
|
|
67
70
|
if (existing.split('\n').some((l) => l.trim() === '.salmonloop/'))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
|
+
import { pythonPlugin } from '../../languages/python/index.js';
|
|
2
3
|
import { typescriptPlugin, tsxPlugin, javascriptPlugin } from '../../languages/typescript/index.js';
|
|
3
4
|
import { readdir } from '../adapters/fs/node-fs.js';
|
|
4
5
|
import { getLogger } from '../observability/logger.js';
|
|
@@ -19,10 +20,11 @@ export class PluginLoader {
|
|
|
19
20
|
try {
|
|
20
21
|
// Phase 1: Manually register TypeScript/JavaScript plugins
|
|
21
22
|
getLogger().debug('Loading built-in plugins...');
|
|
23
|
+
this.registerWithValidation(registry, pythonPlugin);
|
|
22
24
|
this.registerWithValidation(registry, typescriptPlugin);
|
|
23
25
|
this.registerWithValidation(registry, tsxPlugin);
|
|
24
26
|
this.registerWithValidation(registry, javascriptPlugin);
|
|
25
|
-
getLogger().debug(`Plugins loaded: ${typescriptPlugin.meta.name}, ${tsxPlugin.meta.name}, ${javascriptPlugin.meta.name}`);
|
|
27
|
+
getLogger().debug(`Plugins loaded: ${pythonPlugin.meta.name}, ${typescriptPlugin.meta.name}, ${tsxPlugin.meta.name}, ${javascriptPlugin.meta.name}`);
|
|
26
28
|
// Phase 2: Load user plugins from .salmonloop/languages/
|
|
27
29
|
if (repoPath) {
|
|
28
30
|
await this.loadUserPlugins(registry, repoPath);
|
|
@@ -156,7 +156,7 @@ export class PromptRegistry {
|
|
|
156
156
|
return this.render('explore_system', { tools: this.getToolsForTemplate() });
|
|
157
157
|
}
|
|
158
158
|
renderAutopilotSystem() {
|
|
159
|
-
return this.render('autopilot_system', {});
|
|
159
|
+
return this.render('autopilot_system', { tools: this.getToolsForTemplate() });
|
|
160
160
|
}
|
|
161
161
|
renderAnswerSystem() {
|
|
162
162
|
return this.render('answer_system', {});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../observability/logger.js';
|
|
1
2
|
import { resolvePhaseVisibleTools } from '../tools/tool-visibility.js';
|
|
2
3
|
import { Phase } from '../types/runtime.js';
|
|
3
4
|
import { getPromptRegistry } from './registry.js';
|
|
@@ -16,7 +17,8 @@ function extractTargetFiles(plan) {
|
|
|
16
17
|
const files = parsed.files.filter((f) => typeof f === 'string');
|
|
17
18
|
return files.length > 0 ? files.join(', ') : undefined;
|
|
18
19
|
}
|
|
19
|
-
catch {
|
|
20
|
+
catch (error) {
|
|
21
|
+
getLogger().debug(`[PromptRuntime] Failed to extract target files from plan JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
22
|
return undefined;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
You are a coding assistant running in "autopilot" mode.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
Your job is to complete the given task by using the available tools to inspect and modify the repository.
|
|
3
|
+
You MUST use tools to accomplish the task — do not just describe what needs to be done, actually do it.
|
|
4
|
+
|
|
5
|
+
## Problem-Solving Strategy
|
|
6
|
+
|
|
7
|
+
1. **Understand**: Read the problem statement carefully. Identify what is failing and what the expected behavior should be.
|
|
8
|
+
2. **Locate**: Find the source file to fix AND all test files that will be affected. Use `code.search` to search for the rule name, function name, or error message across the entire codebase. Pay special attention to CLI integration tests, snapshot tests, and end-to-end tests that assert on the behavior you are changing. Read ALL affected test files before writing any code.
|
|
9
|
+
3. **Diagnose**: Understand the root cause before writing any code. Read surrounding context, not just the error site.
|
|
10
|
+
4. **Fix**: Write the minimal change that fixes the issue. Update ALL affected test files to match the new behavior. Do not refactor unrelated code.
|
|
11
|
+
5. **Verify**: Run the FULL test suite with `shell.exec` (e.g. `pytest`). If tests fail, analyze the output and iterate. Do NOT run only targeted tests — behavioral changes cascade to other tests.
|
|
12
|
+
|
|
13
|
+
## Efficiency Rules
|
|
14
|
+
- Do NOT reproduce the bug. The problem statement already describes it. Go straight from understanding to fixing.
|
|
15
|
+
- Do NOT run multiple exploratory shell commands. Read the source code, understand the logic, write the fix.
|
|
16
|
+
- If you understand the root cause after reading the source, write the fix immediately. Do not verify the bug first.
|
|
17
|
+
- IMPORTANT: The Locate step is not optional. Spending time finding all affected tests upfront saves rounds later.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
- After writing code, you MUST run tests with `shell.exec` to verify your changes. This is not optional — a fix without verification is incomplete.
|
|
21
|
+
- If tests fail, read the failure output carefully, diagnose the issue, and fix it. Do not stop until tests pass. A failing test means your fix is incomplete — find all affected tests and update them.
|
|
22
|
+
- Read test files before modifying code — tests define the expected behavior.
|
|
23
|
+
- Do not modify test files unless the test itself is incorrect. If your fix changes behavior correctly, update the test's expected output to match.
|
|
24
|
+
- Before writing any code, use `code.search` to find ALL tests that reference the changed behavior. Search for: the rule name (e.g. "L031"), the function name, error messages, and expected test output. CLI integration tests and snapshot tests often contain hardcoded expected output that must be updated when behavior changes.
|
|
25
|
+
- Make the smallest possible change. Avoid unrelated refactoring or style changes.
|
|
26
|
+
- Drive the task forward autonomously and answer in the same language as the user.
|
|
27
|
+
|
|
6
28
|
Treat simple repo-relative paths like "smoke.txt", "README.md", or "src/index.ts" as valid paths from the repository root.
|
|
7
29
|
Do not ask the user to validate a path that is already a valid relative path or can be inferred directly from the instruction.
|
|
8
30
|
Use `interaction.ask_user` only when the task is genuinely ambiguous and you cannot safely infer the next action.
|
|
9
31
|
When calling `interaction.ask_user`, the arguments MUST be valid JSON with exactly this shape: {"questions":[{"header":"Short header","question":"One clear question","options":[{"label":"Option 1","description":"Why pick it"},{"label":"Option 2","description":"Why pick it"}],"multiSelect":false}]}.
|
|
10
32
|
Do not call `interaction.ask_user` with free-form strings, a single question field, string-only options, or empty options arrays.
|
|
11
33
|
For simple file creation or edits, call `fs.write_file` directly with {"file":"relative/path.ext","content":"..."} instead of asking the user for the path again.
|
|
34
|
+
|
|
35
|
+
{{> tool_defs}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InMemoryTaskStore } from '@a2a-js/sdk/server';
|
|
2
|
+
import { getLogger } from '../../../observability/logger.js';
|
|
2
3
|
import { buildCanonicalExecutionRequest, buildInstructionFromParts, } from '../../shared/execution-request.js';
|
|
3
4
|
import { parseA2ASkillFlowMode } from '../../shared/flow-mode-mapping.js';
|
|
4
5
|
export function createA2AInteractionExecutor(deps) {
|
|
@@ -346,7 +347,8 @@ export function createA2AInteractionExecutor(deps) {
|
|
|
346
347
|
data: JSON.parse(artifact.content),
|
|
347
348
|
};
|
|
348
349
|
}
|
|
349
|
-
catch {
|
|
350
|
+
catch (error) {
|
|
351
|
+
getLogger().debug(`[A2AExecutor] Failed to parse artifact content as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
350
352
|
// Fall through to text
|
|
351
353
|
}
|
|
352
354
|
}
|
|
@@ -406,6 +406,7 @@ const normalizeA2AExtensionHeadersByVersion = (req, res, next) => {
|
|
|
406
406
|
});
|
|
407
407
|
next();
|
|
408
408
|
};
|
|
409
|
+
import { getLogger } from '../../../observability/logger.js';
|
|
409
410
|
import { isRecord } from '../../../utils/serialize.js';
|
|
410
411
|
const isObjectRecord = isRecord;
|
|
411
412
|
function looksLikeTask(result) {
|
|
@@ -825,7 +826,8 @@ function normalizeSseJsonRpcChunkByMethod(chunk, method) {
|
|
|
825
826
|
const payload = JSON.parse(line.slice('data: '.length));
|
|
826
827
|
return `data: ${JSON.stringify(normalizeJsonRpcPayloadByMethod(method, payload))}`;
|
|
827
828
|
}
|
|
828
|
-
catch {
|
|
829
|
+
catch (error) {
|
|
830
|
+
getLogger().debug(`[A2AServer] Failed to parse SSE JSON-RPC chunk: ${error instanceof Error ? error.message : String(error)}`);
|
|
829
831
|
return line;
|
|
830
832
|
}
|
|
831
833
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../../observability/logger.js';
|
|
1
2
|
function sleep(ms) {
|
|
2
3
|
if (ms <= 0)
|
|
3
4
|
return Promise.resolve();
|
|
@@ -72,8 +73,8 @@ async function safeRelease(terminal) {
|
|
|
72
73
|
try {
|
|
73
74
|
await terminal.release();
|
|
74
75
|
}
|
|
75
|
-
catch {
|
|
76
|
-
|
|
76
|
+
catch (error) {
|
|
77
|
+
getLogger().debug(`[AcpCommandRunner] Failed to release terminal: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
export function createAcpCommandRunner(params) {
|
|
@@ -134,8 +135,8 @@ export function createAcpCommandRunner(params) {
|
|
|
134
135
|
try {
|
|
135
136
|
await terminal.kill();
|
|
136
137
|
}
|
|
137
|
-
catch {
|
|
138
|
-
|
|
138
|
+
catch (error) {
|
|
139
|
+
getLogger().debug(`[AcpCommandRunner] Failed to kill terminal on abort: ${error instanceof Error ? error.message : String(error)}`);
|
|
139
140
|
}
|
|
140
141
|
break;
|
|
141
142
|
}
|
|
@@ -144,8 +145,8 @@ export function createAcpCommandRunner(params) {
|
|
|
144
145
|
try {
|
|
145
146
|
await terminal.kill();
|
|
146
147
|
}
|
|
147
|
-
catch {
|
|
148
|
-
|
|
148
|
+
catch (error) {
|
|
149
|
+
getLogger().debug(`[AcpCommandRunner] Failed to kill terminal on timeout: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
150
|
}
|
|
150
151
|
break;
|
|
151
152
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, open, readFile, rename, stat, unlink, writeFile, } from '../../adapters/fs/node-fs.js';
|
|
2
2
|
import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
|
|
3
3
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
4
|
+
import { getLogger } from '../../observability/logger.js';
|
|
4
5
|
import { hashRepoPath, isPermissionPolicyValue, parseTimestamp, } from './acp-types.js';
|
|
5
6
|
export function createAcpSessionPersistence(options) {
|
|
6
7
|
const deletedSessionIds = new Map();
|
|
@@ -219,7 +220,8 @@ export function createAcpSessionPersistence(options) {
|
|
|
219
220
|
phase: 'PREFLIGHT',
|
|
220
221
|
});
|
|
221
222
|
}
|
|
222
|
-
catch {
|
|
223
|
+
catch (error) {
|
|
224
|
+
getLogger().debug(`[AcpSessionPersistence] Failed to parse stale lock file: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
225
|
try {
|
|
224
226
|
const lockStat = await stat(lockPath);
|
|
225
227
|
const ageMs = Date.now() - lockStat.mtimeMs;
|
|
@@ -231,8 +233,8 @@ export function createAcpSessionPersistence(options) {
|
|
|
231
233
|
}, { source: 'acp', severity: 'medium', scope: 'session', phase: 'PREFLIGHT' });
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
|
-
catch {
|
|
235
|
-
|
|
236
|
+
catch (innerError) {
|
|
237
|
+
getLogger().debug(`[AcpSessionPersistence] Failed to stat lock file for age check: ${innerError instanceof Error ? innerError.message : String(innerError)}`);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
};
|
|
@@ -246,7 +248,8 @@ export function createAcpSessionPersistence(options) {
|
|
|
246
248
|
await lockHandle.writeFile(JSON.stringify({ pid: process.pid, createdAtMs: Date.now() }), 'utf8');
|
|
247
249
|
break;
|
|
248
250
|
}
|
|
249
|
-
catch {
|
|
251
|
+
catch (error) {
|
|
252
|
+
getLogger().debug(`[AcpSessionPersistence] Lock acquire attempt failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
253
|
await tryClearStaleLock();
|
|
251
254
|
const delayMs = Math.min(250, 20 * (attempt + 1));
|
|
252
255
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -271,8 +274,8 @@ export function createAcpSessionPersistence(options) {
|
|
|
271
274
|
const existingRaw = await readFile(options.path, 'utf8');
|
|
272
275
|
existing = normalizePersistedSessionStore(JSON.parse(existingRaw));
|
|
273
276
|
}
|
|
274
|
-
catch {
|
|
275
|
-
|
|
277
|
+
catch (error) {
|
|
278
|
+
getLogger().debug(`[AcpSessionPersistence] Failed to read existing session store for merge: ${error instanceof Error ? error.message : String(error)}`);
|
|
276
279
|
}
|
|
277
280
|
const merged = new Map();
|
|
278
281
|
const mergedDeletedSessions = pruneDeletedSessionRecords([
|
|
@@ -311,14 +314,14 @@ export function createAcpSessionPersistence(options) {
|
|
|
311
314
|
try {
|
|
312
315
|
await lockHandle.close();
|
|
313
316
|
}
|
|
314
|
-
catch {
|
|
315
|
-
|
|
317
|
+
catch (error) {
|
|
318
|
+
getLogger().debug(`[AcpSessionPersistence] Failed to close lock handle: ${error instanceof Error ? error.message : String(error)}`);
|
|
316
319
|
}
|
|
317
320
|
try {
|
|
318
321
|
await unlink(lockPath);
|
|
319
322
|
}
|
|
320
|
-
catch {
|
|
321
|
-
|
|
323
|
+
catch (error) {
|
|
324
|
+
getLogger().debug(`[AcpSessionPersistence] Failed to unlink lock file: ${error instanceof Error ? error.message : String(error)}`);
|
|
322
325
|
}
|
|
323
326
|
}
|
|
324
327
|
}
|
|
@@ -3,6 +3,7 @@ import { text } from '../../../locales/index.js';
|
|
|
3
3
|
import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
|
|
4
4
|
import { inferTurnStopReasonFromFailure } from '../../interaction/turn-stop-reason.js';
|
|
5
5
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
6
|
+
import { getLogger } from '../../observability/logger.js';
|
|
6
7
|
import { toAcpPublicModes } from '../../public-capabilities/projections.js';
|
|
7
8
|
import { buildPublicCapabilityRegistry } from '../../public-capabilities/registry.js';
|
|
8
9
|
import { parseSlashInput } from '../../slash/parser.js';
|
|
@@ -641,8 +642,8 @@ export function createAcpFormalAgent(deps) {
|
|
|
641
642
|
try {
|
|
642
643
|
await emitSessionUpdate(sessionId, update);
|
|
643
644
|
}
|
|
644
|
-
catch {
|
|
645
|
-
|
|
645
|
+
catch (error) {
|
|
646
|
+
getLogger().debug(`[AcpFormalAgent] Best-effort session info update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
646
647
|
}
|
|
647
648
|
}
|
|
648
649
|
async function emitRuntimePlanUpdateIfNeeded(params) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { text } from '../../../locales/index.js';
|
|
2
|
+
import { getLogger } from '../../observability/logger.js';
|
|
2
3
|
import { mapToolKind } from './tool-kind-mapping.js';
|
|
3
4
|
function buildPermissionOptions() {
|
|
4
5
|
return [
|
|
@@ -45,8 +46,8 @@ export function createAcpToolAuthorizationProvider(params) {
|
|
|
45
46
|
};
|
|
46
47
|
await params.conn.sessionUpdate({ sessionId: params.sessionId, update });
|
|
47
48
|
}
|
|
48
|
-
catch {
|
|
49
|
-
|
|
49
|
+
catch (error) {
|
|
50
|
+
getLogger().debug(`[AcpPermissionProvider] Best-effort in_progress update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
return {
|