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.
Files changed (146) hide show
  1. package/dist/cli/authorization/provider.js +2 -10
  2. package/dist/cli/commands/config.js +2 -2
  3. package/dist/cli/commands/mode.js +2 -2
  4. package/dist/cli/commands/run/handler.js +3 -1
  5. package/dist/cli/commands/run/loop-params.js +1 -0
  6. package/dist/cli/commands/run/runtime-options.js +3 -1
  7. package/dist/cli/config.js +0 -8
  8. package/dist/cli/locales/en.js +2 -2
  9. package/dist/cli/reporters/standard.js +10 -0
  10. package/dist/core/adapters/fs/file-adapter.js +3 -1
  11. package/dist/core/adapters/git/git-adapter.js +6 -3
  12. package/dist/core/adapters/git/git-runner.js +5 -2
  13. package/dist/core/adapters/git/lock-manager.js +7 -4
  14. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  15. package/dist/core/checkpoint-domain/service.js +3 -1
  16. package/dist/core/config/limits.js +1 -1
  17. package/dist/core/config/model-pricing.js +61 -0
  18. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  19. package/dist/core/context/ast/source-outline.js +24 -1
  20. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  21. package/dist/core/context/builder.js +7 -3
  22. package/dist/core/context/cache/store-factory.js +3 -1
  23. package/dist/core/context/dependencies.js +2 -1
  24. package/dist/core/context/effectiveness/persistence.js +50 -0
  25. package/dist/core/context/effectiveness/tracker.js +24 -0
  26. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  27. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  28. package/dist/core/context/gatherers/ast-gatherer.js +30 -28
  29. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  30. package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
  31. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  32. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  33. package/dist/core/context/service.js +4 -2
  34. package/dist/core/context/steps/context-gather.js +14 -3
  35. package/dist/core/context/steps/context-targets.js +1 -0
  36. package/dist/core/context/targeting/target-resolver.js +29 -11
  37. package/dist/core/context/token/cache.js +5 -2
  38. package/dist/core/context/truncation/strategies/json.js +5 -2
  39. package/dist/core/context/truncation/type-detector.js +3 -1
  40. package/dist/core/extensions/paths.js +2 -2
  41. package/dist/core/facades/cli-authorization-provider.js +1 -0
  42. package/dist/core/feedback/parsers.js +290 -1
  43. package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
  44. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  45. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
  46. package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
  47. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  48. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  49. package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
  50. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  51. package/dist/core/grizzco/steps/autopilot.js +21 -32
  52. package/dist/core/grizzco/steps/explore.js +5 -2
  53. package/dist/core/grizzco/steps/generateReview.js +3 -1
  54. package/dist/core/grizzco/steps/research.js +3 -1
  55. package/dist/core/grizzco/steps/verify.js +7 -1
  56. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  57. package/dist/core/history/input-history.js +3 -1
  58. package/dist/core/intent/chat-intent.js +3 -1
  59. package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
  60. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  61. package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
  62. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  63. package/dist/core/llm/errors.js +5 -4
  64. package/dist/core/llm/retry-utils.js +8 -2
  65. package/dist/core/llm/stream-utils.js +5 -3
  66. package/dist/core/llm/sub-agent-factory.js +3 -0
  67. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  68. package/dist/core/mcp/catalog/discovery.js +3 -1
  69. package/dist/core/mcp/client/connection-manager.js +4 -2
  70. package/dist/core/mcp/client/transport-factory.js +7 -3
  71. package/dist/core/observability/audit-file.js +2 -1
  72. package/dist/core/observability/audit-trail.js +3 -1
  73. package/dist/core/observability/logger.js +2 -1
  74. package/dist/core/observability/monitor.js +24 -0
  75. package/dist/core/observability/run-outcome-reporter.js +1 -0
  76. package/dist/core/permission-gate/default-gate.js +5 -8
  77. package/dist/core/plan/storage.js +7 -4
  78. package/dist/core/plugin/loader.js +3 -1
  79. package/dist/core/prompts/registry.js +1 -1
  80. package/dist/core/prompts/runtime.js +3 -1
  81. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  82. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  83. package/dist/core/protocols/a2a/sdk/server.js +3 -1
  84. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  85. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  86. package/dist/core/protocols/acp/formal-agent.js +3 -2
  87. package/dist/core/protocols/acp/permission-provider.js +3 -2
  88. package/dist/core/reflection/engine.js +114 -14
  89. package/dist/core/runtime/batch-runner.js +81 -0
  90. package/dist/core/runtime/initialize.js +2 -1
  91. package/dist/core/runtime/loop-finalize.js +3 -0
  92. package/dist/core/runtime/loop-session-runner.js +5 -0
  93. package/dist/core/runtime/loop.js +4 -0
  94. package/dist/core/runtime/paths.js +9 -6
  95. package/dist/core/runtime/spawn-interactive.js +5 -4
  96. package/dist/core/security/redaction.js +3 -2
  97. package/dist/core/session/compression.js +3 -1
  98. package/dist/core/session/manager.js +2 -1
  99. package/dist/core/session/pruning-strategy.js +2 -1
  100. package/dist/core/session/token-tracker.js +11 -4
  101. package/dist/core/skills/permissions.js +2 -2
  102. package/dist/core/strata/checkpoint/manager.js +16 -10
  103. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  104. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  105. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  106. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  107. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  108. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  109. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  110. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  111. package/dist/core/strata/layers/worktree.js +2 -1
  112. package/dist/core/strata/runtime/environment.js +2 -1
  113. package/dist/core/strata/runtime/synchronizer.js +18 -17
  114. package/dist/core/structured-output/json-extract.js +3 -1
  115. package/dist/core/sub-agent/artifacts/store.js +2 -1
  116. package/dist/core/sub-agent/core/manager.js +24 -1
  117. package/dist/core/sub-agent/registry-defaults.js +2 -2
  118. package/dist/core/sub-agent/summary.js +96 -0
  119. package/dist/core/sub-agent/tools/task-spawn.js +7 -4
  120. package/dist/core/target-runtime/profile.js +3 -1
  121. package/dist/core/tools/audit.js +3 -2
  122. package/dist/core/tools/budget.js +3 -1
  123. package/dist/core/tools/builtin/ast.js +144 -0
  124. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  125. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  126. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  127. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  128. package/dist/core/tools/builtin/fs.js +76 -1
  129. package/dist/core/tools/builtin/git.js +242 -0
  130. package/dist/core/tools/builtin/glob.js +79 -0
  131. package/dist/core/tools/builtin/index.js +12 -4
  132. package/dist/core/tools/builtin/knowledge.js +146 -4
  133. package/dist/core/tools/builtin/proposal.js +3 -1
  134. package/dist/core/tools/builtin/verify.js +35 -3
  135. package/dist/core/tools/permissions/permission-rules.js +3 -1
  136. package/dist/core/tools/router.js +88 -5
  137. package/dist/core/tools/session.js +10 -5
  138. package/dist/core/types/batch.js +2 -0
  139. package/dist/core/utils/sanitizer.js +5 -2
  140. package/dist/core/utils/serialize.js +5 -2
  141. package/dist/core/verification/detect-runner.js +86 -0
  142. package/dist/core/verification/runner.js +76 -0
  143. package/dist/core/version.js +3 -1
  144. package/dist/languages/python/index.js +154 -0
  145. package/dist/locales/en.js +6 -0
  146. 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: ['/permission-mode', '/perm-mode'],
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,
@@ -3,6 +3,7 @@ export function buildRunLoopParams(params) {
3
3
  return {
4
4
  instruction: params.instruction,
5
5
  verify: params.verify,
6
+ verifyPolicy: params.verifyPolicy,
6
7
  repoPath: params.repoPath,
7
8
  llm: params.llm,
8
9
  conversationContext: params.conversationContext,
@@ -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
- return { ok: true, llmOutput, effectiveVerify, effectiveWorktreePrepare };
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
@@ -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
@@ -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 - Set permission mode (interactive/yolo)',
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
- // ignore
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
- // ignore
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
- // fallback to legacy v1 file
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: 2,
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