salmon-loop 0.3.2 → 0.4.1
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/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +6 -3
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/reporters/standard.js +2 -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/ast/parser.js +18 -9
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/gatherers/ast-gatherer.js +4 -12
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +3 -0
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/token/encoding-registry.js +7 -6
- 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 +6 -0
- package/dist/core/extensions/schemas.js +21 -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/grizzco/dsl/llm-strategy.js +3 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -10
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +5 -4
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/runtime/apply-back-runtime.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/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +9 -2
- 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/tool-runtime.js +3 -0
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +24 -18
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +6 -4
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +13 -10
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/sub-agent-factory.js +48 -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/tool-bridge.js +5 -14
- package/dist/core/mcp/client/connection-manager.js +3 -2
- 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/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/plugin/loader.js +5 -4
- package/dist/core/prompts/registry.js +11 -29
- package/dist/core/protocols/a2a/sdk/server.js +2 -3
- package/dist/core/protocols/acp/formal-agent.js +10 -4
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/initialize.js +70 -6
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/manager.js +24 -37
- package/dist/core/session/token-tracker.js +18 -7
- package/dist/core/skills/parser.js +3 -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/layers/worktree.js +7 -9
- package/dist/core/strata/runtime/synchronizer.js +10 -9
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- 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 +319 -116
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -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 +49 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/tools/budget.js +4 -11
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/fs.js +14 -6
- package/dist/core/tools/builtin/index.js +41 -107
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/proposal.js +11 -2
- 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 +66 -114
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +24 -53
- package/dist/core/tools/session.js +54 -97
- 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/utils/error.js +79 -0
- package/dist/core/utils/serialize.js +63 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/locales/en.js +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
2
3
|
import { jsonSchemaToZod } from '../schema/json-schema-to-zod.js';
|
|
3
4
|
const SAFE_TOKEN_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
4
5
|
function normalizeToken(value) {
|
|
@@ -16,11 +17,11 @@ function safeToken(value, fallback) {
|
|
|
16
17
|
return fallback;
|
|
17
18
|
}
|
|
18
19
|
function isPromptOptions(value) {
|
|
19
|
-
return Boolean(value &&
|
|
20
|
-
typeof value === 'object' &&
|
|
20
|
+
return Boolean(isRecord(value) &&
|
|
21
21
|
'serverName' in value &&
|
|
22
22
|
'client' in value &&
|
|
23
|
-
|
|
23
|
+
isRecord(value.client) &&
|
|
24
|
+
typeof value.client.listPrompts === 'function');
|
|
24
25
|
}
|
|
25
26
|
function buildFallbackSchemaFromArguments(args = []) {
|
|
26
27
|
const shape = {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { LIMITS } from '../../config/limits.js';
|
|
3
3
|
import { Phase } from '../../types/runtime.js';
|
|
4
|
+
import { safeStringify } from '../../utils/serialize.js';
|
|
4
5
|
import { classifyMcpTool } from '../policy/classifier.js';
|
|
5
6
|
import { jsonSchemaToZod } from '../schema/json-schema-to-zod.js';
|
|
6
7
|
const MCP_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
@@ -111,10 +112,9 @@ export function wrapMcpToolResult(result) {
|
|
|
111
112
|
}
|
|
112
113
|
export function mcpToolToToolSpec(input) {
|
|
113
114
|
const override = findOverride(input.server.capabilities, input.tool.name);
|
|
114
|
-
const classification = classifyMcpTool({
|
|
115
|
-
tool: input.tool,
|
|
115
|
+
const classification = classifyMcpTool(input.tool, {
|
|
116
116
|
trust: input.server.trust,
|
|
117
|
-
override,
|
|
117
|
+
override: override ? { sideEffects: override } : undefined,
|
|
118
118
|
});
|
|
119
119
|
const phase = input.server.capabilities.tools.phases[0] ?? Phase.VERIFY;
|
|
120
120
|
const grantDecision = input.policy.decideTool({
|
|
@@ -169,7 +169,7 @@ export async function registerMcpV2Tools(input) {
|
|
|
169
169
|
if (!catalog)
|
|
170
170
|
continue;
|
|
171
171
|
for (const tool of catalog.tools) {
|
|
172
|
-
const classification = classifyMcpTool(
|
|
172
|
+
const classification = classifyMcpTool(tool, { trust: server.trust });
|
|
173
173
|
const decision = input.policy.decideTool({
|
|
174
174
|
server: server.name,
|
|
175
175
|
toolName: tool.name,
|
|
@@ -267,16 +267,7 @@ function summarizeMcpAuthorization(input, args, riskLevel, sideEffects) {
|
|
|
267
267
|
: { kind: 'classified', reason: input.classification.reason },
|
|
268
268
|
args,
|
|
269
269
|
};
|
|
270
|
-
return safeStringify(payload);
|
|
271
|
-
}
|
|
272
|
-
function safeStringify(value, maxLength = 1200) {
|
|
273
|
-
try {
|
|
274
|
-
const raw = JSON.stringify(value);
|
|
275
|
-
return raw.length <= maxLength ? raw : `${raw.slice(0, maxLength)}...`;
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
return '[Unserializable]';
|
|
279
|
-
}
|
|
270
|
+
return safeStringify(payload, { maxLength: 1200 });
|
|
280
271
|
}
|
|
281
272
|
function coerceRecord(input) {
|
|
282
273
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
@@ -2,6 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { CallToolResultSchema, PromptListChangedNotificationSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ToolListChangedNotificationSchema, GetPromptResultSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import { LIMITS } from '../../config/limits.js';
|
|
4
4
|
import { getLogger } from '../../observability/logger.js';
|
|
5
|
+
import { errorMessage } from '../../utils/error.js';
|
|
5
6
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
6
7
|
import { discoverMcpCatalog } from '../catalog/discovery.js';
|
|
7
8
|
import { McpNotificationRouter } from '../catalog/notification-router.js';
|
|
@@ -93,7 +94,7 @@ export class McpConnectionManager {
|
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
95
96
|
entry.status = 'degraded';
|
|
96
|
-
entry.error =
|
|
97
|
+
entry.error = errorMessage(error);
|
|
97
98
|
getLogger().warn(`Failed to connect MCP server ${server.name}: ${entry.error}`);
|
|
98
99
|
}
|
|
99
100
|
return this.view(entry);
|
|
@@ -183,7 +184,7 @@ export class McpConnectionManager {
|
|
|
183
184
|
entry.subscribedResources.add(uri);
|
|
184
185
|
}
|
|
185
186
|
catch (error) {
|
|
186
|
-
const message =
|
|
187
|
+
const message = errorMessage(error);
|
|
187
188
|
getLogger().warn(`MCP server ${entry.server.name} resource subscription failed for ${uri}: ${message}`);
|
|
188
189
|
}
|
|
189
190
|
}
|
|
@@ -120,7 +120,7 @@ export class McpSamplingProvider {
|
|
|
120
120
|
throw new Error('MCP_SAMPLING_TOKEN_LIMIT_EXCEEDED');
|
|
121
121
|
}
|
|
122
122
|
const result = await this.llm.chat([{ role: 'user', content: input.prompt }]);
|
|
123
|
-
return typeof result === 'string' ? result : String(result
|
|
123
|
+
return typeof result === 'string' ? result : String(result.content ?? '');
|
|
124
124
|
}
|
|
125
125
|
async createMessage(params, options = {}) {
|
|
126
126
|
const sanitizedParams = sanitizeForAudit(params, this.maxDepth);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
3
4
|
export function jsonSchemaToZod(jsonSchema) {
|
|
4
5
|
return jsonSchemaToZodWithContext(jsonSchema, jsonSchema);
|
|
5
6
|
}
|
|
@@ -499,7 +500,7 @@ function propertyNamesToZod(jsonSchema, rootSchema) {
|
|
|
499
500
|
return jsonSchemaToZodWithContext(jsonSchema, rootSchema);
|
|
500
501
|
}
|
|
501
502
|
function isPlainObject(value) {
|
|
502
|
-
return
|
|
503
|
+
return isRecord(value);
|
|
503
504
|
}
|
|
504
505
|
function isMultipleOf(value, divisor) {
|
|
505
506
|
const quotient = value / divisor;
|
|
@@ -77,11 +77,12 @@ export function buildRelevantMemoryCandidates(context) {
|
|
|
77
77
|
tags: ['rules', 'project'],
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
const userPrefs = trimToUndefined(knowledge?.user_preferences);
|
|
81
|
+
if (userPrefs) {
|
|
81
82
|
candidates.push({
|
|
82
83
|
path: '.salmonloop/knowledge/user_preferences',
|
|
83
84
|
title: 'User preferences',
|
|
84
|
-
summary:
|
|
85
|
+
summary: userPrefs,
|
|
85
86
|
tags: ['preferences', 'user'],
|
|
86
87
|
});
|
|
87
88
|
}
|
|
@@ -96,11 +97,12 @@ export function buildRelevantMemoryCandidates(context) {
|
|
|
96
97
|
tags: ['architecture', ...(decision.related_files ?? []).map((file) => file.toLowerCase())],
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
const aiInstructions = trimToUndefined(metadata?.aiInstructions);
|
|
101
|
+
if (aiInstructions) {
|
|
100
102
|
candidates.push({
|
|
101
103
|
path: '.salmonloop/project/ai-instructions',
|
|
102
104
|
title: 'Project AI instructions',
|
|
103
|
-
summary:
|
|
105
|
+
summary: aiInstructions,
|
|
104
106
|
tags: ['instructions', 'project'],
|
|
105
107
|
});
|
|
106
108
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord } from '../utils/serialize.js';
|
|
1
2
|
import { getAuditTrail } from './audit-trail.js';
|
|
2
3
|
function safeString(value) {
|
|
3
4
|
if (typeof value !== 'string')
|
|
@@ -23,13 +24,13 @@ export function extractAuthorizationDecisionsFromAuditTrail(auditTrail) {
|
|
|
23
24
|
continue;
|
|
24
25
|
if (event.action !== 'authorization.decision')
|
|
25
26
|
continue;
|
|
26
|
-
|
|
27
|
-
if (!details || typeof details !== 'object')
|
|
27
|
+
if (!event.details || typeof event.details !== 'object')
|
|
28
28
|
continue;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
29
|
+
const d = asRecord(event.details);
|
|
30
|
+
const callId = safeString(d.callId);
|
|
31
|
+
const toolName = safeString(d.toolName);
|
|
32
|
+
const phase = safeString(d.phase) ?? safeString(event.phase);
|
|
33
|
+
const outcome = safeString(d.outcome);
|
|
33
34
|
if (!callId || !toolName || !phase || !outcome)
|
|
34
35
|
continue;
|
|
35
36
|
decisions.push({
|
|
@@ -37,12 +38,12 @@ export function extractAuthorizationDecisionsFromAuditTrail(auditTrail) {
|
|
|
37
38
|
toolName,
|
|
38
39
|
phase: phase,
|
|
39
40
|
outcome: outcome,
|
|
40
|
-
source: (safeString(
|
|
41
|
-
reason: safeString(
|
|
42
|
-
ttlMs: safeNumber(
|
|
43
|
-
persist: safeString(
|
|
44
|
-
riskLevel: safeString(
|
|
45
|
-
sideEffects: safeStringArray(
|
|
41
|
+
source: (safeString(d.source) ?? 'unknown'),
|
|
42
|
+
reason: safeString(d.reason),
|
|
43
|
+
ttlMs: safeNumber(d.ttlMs),
|
|
44
|
+
persist: safeString(d.persist),
|
|
45
|
+
riskLevel: safeString(d.riskLevel),
|
|
46
|
+
sideEffects: safeStringArray(d.sideEffects),
|
|
46
47
|
timestamp: safeString(event.timestamp) ?? new Date().toISOString(),
|
|
47
48
|
});
|
|
48
49
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { text } from '../../locales/index.js';
|
|
2
|
+
import { isRecord } from '../utils/serialize.js';
|
|
2
3
|
import { getAuditTrail } from './audit-trail.js';
|
|
3
4
|
import { REDACTED_ERROR_TOKEN } from './error-envelope.js';
|
|
4
5
|
function mapLlmCodeToMessage(code) {
|
|
@@ -193,7 +194,7 @@ export function mapErrorForAudit(input) {
|
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
196
|
function buildLangfuseHttpFailed(details) {
|
|
196
|
-
const status = typeof details
|
|
197
|
+
const status = isRecord(details) && typeof details.status === 'number' ? details.status : undefined;
|
|
197
198
|
if (!status)
|
|
198
199
|
return undefined;
|
|
199
200
|
if (status === 401 || status === 403) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asRecord } from '../utils/serialize.js';
|
|
1
2
|
import { getAuditTrail } from './audit-trail.js';
|
|
2
3
|
function safeFiniteNumber(value) {
|
|
3
4
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
@@ -12,11 +13,11 @@ export function extractTokenUsageFromAuditTrail(auditTrail) {
|
|
|
12
13
|
continue;
|
|
13
14
|
if (event.action !== 'llm.usage')
|
|
14
15
|
continue;
|
|
15
|
-
|
|
16
|
-
if (!details || typeof details !== 'object')
|
|
16
|
+
if (!event.details || typeof event.details !== 'object')
|
|
17
17
|
continue;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const d = asRecord(event.details);
|
|
19
|
+
const promptTokens = safeFiniteNumber(d.promptTokens);
|
|
20
|
+
const completionTokens = safeFiniteNumber(d.completionTokens);
|
|
20
21
|
if (typeof promptTokens === 'number')
|
|
21
22
|
inputTokens += promptTokens;
|
|
22
23
|
if (typeof completionTokens === 'number')
|
|
@@ -2,6 +2,7 @@ import { join } from 'path';
|
|
|
2
2
|
import { typescriptPlugin, tsxPlugin, javascriptPlugin } from '../../languages/typescript/index.js';
|
|
3
3
|
import { readdir } from '../adapters/fs/node-fs.js';
|
|
4
4
|
import { getLogger } from '../observability/logger.js';
|
|
5
|
+
import { errorMessage } from '../utils/error.js';
|
|
5
6
|
import { validateQueryPack } from './validator.js';
|
|
6
7
|
// Import built-in plugins (Phase 1: explicit import)
|
|
7
8
|
export class PluginLoader {
|
|
@@ -31,11 +32,11 @@ export class PluginLoader {
|
|
|
31
32
|
catch (error) {
|
|
32
33
|
// In test environment, we want to know why it failed
|
|
33
34
|
if (process.env.NODE_ENV === 'test') {
|
|
34
|
-
const errorMsg =
|
|
35
|
+
const errorMsg = errorMessage(error);
|
|
35
36
|
getLogger().error(`CRITICAL: Failed to load plugins: ${errorMsg}`);
|
|
36
37
|
throw error;
|
|
37
38
|
}
|
|
38
|
-
getLogger().error(`Failed to load plugins: ${
|
|
39
|
+
getLogger().error(`Failed to load plugins: ${errorMessage(error)}`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
@@ -70,7 +71,7 @@ export class PluginLoader {
|
|
|
70
71
|
}
|
|
71
72
|
catch (err) {
|
|
72
73
|
if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') {
|
|
73
|
-
getLogger().warn(`Failed to load user plugin from ${dirName}: ${
|
|
74
|
+
getLogger().warn(`Failed to load user plugin from ${dirName}: ${errorMessage(err)}`);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -78,7 +79,7 @@ export class PluginLoader {
|
|
|
78
79
|
catch (err) {
|
|
79
80
|
// Ignore if directory doesn't exist
|
|
80
81
|
if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') {
|
|
81
|
-
getLogger().debug(`Error scanning for user plugins: ${
|
|
82
|
+
getLogger().debug(`Error scanning for user plugins: ${errorMessage(err)}`);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
}
|
|
@@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
2
2
|
import Handlebars from 'handlebars';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { readFile } from '../adapters/fs/node-fs.js';
|
|
5
|
+
import { unwrapZodSchema } from '../utils/zod.js';
|
|
5
6
|
const TEMPLATE_URLS = {
|
|
6
7
|
'system/_tool_defs.hbs': new URL('./templates/system/_tool_defs.hbs', import.meta.url),
|
|
7
8
|
'system/main_system.hbs': new URL('./templates/system/main_system.hbs', import.meta.url),
|
|
@@ -60,8 +61,8 @@ export class PromptRegistry {
|
|
|
60
61
|
if (!url) {
|
|
61
62
|
throw new Error(`Unknown prompt template path: ${relativePath}`);
|
|
62
63
|
}
|
|
63
|
-
const
|
|
64
|
-
const bun =
|
|
64
|
+
const bunGlobal = globalThis;
|
|
65
|
+
const bun = bunGlobal.Bun;
|
|
65
66
|
if (bun?.file) {
|
|
66
67
|
return bun.file(url).text();
|
|
67
68
|
}
|
|
@@ -113,34 +114,15 @@ export class PromptRegistry {
|
|
|
113
114
|
if (!zodSchema) {
|
|
114
115
|
return { type: 'object', description: 'Schema details unavailable' };
|
|
115
116
|
}
|
|
116
|
-
const unwrapForJsonSchema = (schema) => {
|
|
117
|
-
let current = schema;
|
|
118
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
119
|
-
const ZodEffects = z.ZodEffects;
|
|
120
|
-
if (ZodEffects && current instanceof ZodEffects) {
|
|
121
|
-
current = current._def.schema;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (current instanceof z.ZodPipe) {
|
|
125
|
-
current = current._def.out;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (current instanceof z.ZodOptional ||
|
|
129
|
-
current instanceof z.ZodNullable ||
|
|
130
|
-
current instanceof z.ZodDefault) {
|
|
131
|
-
current = current._def.innerType;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
return current;
|
|
137
|
-
};
|
|
138
117
|
try {
|
|
139
|
-
const unwrapped =
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
142
|
-
const
|
|
143
|
-
|
|
118
|
+
const unwrapped = unwrapZodSchema(zodSchema);
|
|
119
|
+
const zWithJson = z;
|
|
120
|
+
if (zWithJson.toJSONSchema) {
|
|
121
|
+
const schema = zWithJson.toJSONSchema(unwrapped);
|
|
122
|
+
if (schema && typeof schema === 'object') {
|
|
123
|
+
const { $schema: _ignored, ...rest } = schema;
|
|
124
|
+
return rest;
|
|
125
|
+
}
|
|
144
126
|
}
|
|
145
127
|
}
|
|
146
128
|
catch (_e) {
|
|
@@ -406,9 +406,8 @@ const normalizeA2AExtensionHeadersByVersion = (req, res, next) => {
|
|
|
406
406
|
});
|
|
407
407
|
next();
|
|
408
408
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
409
|
+
import { isRecord } from '../../../utils/serialize.js';
|
|
410
|
+
const isObjectRecord = isRecord;
|
|
412
411
|
function looksLikeTask(result) {
|
|
413
412
|
return typeof result.id === 'string' && 'status' in result;
|
|
414
413
|
}
|
|
@@ -7,6 +7,7 @@ import { toAcpPublicModes } from '../../public-capabilities/projections.js';
|
|
|
7
7
|
import { buildPublicCapabilityRegistry } from '../../public-capabilities/registry.js';
|
|
8
8
|
import { parseSlashInput } from '../../slash/parser.js';
|
|
9
9
|
import { Phase } from '../../types/runtime.js';
|
|
10
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
10
11
|
import { buildCanonicalExecutionRequest } from '../shared/execution-request.js';
|
|
11
12
|
import { parseAcpFlowMode } from '../shared/flow-mode-mapping.js';
|
|
12
13
|
import { probeCheckpoint, probeCheckpointForNewSession, } from './acp-checkpoint-probe.js';
|
|
@@ -18,7 +19,7 @@ import { createAcpSessionStore, isTerminalTaskEvent } from './handlers.js';
|
|
|
18
19
|
import { createAcpToolAuthorizationProvider } from './permission-provider.js';
|
|
19
20
|
import { mapToolKind } from './tool-kind-mapping.js';
|
|
20
21
|
function formatInputRequiredMessage(inputRequired) {
|
|
21
|
-
if (!inputRequired || !Array.isArray(inputRequired.questions))
|
|
22
|
+
if (!inputRequired || !isRecord(inputRequired) || !Array.isArray(inputRequired.questions))
|
|
22
23
|
return null;
|
|
23
24
|
const questions = inputRequired.questions;
|
|
24
25
|
if (questions.length === 0)
|
|
@@ -525,6 +526,7 @@ function acpMcpServersToExtensions(mcpServers) {
|
|
|
525
526
|
mcpServers: resolvedServers,
|
|
526
527
|
toolPlugins: [],
|
|
527
528
|
skillDiscovery: { paths: [], scope: 'repo' },
|
|
529
|
+
agentProfiles: [],
|
|
528
530
|
};
|
|
529
531
|
}
|
|
530
532
|
function validateAcpMcpServers(mcpServers) {
|
|
@@ -569,12 +571,13 @@ function isPersistableSession(session) {
|
|
|
569
571
|
async function awaitTerminalEvent(params) {
|
|
570
572
|
if (!params.eventBus)
|
|
571
573
|
return null;
|
|
572
|
-
const
|
|
574
|
+
const { eventBus } = params;
|
|
575
|
+
const history = eventBus.list(params.taskId);
|
|
573
576
|
const terminal = history.find(isTerminalTaskEvent);
|
|
574
577
|
if (terminal)
|
|
575
578
|
return terminal;
|
|
576
579
|
return await new Promise((resolve) => {
|
|
577
|
-
const unsubscribe =
|
|
580
|
+
const unsubscribe = eventBus.subscribe((event) => {
|
|
578
581
|
if (event.taskId !== params.taskId)
|
|
579
582
|
return;
|
|
580
583
|
if (!isTerminalTaskEvent(event))
|
|
@@ -1092,7 +1095,10 @@ export function createAcpFormalAgent(deps) {
|
|
|
1092
1095
|
...current,
|
|
1093
1096
|
history: [
|
|
1094
1097
|
...current.history,
|
|
1095
|
-
{
|
|
1098
|
+
{
|
|
1099
|
+
role: 'assistant',
|
|
1100
|
+
content: [buildTextContentBlock(responseText)],
|
|
1101
|
+
},
|
|
1096
1102
|
],
|
|
1097
1103
|
}));
|
|
1098
1104
|
await sessionPersistence.persist();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Readable, Writable } from 'node:stream';
|
|
2
2
|
import { AgentSideConnection } from '@agentclientprotocol/sdk';
|
|
3
3
|
import { tryGetLogger } from '../../observability/logger.js';
|
|
4
|
+
import { errorMessage } from '../../utils/error.js';
|
|
5
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
4
6
|
const INVALID_REQUEST = {
|
|
5
7
|
jsonrpc: '2.0',
|
|
6
8
|
id: null,
|
|
@@ -11,9 +13,7 @@ const PARSE_ERROR = {
|
|
|
11
13
|
id: null,
|
|
12
14
|
error: { code: -32700, message: 'Parse error' },
|
|
13
15
|
};
|
|
14
|
-
|
|
15
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
-
}
|
|
16
|
+
const isJsonObject = isRecord;
|
|
17
17
|
function hasOwn(value, key) {
|
|
18
18
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
19
19
|
}
|
|
@@ -50,9 +50,9 @@ function createNdjsonWriter(output) {
|
|
|
50
50
|
.catch(() => undefined)
|
|
51
51
|
.then(() => writer.write(data))
|
|
52
52
|
.catch((error) => {
|
|
53
|
-
const detail =
|
|
53
|
+
const detail = errorMessage(error);
|
|
54
54
|
safeWarn(`ACP stdio failed to write NDJSON line. reason="${detail}"`);
|
|
55
|
-
lastError =
|
|
55
|
+
lastError = new Error(detail);
|
|
56
56
|
});
|
|
57
57
|
await tail;
|
|
58
58
|
},
|
|
@@ -83,7 +83,7 @@ async function processStdioLine(line, ndjson, controller) {
|
|
|
83
83
|
controller.enqueue(parsed);
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
86
|
-
const detail =
|
|
86
|
+
const detail = errorMessage(error);
|
|
87
87
|
safeWarn(`ACP stdio failed to parse JSON line. reason="${detail}"`);
|
|
88
88
|
await ndjson.write(PARSE_ERROR);
|
|
89
89
|
}
|
|
@@ -65,12 +65,13 @@ export function createAgentServerRuntime(deps) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
async function close() {
|
|
68
|
-
|
|
68
|
+
const instance = a2aServerInstance;
|
|
69
|
+
if (!instance) {
|
|
69
70
|
started = false;
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
await new Promise((resolve, reject) => {
|
|
73
|
-
|
|
74
|
+
instance.close((error) => {
|
|
74
75
|
if (error) {
|
|
75
76
|
reject(error);
|
|
76
77
|
return;
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from '../adapters/fs/node-fs.js';
|
|
1
2
|
import { initializeDefaultCalculator } from '../context/policies/pack-until-full.js';
|
|
3
|
+
import { getRepoAgentsConfigPath, getUserAgentsConfigPath } from '../extensions/paths.js';
|
|
4
|
+
import { AgentsConfigSchema } from '../extensions/schemas.js';
|
|
2
5
|
import { createLogger, getLogger, setLogger, tryGetLogger } from '../observability/logger.js';
|
|
3
6
|
import { createMonitor, setMonitor, tryGetMonitor } from '../observability/monitor.js';
|
|
4
7
|
import { registerDefaultSubAgentProfiles } from '../sub-agent/registry-defaults.js';
|
|
5
8
|
import { createSubAgentRegistry, setSubAgentRegistry, tryGetSubAgentRegistry, } from '../sub-agent/registry.js';
|
|
9
|
+
import { isRecord } from '../utils/serialize.js';
|
|
6
10
|
/**
|
|
7
11
|
* Initializes the Core safety runtime.
|
|
8
12
|
* Mounts global error handlers and ensures environment safety.
|
|
9
13
|
*/
|
|
14
|
+
const GLOBAL_FLAG = '__SALMON_RUNTIME_INITIALIZED__';
|
|
10
15
|
export function initializeRuntime() {
|
|
11
16
|
// Prevent duplicate initialization
|
|
12
|
-
if (globalThis
|
|
17
|
+
if (globalThis[GLOBAL_FLAG])
|
|
13
18
|
return;
|
|
14
19
|
// Bypass interception in debug mode to allow raw console/stream output
|
|
15
20
|
if (process.env.SALMONLOOP_DEBUG === 'true') {
|
|
16
|
-
globalThis
|
|
21
|
+
globalThis[GLOBAL_FLAG] = true;
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
24
|
// Preload token calculator in background (non-blocking)
|
|
@@ -32,6 +37,7 @@ export function initializeRuntime() {
|
|
|
32
37
|
if (!tryGetSubAgentRegistry()) {
|
|
33
38
|
const registry = createSubAgentRegistry();
|
|
34
39
|
registerDefaultSubAgentProfiles(registry);
|
|
40
|
+
loadUserAgentProfiles(registry);
|
|
35
41
|
setSubAgentRegistry(registry);
|
|
36
42
|
}
|
|
37
43
|
const isGui = process.argv.includes('--gui');
|
|
@@ -40,10 +46,12 @@ export function initializeRuntime() {
|
|
|
40
46
|
const originalConsoleError = console.error;
|
|
41
47
|
console.error = (...args) => {
|
|
42
48
|
const sanitizedArgs = args.map((arg) => {
|
|
43
|
-
if (
|
|
49
|
+
if (isRecord(arg)) {
|
|
44
50
|
// Drop the object structure entirely for console output to prevent UI pollution
|
|
45
|
-
const code = arg.code
|
|
46
|
-
|
|
51
|
+
const code = (typeof arg.code === 'string' ? arg.code : undefined) ||
|
|
52
|
+
(typeof arg.llmCode === 'string' ? arg.llmCode : undefined) ||
|
|
53
|
+
'TECHNICAL_ERROR';
|
|
54
|
+
const msg = (typeof arg.message === 'string' ? arg.message : undefined) || 'No detail provided';
|
|
47
55
|
return `[${code}] ${msg}`;
|
|
48
56
|
}
|
|
49
57
|
return arg;
|
|
@@ -127,6 +135,62 @@ export function initializeRuntime() {
|
|
|
127
135
|
process.on('uncaughtException', (error) => {
|
|
128
136
|
getLogger().error('Uncaught Exception detected in Core runtime', error, true);
|
|
129
137
|
});
|
|
130
|
-
globalThis
|
|
138
|
+
globalThis[GLOBAL_FLAG] = true;
|
|
139
|
+
}
|
|
140
|
+
function loadUserAgentProfiles(registry) {
|
|
141
|
+
const tryLoadSync = (filePath) => {
|
|
142
|
+
try {
|
|
143
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
144
|
+
return AgentsConfigSchema.parse(JSON.parse(content));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
// Load from repo and user scopes; repo takes priority
|
|
151
|
+
const repoRoot = process.cwd();
|
|
152
|
+
const userConfig = tryLoadSync(getUserAgentsConfigPath());
|
|
153
|
+
const repoConfig = tryLoadSync(getRepoAgentsConfigPath(repoRoot));
|
|
154
|
+
const toProfile = (raw) => ({
|
|
155
|
+
id: raw.id,
|
|
156
|
+
name: raw.name,
|
|
157
|
+
role: raw.role,
|
|
158
|
+
description: raw.description,
|
|
159
|
+
allowedTools: raw.allowedTools ?? ['code.search', 'fs.read'],
|
|
160
|
+
readOnly: raw.readOnly ?? false,
|
|
161
|
+
stratagem: raw.stratagem ?? 'investigator',
|
|
162
|
+
toolInheritance: raw.toolInheritance,
|
|
163
|
+
permissionMode: raw.permissionMode,
|
|
164
|
+
systemPrompt: raw.systemPrompt,
|
|
165
|
+
maxTokens: raw.maxTokens,
|
|
166
|
+
maxAttempts: raw.maxAttempts,
|
|
167
|
+
timeoutMs: raw.timeoutMs,
|
|
168
|
+
});
|
|
169
|
+
// User profiles first (lower priority)
|
|
170
|
+
if (userConfig) {
|
|
171
|
+
for (const agent of userConfig.agents) {
|
|
172
|
+
if (agent.enabled === false)
|
|
173
|
+
continue;
|
|
174
|
+
// Don't override built-in profiles
|
|
175
|
+
if (registry.has(agent.id)) {
|
|
176
|
+
tryGetLogger()?.debug(`[initializeRuntime] Skipping user agent '${agent.id}': conflicts with built-in profile`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
registry.register(toProfile(agent));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Repo profiles override user (higher priority)
|
|
183
|
+
if (repoConfig) {
|
|
184
|
+
for (const agent of repoConfig.agents) {
|
|
185
|
+
if (agent.enabled === false)
|
|
186
|
+
continue;
|
|
187
|
+
// Don't override built-in profiles
|
|
188
|
+
if (registry.has(agent.id)) {
|
|
189
|
+
tryGetLogger()?.debug(`[initializeRuntime] Skipping repo agent '${agent.id}': conflicts with built-in profile`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
registry.register(toProfile(agent));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
131
195
|
}
|
|
132
196
|
//# sourceMappingURL=initialize.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getModelRecommendedBudget } from '../../context/token/adaptive-budget.js';
|
|
2
2
|
import { LlmError } from '../../llm/errors.js';
|
|
3
3
|
import { getLogger } from '../../observability/logger.js';
|
|
4
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
4
5
|
import { refreshSessionSummary } from '../summary-sync.js';
|
|
5
6
|
import { TokenTracker } from '../token-tracker.js';
|
|
6
7
|
import { isCircuitBreakerTripped, onCompactionFailure, onCompactionSuccess } from './tracking.js';
|
|
@@ -11,8 +12,8 @@ function isContextOverflowLike(error) {
|
|
|
11
12
|
}
|
|
12
13
|
const message = error instanceof Error
|
|
13
14
|
? error.message
|
|
14
|
-
: error && typeof error
|
|
15
|
-
?
|
|
15
|
+
: isRecord(error) && typeof error.message === 'string'
|
|
16
|
+
? error.message
|
|
16
17
|
: '';
|
|
17
18
|
if (!message)
|
|
18
19
|
return false;
|
|
@@ -136,7 +137,7 @@ export async function autocompact(params) {
|
|
|
136
137
|
performed: true,
|
|
137
138
|
tracking: onCompactionSuccess(tracking),
|
|
138
139
|
preTokens: totalTokens,
|
|
139
|
-
trigger
|
|
140
|
+
trigger,
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
catch (error) {
|