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,6 +1,5 @@
|
|
|
1
1
|
import { createInterface } from 'readline/promises';
|
|
2
|
-
import { getLogger } from '../../core/facades/cli-authorization-provider.js';
|
|
3
|
-
import { TOOL_AUTH_CONFIG } from '../config.js';
|
|
2
|
+
import { DEFAULT_TOOL_AUTH, getLogger } from '../../core/facades/cli-authorization-provider.js';
|
|
4
3
|
import { text } from '../locales/index.js';
|
|
5
4
|
import { getPendingAuthorization, requestAuthorization } from '../ui/authorization/bus.js';
|
|
6
5
|
import { loadAllowlistDecision, persistAllowlistDecision } from './allowlist.js';
|
|
@@ -27,14 +26,7 @@ const shouldAutoAllow = (request, config) => {
|
|
|
27
26
|
const resolveConfig = (config) => {
|
|
28
27
|
if (config)
|
|
29
28
|
return config;
|
|
30
|
-
return
|
|
31
|
-
sessionTtlMs: TOOL_AUTH_CONFIG.SESSION_TTL_MS,
|
|
32
|
-
autoAllowRisk: TOOL_AUTH_CONFIG.AUTO_ALLOW_RISK,
|
|
33
|
-
allowlist: {
|
|
34
|
-
repoFile: '.salmonloop/config/authorization.json',
|
|
35
|
-
userFile: '~/.salmonloop/config/authorization-user.json',
|
|
36
|
-
},
|
|
37
|
-
};
|
|
29
|
+
return DEFAULT_TOOL_AUTH;
|
|
38
30
|
};
|
|
39
31
|
export function createUiAuthorizationProvider(options) {
|
|
40
32
|
const pending = new Map();
|
|
@@ -169,9 +169,9 @@ const permissionModeSubcommand = {
|
|
|
169
169
|
usage: text.cli.configPermissionModeUsage,
|
|
170
170
|
getSuggestions: (ctx) => modeCommand.getSuggestions?.({
|
|
171
171
|
...ctx,
|
|
172
|
-
input: delegateInputToCommand(ctx.input, '/mode'),
|
|
172
|
+
input: delegateInputToCommand(ctx.input, '/permission-mode'),
|
|
173
173
|
}) ?? [],
|
|
174
|
-
execute: async (ctx) => modeCommand.execute({ ...ctx, input: delegateInputToCommand(ctx.input, '/mode') }),
|
|
174
|
+
execute: async (ctx) => modeCommand.execute({ ...ctx, input: delegateInputToCommand(ctx.input, '/permission-mode') }),
|
|
175
175
|
};
|
|
176
176
|
const outputSubcommand = {
|
|
177
177
|
name: 'output',
|
|
@@ -41,8 +41,8 @@ async function persistPermissionMode(repoRoot, mode) {
|
|
|
41
41
|
return configPath;
|
|
42
42
|
}
|
|
43
43
|
export const modeCommand = {
|
|
44
|
-
name: '/mode',
|
|
45
|
-
aliases: ['/
|
|
44
|
+
name: '/permission-mode',
|
|
45
|
+
aliases: ['/mode', '/perm-mode'],
|
|
46
46
|
description: text.cli.commandMode,
|
|
47
47
|
order: 53,
|
|
48
48
|
getSuggestions: ({ input }) => {
|
|
@@ -201,6 +201,7 @@ export async function handleRunCommand(options, command) {
|
|
|
201
201
|
const llmOutput = runtimeOptions.llmOutput;
|
|
202
202
|
const effectiveVerify = runtimeOptions.effectiveVerify;
|
|
203
203
|
const effectiveWorktreePrepare = runtimeOptions.effectiveWorktreePrepare;
|
|
204
|
+
const verifyPolicyOverride = runtimeOptions.verifyPolicyOverride;
|
|
204
205
|
const instructionGuard = ensureInstructionOrExit({
|
|
205
206
|
command,
|
|
206
207
|
instruction,
|
|
@@ -303,7 +304,7 @@ export async function handleRunCommand(options, command) {
|
|
|
303
304
|
}
|
|
304
305
|
const extensionResolution = extensionsResult.extensionResolution;
|
|
305
306
|
const operationalHeadlessWarnings = [];
|
|
306
|
-
if (!effectiveVerify) {
|
|
307
|
+
if (!effectiveVerify && !verifyPolicyOverride) {
|
|
307
308
|
if (!headlessOutput) {
|
|
308
309
|
getLogger().warn(text.verify.noCommandFound);
|
|
309
310
|
}
|
|
@@ -399,6 +400,7 @@ export async function handleRunCommand(options, command) {
|
|
|
399
400
|
const loopParams = buildRunLoopParams({
|
|
400
401
|
instruction: instructionText,
|
|
401
402
|
verify: effectiveVerify,
|
|
403
|
+
verifyPolicy: verifyPolicyOverride,
|
|
402
404
|
repoPath: runPath,
|
|
403
405
|
llm,
|
|
404
406
|
languagePlugins,
|
|
@@ -25,6 +25,8 @@ export async function resolveRunRuntimeOptions(params) {
|
|
|
25
25
|
}
|
|
26
26
|
const effectiveVerify = await resolveVerifyOption(params.repoPath, params.cliOptions.verify, params.resolvedConfig.verify.command, { quiet: params.headlessOutput });
|
|
27
27
|
const effectiveWorktreePrepare = await resolveWorktreePrepareOption(params.repoPath, params.cliOptions.checkpointStrategy, params.cliOptions.worktreePrepare, { quiet: params.headlessOutput });
|
|
28
|
-
|
|
28
|
+
// When --no-verify is explicit, override verifyPolicy to 'never'
|
|
29
|
+
const verifyPolicyOverride = params.cliOptions.verify === false && effectiveVerify === undefined ? 'never' : undefined;
|
|
30
|
+
return { ok: true, llmOutput, effectiveVerify, effectiveWorktreePrepare, verifyPolicyOverride };
|
|
29
31
|
}
|
|
30
32
|
//# sourceMappingURL=runtime-options.js.map
|
package/dist/cli/config.js
CHANGED
|
@@ -5,12 +5,4 @@ export const CHAT_QUEUE_CONFIG = {
|
|
|
5
5
|
THINKING_MIN_VISIBLE_MS: 150, // Reduced from 300ms to minimize perceived lag
|
|
6
6
|
TASK_TIMEOUT_MS: 10 * 60 * 1000,
|
|
7
7
|
};
|
|
8
|
-
export const TOOL_AUTH_CONFIG = {
|
|
9
|
-
SESSION_TTL_MS: 30 * 60 * 1000,
|
|
10
|
-
AUTO_ALLOW_RISK: {
|
|
11
|
-
low: true,
|
|
12
|
-
medium: false,
|
|
13
|
-
high: false,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
8
|
//# sourceMappingURL=config.js.map
|
package/dist/cli/locales/en.js
CHANGED
|
@@ -19,7 +19,7 @@ export const en = {
|
|
|
19
19
|
chatCommandHistory: ' /history - Show iteration history',
|
|
20
20
|
chatCommandQueue: ' /queue - Manage the chat queue',
|
|
21
21
|
chatCommandAuth: ' /config allowlist - Manage tool allowlist',
|
|
22
|
-
chatCommandMode: ' /mode
|
|
22
|
+
chatCommandMode: ' /permission-mode - Set permission mode (interactive/yolo)',
|
|
23
23
|
chatCommandConfig: ' /config - Settings (log-mode/view/output/allowlist/permission-mode)',
|
|
24
24
|
chatSessionSaved: 'Session saved. Goodbye!',
|
|
25
25
|
chatThinking: 'Thinking...',
|
|
@@ -108,7 +108,7 @@ export const en = {
|
|
|
108
108
|
llmOutputUnavailable: 'LLM output configuration is unavailable in this mode.',
|
|
109
109
|
llmOutputPersisted: (path) => `LLM output settings saved to ${path}`,
|
|
110
110
|
llmOutputPersistFailed: (reason) => `Failed to save LLM output settings: ${reason}`,
|
|
111
|
-
modeUsage: 'Usage: /mode <interactive|yolo>',
|
|
111
|
+
modeUsage: 'Usage: /permission-mode <interactive|yolo>',
|
|
112
112
|
modeSuggestion: (mode) => {
|
|
113
113
|
if (mode === 'interactive')
|
|
114
114
|
return 'Interactive permission checks and allowlist rules';
|
|
@@ -110,6 +110,16 @@ export class StandardReporter {
|
|
|
110
110
|
getLogger().info(text.cli.budgetSummaryTitle);
|
|
111
111
|
getLogger().info(text.cli.budgetSummaryLine(s.attemptCount, s.adjustmentCount, s.alertCount, s.criticalDropCount, Math.round(s.avgUtilization * 100), Math.round(s.truncationRate * 100), Math.round(s.successRate * 100)));
|
|
112
112
|
}
|
|
113
|
+
if (result.usage) {
|
|
114
|
+
const u = result.usage;
|
|
115
|
+
const tokenLine = `Tokens: ${u.inputTokens.toLocaleString()} in / ${u.outputTokens.toLocaleString()} out (${u.totalTokens.toLocaleString()} total)`;
|
|
116
|
+
if (u.estimatedCost !== undefined && u.estimatedCost > 0) {
|
|
117
|
+
getLogger().info(`${tokenLine} | Est. cost: $${u.estimatedCost.toFixed(2)}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
getLogger().info(tokenLine);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
113
123
|
if (this.verbose && result.logs) {
|
|
114
124
|
getLogger().log('\n' + chalk.bold(text.cli.stepLogs));
|
|
115
125
|
result.logs.forEach((log) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs, constants } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { AtomicFileWriter } from './atomic-file-writer.js';
|
|
4
5
|
/**
|
|
5
6
|
* Unified file system adapter for all file operations.
|
|
@@ -45,7 +46,8 @@ export class FileAdapter {
|
|
|
45
46
|
await fs.access(filePath);
|
|
46
47
|
return true;
|
|
47
48
|
}
|
|
48
|
-
catch {
|
|
49
|
+
catch (error) {
|
|
50
|
+
getLogger().debug(`[FileAdapter] exists check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
51
|
return false;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
@@ -662,8 +662,9 @@ export class GitAdapter {
|
|
|
662
662
|
// Example: /tmp -> /private/tmp on macOS
|
|
663
663
|
tmpReal = realpathSync(tmpResolved);
|
|
664
664
|
}
|
|
665
|
-
catch {
|
|
665
|
+
catch (error) {
|
|
666
666
|
// Fall back to resolved path. If tmp is not realpath-resolvable, prefer denying shadow checks elsewhere.
|
|
667
|
+
getLogger().debug(`[GitAdapter] realpath resolve failed for tmpdir: ${error instanceof Error ? error.message : String(error)}`);
|
|
667
668
|
tmpReal = tmpResolved;
|
|
668
669
|
}
|
|
669
670
|
return path.join(tmpReal, 's8p-wt');
|
|
@@ -674,7 +675,8 @@ export class GitAdapter {
|
|
|
674
675
|
try {
|
|
675
676
|
repoReal = realpathSync(repoResolved);
|
|
676
677
|
}
|
|
677
|
-
catch {
|
|
678
|
+
catch (error) {
|
|
679
|
+
getLogger().debug(`[GitAdapter] realpath resolve failed for repo: ${error instanceof Error ? error.message : String(error)}`);
|
|
678
680
|
repoReal = repoResolved;
|
|
679
681
|
}
|
|
680
682
|
const parent = path.dirname(repoReal);
|
|
@@ -721,7 +723,8 @@ export class GitAdapter {
|
|
|
721
723
|
// Prevents attacker from creating a symlink to main repo inside shadow root
|
|
722
724
|
repo = realpathSync(repoResolved);
|
|
723
725
|
}
|
|
724
|
-
catch {
|
|
726
|
+
catch (error) {
|
|
727
|
+
getLogger().debug(`[GitAdapter] realpath resolve failed for shadow check: ${error instanceof Error ? error.message : String(error)}`);
|
|
725
728
|
repo = repoResolved;
|
|
726
729
|
}
|
|
727
730
|
if (isPathWithinDirectory(expectedRoot, repo, { allowEqual: false }))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { LIMITS } from '../../config/limits.js';
|
|
4
|
+
import { getLogger } from '../../observability/logger.js';
|
|
4
5
|
import { spawnCommand } from '../../runtime/process-runner.js';
|
|
5
6
|
function assertCwdSandboxed(repoRoot, cwd) {
|
|
6
7
|
const resolvedRoot = path.resolve(repoRoot);
|
|
@@ -10,13 +11,15 @@ function assertCwdSandboxed(repoRoot, cwd) {
|
|
|
10
11
|
try {
|
|
11
12
|
realRoot = realpathSync(resolvedRoot);
|
|
12
13
|
}
|
|
13
|
-
catch {
|
|
14
|
+
catch (error) {
|
|
15
|
+
getLogger().debug(`[GitRunner] realpath resolve failed for root: ${error instanceof Error ? error.message : String(error)}`);
|
|
14
16
|
realRoot = resolvedRoot;
|
|
15
17
|
}
|
|
16
18
|
try {
|
|
17
19
|
realCwd = realpathSync(resolvedCwd);
|
|
18
20
|
}
|
|
19
|
-
catch {
|
|
21
|
+
catch (error) {
|
|
22
|
+
getLogger().debug(`[GitRunner] realpath resolve failed for cwd: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
23
|
realCwd = resolvedCwd;
|
|
21
24
|
}
|
|
22
25
|
const rel = path.relative(realRoot, realCwd);
|
|
@@ -96,8 +96,9 @@ export class FileHandleManager {
|
|
|
96
96
|
const fs = await import('fs/promises');
|
|
97
97
|
await fs.unlink(lockFile);
|
|
98
98
|
}
|
|
99
|
-
catch {
|
|
100
|
-
// Ignore
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Ignore - best-effort force unlock
|
|
101
|
+
getLogger().debug(`[LockManager] force unlock failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
while (Date.now() - start < LIMITS.lockWaitTimeoutMs) {
|
|
@@ -142,9 +143,10 @@ export class FileHandleManager {
|
|
|
142
143
|
continue; // Retry immediately after removing stale lock
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
|
-
catch {
|
|
146
|
+
catch (error) {
|
|
146
147
|
// If the lock file is unreadable, we cannot safely determine ownership.
|
|
147
148
|
// Do not auto-remove in this path; rely on timeout-based recovery instead.
|
|
149
|
+
getLogger().debug(`[LockManager] lock file unreadable during stale check: ${error instanceof Error ? error.message : String(error)}`);
|
|
148
150
|
}
|
|
149
151
|
// Exponential backoff: delay increases with retry count, capped at 2000ms
|
|
150
152
|
retryCount++;
|
|
@@ -159,8 +161,9 @@ export class FileHandleManager {
|
|
|
159
161
|
await mkdir(repoPath, { recursive: true });
|
|
160
162
|
continue; // Retry immediately
|
|
161
163
|
}
|
|
162
|
-
catch {
|
|
164
|
+
catch (error) {
|
|
163
165
|
// If mkdir fails, just wait and retry
|
|
166
|
+
getLogger().debug(`[LockManager] mkdir fallback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
167
|
}
|
|
165
168
|
await this.abortableDelay(LIMITS.retry.io.initialDelayMs, hardAbort.signal);
|
|
166
169
|
}
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'crypto';
|
|
|
2
2
|
import { mkdir, open, readFile, rename, stat, unlink, writeFile } from '../adapters/fs/node-fs.js';
|
|
3
3
|
import { defaultPathAdapter } from '../adapters/path/path-adapter.js';
|
|
4
4
|
import { recordAuditEvent } from '../observability/audit-trail.js';
|
|
5
|
+
import { getLogger } from '../observability/logger.js';
|
|
5
6
|
import { getUserCheckpointManifestDir } from '../runtime/paths.js';
|
|
6
7
|
const CHECKPOINT_MANIFEST_FILENAME_V2 = 'manifest.v2.json';
|
|
7
8
|
const CHECKPOINT_MANIFEST_FILENAME_V1 = 'manifest.v1.json';
|
|
@@ -131,7 +132,8 @@ async function withManifestLock(repoPath, operation, lockPolicy) {
|
|
|
131
132
|
lockPathHash,
|
|
132
133
|
}, { source: 'runtime', severity: 'low', scope: 'session', phase: 'PREFLIGHT' });
|
|
133
134
|
}
|
|
134
|
-
catch {
|
|
135
|
+
catch (error) {
|
|
136
|
+
getLogger().debug(`[ManifestStore] Stale lock reclaim failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
135
137
|
try {
|
|
136
138
|
const lockStat = await stat(lockPath);
|
|
137
139
|
const ageMs = Date.now() - lockStat.mtimeMs;
|
|
@@ -144,8 +146,9 @@ async function withManifestLock(repoPath, operation, lockPolicy) {
|
|
|
144
146
|
}, { source: 'runtime', severity: 'medium', scope: 'session', phase: 'PREFLIGHT' });
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
|
-
catch {
|
|
149
|
+
catch (error) {
|
|
148
150
|
// Ignore lock probe failures; retry loop handles contention.
|
|
151
|
+
getLogger().debug(`[ManifestStore] Lock probe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
154
|
};
|
|
@@ -156,7 +159,8 @@ async function withManifestLock(repoPath, operation, lockPolicy) {
|
|
|
156
159
|
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAtMs: Date.now() }), 'utf8');
|
|
157
160
|
break;
|
|
158
161
|
}
|
|
159
|
-
catch {
|
|
162
|
+
catch (error) {
|
|
163
|
+
getLogger().debug(`[ManifestStore] Lock acquisition attempt failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
160
164
|
await tryClearStaleLock();
|
|
161
165
|
await new Promise((resolve) => setTimeout(resolve, 30 * (attempt + 1)));
|
|
162
166
|
}
|
|
@@ -185,14 +189,14 @@ async function withManifestLock(repoPath, operation, lockPolicy) {
|
|
|
185
189
|
try {
|
|
186
190
|
await handle.close();
|
|
187
191
|
}
|
|
188
|
-
catch {
|
|
189
|
-
|
|
192
|
+
catch (error) {
|
|
193
|
+
getLogger().debug(`[ManifestStore] Failed to close lock handle: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
194
|
}
|
|
191
195
|
try {
|
|
192
196
|
await unlink(lockPath);
|
|
193
197
|
}
|
|
194
|
-
catch {
|
|
195
|
-
|
|
198
|
+
catch (error) {
|
|
199
|
+
getLogger().debug(`[ManifestStore] Failed to unlink lock file: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
200
|
}
|
|
197
201
|
}
|
|
198
202
|
}
|
|
@@ -204,7 +208,8 @@ export async function readCheckpointManifest(repoPath) {
|
|
|
204
208
|
try {
|
|
205
209
|
return normalizeManifest(JSON.parse(raw));
|
|
206
210
|
}
|
|
207
|
-
catch {
|
|
211
|
+
catch (error) {
|
|
212
|
+
getLogger().warn(`[ManifestStore] v2 manifest parse error, triggering self-heal: ${error instanceof Error ? error.message : String(error)}`);
|
|
208
213
|
await selfHealCorruptedManifest({
|
|
209
214
|
repoPath,
|
|
210
215
|
manifestPath: manifestV2Path,
|
|
@@ -214,15 +219,16 @@ export async function readCheckpointManifest(repoPath) {
|
|
|
214
219
|
});
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
|
-
catch {
|
|
218
|
-
|
|
222
|
+
catch (error) {
|
|
223
|
+
getLogger().debug(`[ManifestStore] v2 manifest read failed, falling back to v1: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
224
|
}
|
|
220
225
|
try {
|
|
221
226
|
const raw = await readFile(manifestV1Path, 'utf8');
|
|
222
227
|
try {
|
|
223
228
|
return normalizeManifest(JSON.parse(raw));
|
|
224
229
|
}
|
|
225
|
-
catch {
|
|
230
|
+
catch (error) {
|
|
231
|
+
getLogger().warn(`[ManifestStore] v1 manifest parse error, triggering self-heal: ${error instanceof Error ? error.message : String(error)}`);
|
|
226
232
|
await selfHealCorruptedManifest({
|
|
227
233
|
repoPath,
|
|
228
234
|
manifestPath: manifestV1Path,
|
|
@@ -233,7 +239,8 @@ export async function readCheckpointManifest(repoPath) {
|
|
|
233
239
|
return createEmptyManifest();
|
|
234
240
|
}
|
|
235
241
|
}
|
|
236
|
-
catch {
|
|
242
|
+
catch (error) {
|
|
243
|
+
getLogger().debug(`[ManifestStore] v1 manifest read failed, returning empty manifest: ${error instanceof Error ? error.message : String(error)}`);
|
|
237
244
|
return createEmptyManifest();
|
|
238
245
|
}
|
|
239
246
|
return createEmptyManifest();
|
|
@@ -333,7 +340,8 @@ export async function probeCheckpointHandle(repoPath, checkpointId) {
|
|
|
333
340
|
return { handle: null, reason: 'not_found' };
|
|
334
341
|
return { handle, reason: 'ok' };
|
|
335
342
|
}
|
|
336
|
-
catch {
|
|
343
|
+
catch (error) {
|
|
344
|
+
getLogger().debug(`[ManifestStore] Manifest parse failed during probe: ${error instanceof Error ? error.message : String(error)}`);
|
|
337
345
|
return { handle: null, reason: 'manifest_parse_error' };
|
|
338
346
|
}
|
|
339
347
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../observability/logger.js';
|
|
1
2
|
import { CheckpointManager } from '../strata/checkpoint/manager.js';
|
|
2
3
|
import { garbageCollectManifest, linkSessionToCheckpoint, probeCheckpointHandle, readCheckpointManifest, removeCheckpointHandle, upsertCheckpointHandle, } from './manifest-store.js';
|
|
3
4
|
export class GitSnapshotCheckpointService {
|
|
@@ -74,8 +75,9 @@ export class GitSnapshotCheckpointService {
|
|
|
74
75
|
await this.checkpointManager.deleteSnapshot(input.repoPath, checkpointId);
|
|
75
76
|
refsRemoved += 1;
|
|
76
77
|
}
|
|
77
|
-
catch {
|
|
78
|
+
catch (error) {
|
|
78
79
|
// Best-effort ref reconciliation; manifest remains source of truth.
|
|
80
|
+
getLogger().debug(`[CheckpointService] snapshot ref cleanup failed for ${checkpointId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
return { removed: manifestGc.removed, refsRemoved };
|
|
@@ -2,7 +2,7 @@ export const LIMITS = {
|
|
|
2
2
|
// Patch safety
|
|
3
3
|
maxFilesChanged: 2,
|
|
4
4
|
maxDiffLines: 200,
|
|
5
|
-
maxRetries:
|
|
5
|
+
maxRetries: 5,
|
|
6
6
|
// Context budget (token-based, with char fallback)
|
|
7
7
|
maxContextTokens: 7500, // ~30k chars equivalent
|
|
8
8
|
minContextTokens: 1250, // ~5k chars equivalent
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-model token pricing (USD per 1M tokens).
|
|
3
|
+
*
|
|
4
|
+
* Used to estimate run cost from token counts.
|
|
5
|
+
* Prices are approximate and should be updated when providers change rates.
|
|
6
|
+
*/
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
export const MODEL_PRICING = {
|
|
9
|
+
// Anthropic Claude
|
|
10
|
+
'claude-opus-4-7': { inputPer1M: 15, outputPer1M: 75 },
|
|
11
|
+
'claude-opus-4-6': { inputPer1M: 15, outputPer1M: 75 },
|
|
12
|
+
'claude-sonnet-4-6': { inputPer1M: 3, outputPer1M: 15 },
|
|
13
|
+
'claude-sonnet-4-5': { inputPer1M: 3, outputPer1M: 15 },
|
|
14
|
+
'claude-haiku-4-5': { inputPer1M: 0.80, outputPer1M: 4 },
|
|
15
|
+
'claude-haiku-4-5-20251001': { inputPer1M: 0.80, outputPer1M: 4 },
|
|
16
|
+
'claude-3-opus': { inputPer1M: 15, outputPer1M: 75 },
|
|
17
|
+
'claude-3-sonnet': { inputPer1M: 3, outputPer1M: 15 },
|
|
18
|
+
'claude-3-haiku': { inputPer1M: 0.25, outputPer1M: 1.25 },
|
|
19
|
+
// OpenAI
|
|
20
|
+
'gpt-4o': { inputPer1M: 2.50, outputPer1M: 10 },
|
|
21
|
+
'gpt-4o-mini': { inputPer1M: 0.15, outputPer1M: 0.60 },
|
|
22
|
+
'gpt-4-turbo': { inputPer1M: 10, outputPer1M: 30 },
|
|
23
|
+
'gpt-4': { inputPer1M: 30, outputPer1M: 60 },
|
|
24
|
+
'o1': { inputPer1M: 15, outputPer1M: 60 },
|
|
25
|
+
'o1-mini': { inputPer1M: 3, outputPer1M: 12 },
|
|
26
|
+
'o3-mini': { inputPer1M: 1.10, outputPer1M: 4.40 },
|
|
27
|
+
// Google Gemini
|
|
28
|
+
'gemini-2.5-pro': { inputPer1M: 1.25, outputPer1M: 10 },
|
|
29
|
+
'gemini-2.5-flash': { inputPer1M: 0.15, outputPer1M: 0.60 },
|
|
30
|
+
'gemini-2.0-flash': { inputPer1M: 0.10, outputPer1M: 0.40 },
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Look up pricing for a model ID.
|
|
34
|
+
* Tries exact match first, then prefix match (e.g. "claude-sonnet-4-6-20250514" matches "claude-sonnet-4-6").
|
|
35
|
+
* Returns undefined if no pricing is available.
|
|
36
|
+
*/
|
|
37
|
+
export function getModelPricing(modelId) {
|
|
38
|
+
if (!modelId)
|
|
39
|
+
return undefined;
|
|
40
|
+
// Exact match
|
|
41
|
+
if (MODEL_PRICING[modelId])
|
|
42
|
+
return MODEL_PRICING[modelId];
|
|
43
|
+
// Prefix match — try longest prefix first
|
|
44
|
+
const normalized = modelId.toLowerCase();
|
|
45
|
+
for (const key of Object.keys(MODEL_PRICING)) {
|
|
46
|
+
if (normalized.startsWith(key))
|
|
47
|
+
return MODEL_PRICING[key];
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Estimate cost in USD from token counts and model ID.
|
|
53
|
+
*/
|
|
54
|
+
export function estimateCost(inputTokens, outputTokens, modelId) {
|
|
55
|
+
const pricing = getModelPricing(modelId);
|
|
56
|
+
if (!pricing)
|
|
57
|
+
return undefined;
|
|
58
|
+
return ((inputTokens / 1_000_000) * pricing.inputPer1M +
|
|
59
|
+
(outputTokens / 1_000_000) * pricing.outputPer1M);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=model-pricing.js.map
|