salmon-loop 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +12 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +5 -4
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +90 -7
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -1,10 +1,86 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { writeFile, mkdir } from '../../adapters/fs/node-fs.js';
|
|
2
|
+
import { readdir, readFile, writeFile, mkdir } from '../../adapters/fs/node-fs.js';
|
|
3
3
|
import { getDefaultIndexPath } from '../../config/paths.js';
|
|
4
|
+
import { getLogger, tryGetLogger } from '../../observability/logger.js';
|
|
4
5
|
import { Phase } from '../../types/runtime.js';
|
|
5
6
|
import { safeJoin } from '../../utils/path.js';
|
|
6
7
|
let lastEventTimestampMs = 0;
|
|
7
8
|
let eventSequence = 0;
|
|
9
|
+
// ── Knowledge quality gates ──────────────────────────────────────────────────
|
|
10
|
+
const MIN_RULE_LENGTH = 10;
|
|
11
|
+
const MAX_RULE_LENGTH = 500;
|
|
12
|
+
function isValidContent(text) {
|
|
13
|
+
const trimmed = text.trim();
|
|
14
|
+
return (trimmed.length >= MIN_RULE_LENGTH && trimmed.length <= MAX_RULE_LENGTH && /[\w]/.test(trimmed));
|
|
15
|
+
}
|
|
16
|
+
/** Simple Levenshtein distance for short strings. */
|
|
17
|
+
function levenshtein(a, b) {
|
|
18
|
+
if (a === b)
|
|
19
|
+
return 0;
|
|
20
|
+
if (a.length === 0)
|
|
21
|
+
return b.length;
|
|
22
|
+
if (b.length === 0)
|
|
23
|
+
return a.length;
|
|
24
|
+
const matrix = [];
|
|
25
|
+
for (let i = 0; i <= b.length; i++)
|
|
26
|
+
matrix[i] = [i];
|
|
27
|
+
for (let j = 0; j <= a.length; j++)
|
|
28
|
+
matrix[0][j] = j;
|
|
29
|
+
for (let i = 1; i <= b.length; i++) {
|
|
30
|
+
for (let j = 1; j <= a.length; j++) {
|
|
31
|
+
const cost = b[i - 1] === a[j - 1] ? 0 : 1;
|
|
32
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return matrix[b.length][a.length];
|
|
36
|
+
}
|
|
37
|
+
/** Check if a rule is too similar to any existing rule. */
|
|
38
|
+
function isDuplicateRule(newRule, existingRules) {
|
|
39
|
+
const normalized = newRule.trim().toLowerCase();
|
|
40
|
+
for (const existing of existingRules) {
|
|
41
|
+
const existingNorm = existing.trim().toLowerCase();
|
|
42
|
+
if (normalized === existingNorm)
|
|
43
|
+
return true;
|
|
44
|
+
if (levenshtein(normalized, existingNorm) < 5)
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/** Load existing knowledge to check for duplicates. */
|
|
50
|
+
async function loadExistingKnowledge(knowledgeDir) {
|
|
51
|
+
const rules = [];
|
|
52
|
+
const decisions = [];
|
|
53
|
+
const deprecatedRules = [];
|
|
54
|
+
try {
|
|
55
|
+
const files = await readdir(knowledgeDir);
|
|
56
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json')).sort();
|
|
57
|
+
for (const file of jsonFiles) {
|
|
58
|
+
try {
|
|
59
|
+
const content = await readFile(safeJoin(knowledgeDir, file), 'utf-8');
|
|
60
|
+
const data = JSON.parse(content);
|
|
61
|
+
if (Array.isArray(data.project_rules))
|
|
62
|
+
rules.push(...data.project_rules);
|
|
63
|
+
if (Array.isArray(data.deprecated_rules))
|
|
64
|
+
deprecatedRules.push(...data.deprecated_rules);
|
|
65
|
+
if (Array.isArray(data.architectural_decisions)) {
|
|
66
|
+
for (const d of data.architectural_decisions) {
|
|
67
|
+
if (typeof d.decision === 'string')
|
|
68
|
+
decisions.push(d.decision);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
/* skip corrupted */
|
|
74
|
+
getLogger().debug(`[Knowledge] Failed to read knowledge file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
/* dir missing */
|
|
80
|
+
getLogger().debug(`[Knowledge] Failed to read knowledge directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
}
|
|
82
|
+
return { rules, decisions, deprecatedRules };
|
|
83
|
+
}
|
|
8
84
|
function nextEventFilePrefix() {
|
|
9
85
|
const nowMs = Date.now();
|
|
10
86
|
if (nowMs === lastEventTimestampMs) {
|
|
@@ -36,6 +112,14 @@ const updateKnowledgeInputSchema = z.discriminatedUnion('category', [
|
|
|
36
112
|
category: z.literal('user_preferences'),
|
|
37
113
|
preferences: z.string().describe('Updated description of user personal preferences'),
|
|
38
114
|
}),
|
|
115
|
+
z.object({
|
|
116
|
+
category: z.literal('lessons_learned'),
|
|
117
|
+
lessons: z.array(z.string()).describe('Lessons learned from execution outcomes'),
|
|
118
|
+
source: z
|
|
119
|
+
.enum(['success', 'failure'])
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('Whether lessons came from success or failure'),
|
|
122
|
+
}),
|
|
39
123
|
]);
|
|
40
124
|
export const updateKnowledgeSpec = {
|
|
41
125
|
name: 'update_knowledge',
|
|
@@ -56,8 +140,60 @@ export async function executeUpdateKnowledge(input, ctx) {
|
|
|
56
140
|
const { repoRoot } = ctx;
|
|
57
141
|
const indexPath = getDefaultIndexPath(repoRoot);
|
|
58
142
|
const knowledgeDir = safeJoin(indexPath, 'knowledge');
|
|
59
|
-
|
|
60
|
-
|
|
143
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
144
|
+
const existing = await loadExistingKnowledge(knowledgeDir);
|
|
145
|
+
// ── Quality gates ────────────────────────────────────────────────────────
|
|
146
|
+
if (input.category === 'project_rules') {
|
|
147
|
+
// Filter out rules that are invalid or duplicate
|
|
148
|
+
const validRules = [];
|
|
149
|
+
let skipped = 0;
|
|
150
|
+
for (const rule of input.rules) {
|
|
151
|
+
if (!isValidContent(rule)) {
|
|
152
|
+
skipped++;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (isDuplicateRule(rule, existing.rules)) {
|
|
156
|
+
skipped++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (isDuplicateRule(rule, existing.deprecatedRules)) {
|
|
160
|
+
skipped++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
validRules.push(rule);
|
|
164
|
+
}
|
|
165
|
+
if (skipped > 0) {
|
|
166
|
+
tryGetLogger()?.debug(`[Knowledge] Filtered ${skipped} invalid/duplicate rules`);
|
|
167
|
+
}
|
|
168
|
+
// If all rules were filtered, skip the write entirely
|
|
169
|
+
if (validRules.length === 0 &&
|
|
170
|
+
(!input.deprecated_rules || input.deprecated_rules.length === 0)) {
|
|
171
|
+
return { success: true, message: 'All rules were duplicates or invalid, nothing to record' };
|
|
172
|
+
}
|
|
173
|
+
// Rewrite input with filtered rules
|
|
174
|
+
input.rules = validRules;
|
|
175
|
+
}
|
|
176
|
+
if (input.category === 'architectural_decisions') {
|
|
177
|
+
if (!isValidContent(input.decision)) {
|
|
178
|
+
return { success: true, message: 'Decision too short or invalid, nothing to record' };
|
|
179
|
+
}
|
|
180
|
+
if (isDuplicateRule(input.decision, existing.decisions)) {
|
|
181
|
+
return { success: true, message: 'Decision already recorded, skipping duplicate' };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (input.category === 'user_preferences') {
|
|
185
|
+
if (!isValidContent(input.preferences)) {
|
|
186
|
+
return { success: true, message: 'Preferences too short or invalid, nothing to record' };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (input.category === 'lessons_learned') {
|
|
190
|
+
const validLessons = input.lessons.filter((l) => isValidContent(l));
|
|
191
|
+
if (validLessons.length === 0) {
|
|
192
|
+
return { success: true, message: 'All lessons were invalid, nothing to record' };
|
|
193
|
+
}
|
|
194
|
+
input.lessons = validLessons;
|
|
195
|
+
}
|
|
196
|
+
// ── Write ────────────────────────────────────────────────────────────────
|
|
61
197
|
const fileName = `${nextEventFilePrefix()}-${input.category}.json`;
|
|
62
198
|
const filePath = safeJoin(knowledgeDir, fileName);
|
|
63
199
|
let dataToSave = {};
|
|
@@ -82,9 +218,15 @@ export async function executeUpdateKnowledge(input, ctx) {
|
|
|
82
218
|
case 'user_preferences':
|
|
83
219
|
dataToSave = { user_preferences: input.preferences };
|
|
84
220
|
break;
|
|
221
|
+
case 'lessons_learned':
|
|
222
|
+
dataToSave = {
|
|
223
|
+
lessons_learned: input.lessons,
|
|
224
|
+
source: input.source ?? 'unknown',
|
|
225
|
+
date: new Date().toISOString().split('T')[0],
|
|
226
|
+
};
|
|
227
|
+
break;
|
|
85
228
|
}
|
|
86
229
|
try {
|
|
87
|
-
await mkdir(knowledgeDir, { recursive: true });
|
|
88
230
|
await writeFile(filePath, JSON.stringify(dataToSave, null, 2));
|
|
89
231
|
return {
|
|
90
232
|
success: true,
|
|
@@ -9,11 +9,13 @@ import { Executor } from '../../grizzco/execution/Executor.js';
|
|
|
9
9
|
import { WorkerFactory } from '../../grizzco/execution/WorkerFactory.js';
|
|
10
10
|
import { MockLockService } from '../../grizzco/services/implementations/mock/MockLockService.js';
|
|
11
11
|
import { registry } from '../../grizzco/services/registry.js';
|
|
12
|
+
import { getLogger } from '../../observability/logger.js';
|
|
12
13
|
import { normalizeDiff, validateDiff, convertDiffToShadowOperations } from '../../patch/diff.js';
|
|
13
14
|
import { getRejectionsDir } from '../../runtime/paths.js';
|
|
14
15
|
import { FileStateResolver } from '../../strata/layers/file-state-resolver.js';
|
|
15
16
|
import { ArtifactStore } from '../../sub-agent/artifacts/store.js';
|
|
16
17
|
import { Phase } from '../../types/runtime.js';
|
|
18
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
17
19
|
function bootstrapRegistry() {
|
|
18
20
|
if (!registry.has('remote_lock'))
|
|
19
21
|
registry.register(new MockLockService());
|
|
@@ -48,7 +50,7 @@ export const proposalApplySpec = {
|
|
|
48
50
|
// challenge-response authorization without violating the execution contract.
|
|
49
51
|
allowedPhases: [Phase.VERIFY],
|
|
50
52
|
summarizeArgsForAuthorization: async (args, _ctx) => {
|
|
51
|
-
const handle = args
|
|
53
|
+
const handle = isRecord(args) && typeof args.handle === 'string' ? args.handle : undefined;
|
|
52
54
|
if (!handle)
|
|
53
55
|
return undefined;
|
|
54
56
|
const read = await ArtifactStore.readText(handle);
|
|
@@ -66,7 +68,8 @@ export const proposalApplySpec = {
|
|
|
66
68
|
changedFilesTruncated: meta.changedFiles.length > changedFiles.length,
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
|
-
catch {
|
|
71
|
+
catch (error) {
|
|
72
|
+
getLogger().warn(`[Proposal] Failed to validate diff for authorization preview: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
73
|
return JSON.stringify({ handle, preview: 'invalid_diff' });
|
|
71
74
|
}
|
|
72
75
|
},
|
|
@@ -99,7 +102,15 @@ export async function executeProposalApply(input, ctx) {
|
|
|
99
102
|
for (const op of operations) {
|
|
100
103
|
const fileState = stateMap.get(op.path);
|
|
101
104
|
const fileInfo = fileState
|
|
102
|
-
? {
|
|
105
|
+
? {
|
|
106
|
+
path: fileState.path,
|
|
107
|
+
status: fileState.status,
|
|
108
|
+
isBinary: fileState.isBinary,
|
|
109
|
+
isSymlink: fileState.isSymlink,
|
|
110
|
+
isIgnored: fileState.isIgnored,
|
|
111
|
+
hasConflict: fileState.status === FileStatus.CONFLICT,
|
|
112
|
+
size: fileState.size,
|
|
113
|
+
}
|
|
103
114
|
: {
|
|
104
115
|
path: op.path,
|
|
105
116
|
status: FileStatus.CLEAN,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { text } from '../../../locales/index.js';
|
|
3
|
+
import { parseRunnerOutput, parseStructuredSummary } from '../../feedback/parsers.js';
|
|
3
4
|
import { Phase } from '../../types/runtime.js';
|
|
4
|
-
import {
|
|
5
|
+
import { detectRunner, injectJsonFlags, } from '../../verification/detect-runner.js';
|
|
6
|
+
import { runVerify, classifyError, isRetryable as checkRetryable, parseTestSummary, } from '../../verification/runner.js';
|
|
5
7
|
import { processResource, repoResource } from '../parallel/resource-helpers.js';
|
|
6
8
|
export const verifyRunSpec = {
|
|
7
9
|
name: 'test.run',
|
|
@@ -14,6 +16,10 @@ export const verifyRunSpec = {
|
|
|
14
16
|
computeResources: (_input, ctx) => [repoResource(ctx), processResource(ctx)],
|
|
15
17
|
inputSchema: z.object({
|
|
16
18
|
command: z.string().describe('The shell command to run for verification'),
|
|
19
|
+
runner: z
|
|
20
|
+
.enum(['jest', 'vitest', 'pytest', 'tsc', 'eslint', 'bun', 'go'])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Test runner type. Auto-detected from command if omitted.'),
|
|
17
23
|
}),
|
|
18
24
|
outputSchema: z.object({
|
|
19
25
|
ok: z.boolean(),
|
|
@@ -21,6 +27,24 @@ export const verifyRunSpec = {
|
|
|
21
27
|
exitCode: z.number().nullable(),
|
|
22
28
|
errorType: z.string().optional(),
|
|
23
29
|
isRetryable: z.boolean().optional(),
|
|
30
|
+
diagnostics: z
|
|
31
|
+
.array(z.object({
|
|
32
|
+
file: z.string(),
|
|
33
|
+
line: z.number().optional(),
|
|
34
|
+
column: z.number().optional(),
|
|
35
|
+
severity: z.enum(['error', 'warning']),
|
|
36
|
+
message: z.string(),
|
|
37
|
+
source: z.string(),
|
|
38
|
+
}))
|
|
39
|
+
.optional(),
|
|
40
|
+
summary: z
|
|
41
|
+
.object({
|
|
42
|
+
total: z.number(),
|
|
43
|
+
passed: z.number(),
|
|
44
|
+
failed: z.number(),
|
|
45
|
+
skipped: z.number(),
|
|
46
|
+
})
|
|
47
|
+
.optional(),
|
|
24
48
|
}),
|
|
25
49
|
allowedPhases: [Phase.VERIFY],
|
|
26
50
|
};
|
|
@@ -29,13 +53,21 @@ export const verifyRunSpec = {
|
|
|
29
53
|
*/
|
|
30
54
|
export async function executeVerifyRun(input, ctx) {
|
|
31
55
|
const { command } = input;
|
|
56
|
+
const runner = input.runner ?? detectRunner(command);
|
|
57
|
+
const effectiveCommand = injectJsonFlags(command, runner);
|
|
32
58
|
const activePath = ctx.worktreeRoot || ctx.repoRoot;
|
|
33
|
-
const result = await runVerify(activePath,
|
|
59
|
+
const result = await runVerify(activePath, effectiveCommand, ctx.env, ctx.signal);
|
|
34
60
|
const errorType = !result.ok ? classifyError(result.output) : undefined;
|
|
61
|
+
// Structured parsing when we know the runner; generic text heuristics otherwise
|
|
62
|
+
const diagnostics = !result.ok ? parseRunnerOutput(result.output, runner) : [];
|
|
63
|
+
// Prefer structured JSON summary; fall back to regex-based text parser
|
|
64
|
+
const summary = parseStructuredSummary(result.output, runner) ?? parseTestSummary(result.output);
|
|
35
65
|
return {
|
|
36
66
|
...result,
|
|
37
67
|
errorType,
|
|
38
|
-
isRetryable:
|
|
68
|
+
isRetryable: errorType ? checkRetryable(errorType) : false,
|
|
69
|
+
diagnostics: diagnostics.length > 0 ? diagnostics : undefined,
|
|
70
|
+
summary,
|
|
39
71
|
};
|
|
40
72
|
}
|
|
41
73
|
//# sourceMappingURL=verify.js.map
|
|
@@ -75,10 +75,10 @@ export async function runWithFallback(backends, input, ctx, opts) {
|
|
|
75
75
|
throw new Error(`All backends failed for capability. Tried: ${JSON.stringify(meta.tried)}`);
|
|
76
76
|
}
|
|
77
77
|
function createBackendError(backendId, fail, meta) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
return Object.assign(new Error(`Backend ${backendId} failed: [${fail.code}] ${fail.message}`), {
|
|
79
|
+
backendId,
|
|
80
|
+
failCode: fail.code,
|
|
81
|
+
meta,
|
|
82
|
+
});
|
|
83
83
|
}
|
|
84
84
|
//# sourceMappingURL=executor.js.map
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { redactValue } from '../llm/redact.js';
|
|
2
|
-
|
|
3
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
-
}
|
|
2
|
+
import { isRecord } from '../utils/serialize.js';
|
|
5
3
|
function limitValue(value, params) {
|
|
6
4
|
if (params.depth >= params.maxDepth)
|
|
7
5
|
return '[Truncated]';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { unwrapZodSchema } from '../utils/zod.js';
|
|
2
3
|
function formatToolExamplesForDescription(spec) {
|
|
3
4
|
if (!Array.isArray(spec.examples) || spec.examples.length === 0)
|
|
4
5
|
return '';
|
|
@@ -13,37 +14,8 @@ function formatToolExamplesForDescription(spec) {
|
|
|
13
14
|
function toolDescriptionForModel(spec) {
|
|
14
15
|
return `${spec.description}${formatToolExamplesForDescription(spec)}`;
|
|
15
16
|
}
|
|
16
|
-
function unwrapForSchemaGeneration(schema) {
|
|
17
|
-
let current = schema;
|
|
18
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
19
|
-
const ZodEffects = z.ZodEffects;
|
|
20
|
-
if (typeof ZodEffects === 'function' && current instanceof ZodEffects) {
|
|
21
|
-
current = current._def.schema;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (current instanceof z.ZodPipe) {
|
|
25
|
-
// z.preprocess in Zod v4 produces a ZodPipe(in=ZodTransform, out=<schema>).
|
|
26
|
-
current = current._def.out;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (current instanceof z.ZodOptional) {
|
|
30
|
-
current = current._def.innerType;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (current instanceof z.ZodNullable) {
|
|
34
|
-
current = current._def.innerType;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (current instanceof z.ZodDefault) {
|
|
38
|
-
current = current._def.innerType;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
return current;
|
|
44
|
-
}
|
|
45
17
|
function zodToOpenApi3(schema) {
|
|
46
|
-
const unwrapped =
|
|
18
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
47
19
|
const description = unwrapped.description;
|
|
48
20
|
if (unwrapped instanceof z.ZodObject) {
|
|
49
21
|
const shape = unwrapped.shape;
|
|
@@ -64,34 +36,28 @@ function zodToOpenApi3(schema) {
|
|
|
64
36
|
return out;
|
|
65
37
|
}
|
|
66
38
|
if (unwrapped instanceof z.ZodArray) {
|
|
67
|
-
const items = zodToOpenApi3(unwrapped.
|
|
39
|
+
const items = zodToOpenApi3(unwrapped.element);
|
|
68
40
|
const out = { type: 'array', items };
|
|
69
41
|
if (description)
|
|
70
42
|
out.description = description;
|
|
71
43
|
return out;
|
|
72
44
|
}
|
|
73
45
|
if (unwrapped instanceof z.ZodEnum) {
|
|
74
|
-
const options = unwrapped.options
|
|
75
|
-
|
|
76
|
-
if (Array.isArray(options)) {
|
|
77
|
-
values = options.map(String);
|
|
78
|
-
}
|
|
79
|
-
else if (options && typeof options === 'object') {
|
|
80
|
-
values = Object.values(options).map(String);
|
|
81
|
-
}
|
|
46
|
+
const options = unwrapped.options;
|
|
47
|
+
const values = options.map(String);
|
|
82
48
|
const out = values.length > 0 ? { type: 'string', enum: values } : { type: 'string' };
|
|
83
49
|
if (description)
|
|
84
50
|
out.description = description;
|
|
85
51
|
return out;
|
|
86
52
|
}
|
|
87
53
|
if (unwrapped instanceof z.ZodLiteral) {
|
|
88
|
-
const out = { const: unwrapped.
|
|
54
|
+
const out = { const: unwrapped.value };
|
|
89
55
|
if (description)
|
|
90
56
|
out.description = description;
|
|
91
57
|
return out;
|
|
92
58
|
}
|
|
93
59
|
if (unwrapped instanceof z.ZodUnion) {
|
|
94
|
-
const options = unwrapped.
|
|
60
|
+
const options = unwrapped.options;
|
|
95
61
|
const out = { oneOf: options.map((o) => zodToOpenApi3(o)) };
|
|
96
62
|
if (description)
|
|
97
63
|
out.description = description;
|
|
@@ -110,7 +76,7 @@ function zodToOpenApi3(schema) {
|
|
|
110
76
|
return out;
|
|
111
77
|
}
|
|
112
78
|
if (unwrapped instanceof z.ZodNumber) {
|
|
113
|
-
const isInt =
|
|
79
|
+
const isInt = unwrapped.isInt;
|
|
114
80
|
const out = { type: isInt ? 'integer' : 'number' };
|
|
115
81
|
if (description)
|
|
116
82
|
out.description = description;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { syncFs as fs } from '../../adapters/fs/node-fs.js';
|
|
3
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
4
|
+
function isPersistedPlanState(value) {
|
|
5
|
+
return (isRecord(value) &&
|
|
6
|
+
isRecord(value.plan) &&
|
|
7
|
+
isRecord(value.result) &&
|
|
8
|
+
typeof value.updatedAt === 'string');
|
|
9
|
+
}
|
|
3
10
|
/**
|
|
4
11
|
* Persistence layer for parallel execution plans.
|
|
5
12
|
* Supports saving, loading, and listing plans from the .salmonloop/parallel directory.
|
|
@@ -41,10 +48,13 @@ export class PlanPersistence {
|
|
|
41
48
|
const filePath = path.join(this.getPersistenceDir(repoRoot), `${planId}.json`);
|
|
42
49
|
try {
|
|
43
50
|
const content = await fs.readFile(filePath, 'utf8');
|
|
44
|
-
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
if (!isPersistedPlanState(parsed))
|
|
53
|
+
return null;
|
|
54
|
+
return parsed;
|
|
45
55
|
}
|
|
46
56
|
catch (_error) {
|
|
47
|
-
if (_error.code === 'ENOENT') {
|
|
57
|
+
if (isRecord(_error) && _error.code === 'ENOENT') {
|
|
48
58
|
return null;
|
|
49
59
|
}
|
|
50
60
|
throw _error;
|
|
@@ -63,7 +73,9 @@ export class PlanPersistence {
|
|
|
63
73
|
for (const file of jsonFiles) {
|
|
64
74
|
try {
|
|
65
75
|
const content = await fs.readFile(path.join(dir, file), 'utf8');
|
|
66
|
-
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
if (isPersistedPlanState(parsed))
|
|
78
|
+
states.push(parsed);
|
|
67
79
|
}
|
|
68
80
|
catch (_error) {
|
|
69
81
|
// Skip malformed or unreadable files
|
|
@@ -73,7 +85,7 @@ export class PlanPersistence {
|
|
|
73
85
|
return states;
|
|
74
86
|
}
|
|
75
87
|
catch (_error) {
|
|
76
|
-
if (_error.code === 'ENOENT') {
|
|
88
|
+
if (isRecord(_error) && _error.code === 'ENOENT') {
|
|
77
89
|
return [];
|
|
78
90
|
}
|
|
79
91
|
throw _error;
|
|
@@ -117,7 +129,7 @@ export class PlanPersistence {
|
|
|
117
129
|
await fs.unlink(filePath);
|
|
118
130
|
}
|
|
119
131
|
catch (_error) {
|
|
120
|
-
if (_error.code !== 'ENOENT') {
|
|
132
|
+
if (isRecord(_error) && _error.code !== 'ENOENT') {
|
|
121
133
|
throw _error;
|
|
122
134
|
}
|
|
123
135
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Phase } from '../../types/runtime.js';
|
|
2
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
1
3
|
import { isRecoverableToolInputErrorCode } from '../recoverable-tool-errors.js';
|
|
2
4
|
import { IsolationManager } from './isolation.js';
|
|
3
5
|
import { resolveArgsWithResults } from './resolve-args.js';
|
|
@@ -14,8 +16,7 @@ export class ParallelScheduler {
|
|
|
14
16
|
tryResolveSpec(node) {
|
|
15
17
|
if (node.spec)
|
|
16
18
|
return node.spec;
|
|
17
|
-
const
|
|
18
|
-
const spec = typeof router.getSpec === 'function' ? router.getSpec(node.toolName) : undefined;
|
|
19
|
+
const spec = this.router.getSpec?.(node.toolName);
|
|
19
20
|
if (!spec)
|
|
20
21
|
return undefined;
|
|
21
22
|
node.spec = spec;
|
|
@@ -28,6 +29,8 @@ export class ParallelScheduler {
|
|
|
28
29
|
return parsed.success ? parsed.data : args;
|
|
29
30
|
}
|
|
30
31
|
shouldFallbackFromComputeResources(spec, args, error) {
|
|
32
|
+
if (!spec.inputSchema || typeof spec.inputSchema.safeParse !== 'function')
|
|
33
|
+
return false;
|
|
31
34
|
const parsed = spec.inputSchema.safeParse(args);
|
|
32
35
|
if (parsed.success)
|
|
33
36
|
return false;
|
|
@@ -35,9 +38,7 @@ export class ParallelScheduler {
|
|
|
35
38
|
if (issueCode === 'invalid_type' || issueCode === 'invalid_union' || issueCode === 'custom') {
|
|
36
39
|
return true;
|
|
37
40
|
}
|
|
38
|
-
const errorCode = typeof error === '
|
|
39
|
-
? error.code
|
|
40
|
-
: undefined;
|
|
41
|
+
const errorCode = isRecord(error) && typeof error.code === 'string' ? error.code : undefined;
|
|
41
42
|
return isRecoverableToolInputErrorCode(errorCode);
|
|
42
43
|
}
|
|
43
44
|
deriveDefaultResources(spec, ctx) {
|
|
@@ -139,7 +140,7 @@ export class ParallelScheduler {
|
|
|
139
140
|
try {
|
|
140
141
|
const spec = this.tryResolveSpec(node);
|
|
141
142
|
if (!spec) {
|
|
142
|
-
const phase = typeof baseCtx.phase === 'string' ? baseCtx.phase : undefined;
|
|
143
|
+
const phase = isRecord(baseCtx) && typeof baseCtx.phase === 'string' ? baseCtx.phase : undefined;
|
|
143
144
|
const toolResult = {
|
|
144
145
|
id: nodeId,
|
|
145
146
|
toolName: node.toolName,
|
|
@@ -185,15 +186,14 @@ export class ParallelScheduler {
|
|
|
185
186
|
const resolvedArgs = resolveArgsWithResults(node.args, nodeResults);
|
|
186
187
|
const normalizedArgs = this.normalizeArgsForSpec(spec, resolvedArgs);
|
|
187
188
|
// 1.5 Deferred authorization preflight (avoid holding locks while waiting for user)
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: null;
|
|
189
|
+
const phase = isRecord(baseCtx) && typeof baseCtx.phase === 'string' ? baseCtx.phase : Phase.EXPLORE;
|
|
190
|
+
const preflight = (await this.router.preflightDeferredAuthorization?.({
|
|
191
|
+
id: nodeId,
|
|
192
|
+
phase,
|
|
193
|
+
toolName: node.toolName,
|
|
194
|
+
args: normalizedArgs,
|
|
195
|
+
ctx: baseCtx,
|
|
196
|
+
})) ?? null;
|
|
197
197
|
if (preflight?.kind === 'pending') {
|
|
198
198
|
nodeStates.set(nodeId, 'BLOCKED_APPROVAL');
|
|
199
199
|
const approval = {
|
|
@@ -250,7 +250,7 @@ export class ParallelScheduler {
|
|
|
250
250
|
const runStart = Date.now();
|
|
251
251
|
const result = await this.router.call({
|
|
252
252
|
id: nodeId,
|
|
253
|
-
phase
|
|
253
|
+
phase,
|
|
254
254
|
toolName: node.toolName,
|
|
255
255
|
args: normalizedArgs,
|
|
256
256
|
ctx: isolatedEnv
|
|
@@ -275,7 +275,9 @@ export class ParallelScheduler {
|
|
|
275
275
|
toolName: node.toolName,
|
|
276
276
|
riskLevel: spec.riskLevel,
|
|
277
277
|
message: result.error.message || 'Approval required',
|
|
278
|
-
confirmToken: result.error
|
|
278
|
+
confirmToken: isRecord(result.error)
|
|
279
|
+
? result.error.confirmToken
|
|
280
|
+
: undefined,
|
|
279
281
|
};
|
|
280
282
|
nodeResults[nodeId] = {
|
|
281
283
|
status: 'BLOCKED_APPROVAL',
|
|
@@ -307,18 +309,18 @@ export class ParallelScheduler {
|
|
|
307
309
|
catch (e) {
|
|
308
310
|
nodeStates.set(nodeId, 'FAILED');
|
|
309
311
|
const error = e instanceof Error
|
|
310
|
-
? { code: 'EXECUTION_ERROR', message: e.message,
|
|
311
|
-
: { code: 'EXECUTION_ERROR', message: String(e) };
|
|
312
|
+
? { code: 'EXECUTION_ERROR', message: e.message, retryable: false }
|
|
313
|
+
: { code: 'EXECUTION_ERROR', message: String(e), retryable: false };
|
|
312
314
|
const toolResult = {
|
|
313
315
|
id: nodeId,
|
|
314
316
|
toolName: node.toolName,
|
|
315
317
|
source: 'builtin',
|
|
316
318
|
status: 'error',
|
|
317
|
-
error
|
|
319
|
+
error,
|
|
318
320
|
};
|
|
319
321
|
nodeResults[nodeId] = {
|
|
320
322
|
status: 'FAILED',
|
|
321
|
-
error
|
|
323
|
+
error,
|
|
322
324
|
toolResult,
|
|
323
325
|
timing: { lockWaitMs: 0, runMs: 0 },
|
|
324
326
|
};
|