salmon-loop 0.2.16 → 0.3.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.
Files changed (98) hide show
  1. package/dist/cli/authorization/non-interactive.js +7 -21
  2. package/dist/cli/commands/chat.js +1 -1
  3. package/dist/cli/commands/parallel.js +46 -41
  4. package/dist/cli/commands/run/assistant-message.js +3 -0
  5. package/dist/cli/commands/run/handler.js +2 -1
  6. package/dist/cli/commands/serve.js +112 -156
  7. package/dist/cli/headless/json-protocol.js +1 -1
  8. package/dist/cli/headless/stream-json-protocol.js +3 -2
  9. package/dist/cli/program-bootstrap.js +2 -2
  10. package/dist/cli/slash/runtime.js +5 -1
  11. package/dist/core/adapters/fs/node-fs.js +1 -0
  12. package/dist/core/backends/salmon-loop/task-executor.js +1 -0
  13. package/dist/core/benchmark/patch-artifact.js +1 -1
  14. package/dist/core/context/service.js +5 -2
  15. package/dist/core/extensions/index.js +2 -35
  16. package/dist/core/extensions/merge.js +14 -0
  17. package/dist/core/extensions/redact.js +9 -3
  18. package/dist/core/extensions/schemas.js +2 -51
  19. package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
  20. package/dist/core/facades/cli-program-bootstrap.js +1 -0
  21. package/dist/core/facades/cli-serve.js +2 -1
  22. package/dist/core/grizzco/dsl/strategies.js +1 -3
  23. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
  24. package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
  25. package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
  26. package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
  27. package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
  28. package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
  29. package/dist/core/grizzco/steps/apply.js +0 -7
  30. package/dist/core/grizzco/steps/autopilot.js +108 -6
  31. package/dist/core/grizzco/steps/preflight.js +10 -0
  32. package/dist/core/grizzco/steps/tool-runtime.js +1 -0
  33. package/dist/core/interaction/events/bus.js +14 -0
  34. package/dist/core/interaction/orchestration/facade.js +11 -1
  35. package/dist/core/llm/ai-sdk/request-params.js +40 -1
  36. package/dist/core/mcp/bridge/index.js +4 -0
  37. package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
  38. package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
  39. package/dist/core/mcp/bridge/tool-bridge.js +303 -0
  40. package/dist/core/mcp/cache/resource-cache.js +41 -0
  41. package/dist/core/mcp/catalog/discovery.js +51 -0
  42. package/dist/core/mcp/catalog/notification-router.js +28 -0
  43. package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
  44. package/dist/core/mcp/catalog/resource-catalog.js +7 -0
  45. package/dist/core/mcp/catalog/tool-catalog.js +4 -0
  46. package/dist/core/mcp/client/connection-manager.js +239 -0
  47. package/dist/core/mcp/client/lifecycle.js +13 -0
  48. package/dist/core/mcp/client/transport-factory.js +168 -0
  49. package/dist/core/mcp/config/index.js +32 -0
  50. package/dist/core/mcp/config/schema-v2.js +129 -0
  51. package/dist/core/mcp/host/elicitation-provider.js +209 -0
  52. package/dist/core/mcp/host/roots-provider.js +70 -0
  53. package/dist/core/mcp/host/sampling-provider.js +170 -0
  54. package/dist/core/mcp/index.js +4 -0
  55. package/dist/core/mcp/observability/events.js +19 -0
  56. package/dist/core/mcp/policy/approval-policy.js +2 -0
  57. package/dist/core/mcp/policy/classifier.js +172 -0
  58. package/dist/core/mcp/policy/grants.js +356 -0
  59. package/dist/core/mcp/policy/uri-policy.js +60 -0
  60. package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
  61. package/dist/core/mcp/types.js +2 -0
  62. package/dist/core/protocols/a2a/agent-card.js +38 -12
  63. package/dist/core/protocols/a2a/sdk/executor.js +105 -36
  64. package/dist/core/protocols/a2a/sdk/server.js +1311 -3
  65. package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
  66. package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
  67. package/dist/core/protocols/acp/acp-types.js +17 -0
  68. package/dist/core/protocols/acp/formal-agent.js +389 -502
  69. package/dist/core/protocols/acp/handlers.js +3 -0
  70. package/dist/core/protocols/acp/permission-provider.js +11 -39
  71. package/dist/core/protocols/acp/stdio-server.js +20 -1
  72. package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
  73. package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
  74. package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
  75. package/dist/core/public-capabilities/projections.js +1 -0
  76. package/dist/core/runtime/agent-server-runtime.js +2 -3
  77. package/dist/core/runtime/spawn-command.js +8 -2
  78. package/dist/core/runtime/spawn-interactive.js +26 -0
  79. package/dist/core/session/manager.js +48 -25
  80. package/dist/core/tools/builtin/index.js +6 -1
  81. package/dist/core/tools/builtin/proposal.js +0 -7
  82. package/dist/core/tools/builtin/workspace.js +76 -0
  83. package/dist/core/tools/dispatcher.js +1 -0
  84. package/dist/core/tools/loader.js +92 -46
  85. package/dist/core/verification/runner.js +60 -31
  86. package/dist/core/version.js +17 -0
  87. package/dist/core/workspace/capabilities.js +80 -0
  88. package/dist/locales/en.js +17 -3
  89. package/package.json +4 -2
  90. package/dist/core/protocols/a2a/mapper.js +0 -14
  91. package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
  92. package/dist/core/protocols/a2a/task-projection.js +0 -45
  93. package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
  94. package/dist/core/tools/mcp/client.js +0 -308
  95. package/dist/core/tools/mcp/loader.js +0 -110
  96. package/dist/core/tools/mcp/schema.js +0 -54
  97. package/dist/core/tools/mcp/streamable-http.js +0 -101
  98. package/dist/core/tools/mcp/types.js +0 -26
@@ -0,0 +1,356 @@
1
+ import { classifyMcpTool } from './classifier.js';
2
+ import { evaluateMcpUriPolicy, matchesMcpPattern, McpUriPolicy, } from './uri-policy.js';
3
+ export function decideMcpApprovalPolicy(input) {
4
+ const risk = input.remote ? raiseRisk(input.risk) : input.risk;
5
+ if (input.capability === 'sampling') {
6
+ return {
7
+ outcome: input.defaultOutcome ?? 'deny',
8
+ reason: input.defaultOutcome
9
+ ? 'MCP_SAMPLING_EXPLICIT_GRANT'
10
+ : 'MCP_SAMPLING_DENIED_BY_DEFAULT',
11
+ risk,
12
+ };
13
+ }
14
+ if (input.capability === 'elicitation') {
15
+ return {
16
+ outcome: input.defaultOutcome ?? 'ask',
17
+ reason: 'MCP_ELICITATION_APPROVAL_REQUIRED_BY_DEFAULT',
18
+ risk,
19
+ };
20
+ }
21
+ if (risk === 'high') {
22
+ return {
23
+ outcome: input.defaultOutcome ?? 'ask',
24
+ reason: 'MCP_HIGH_RISK_APPROVAL_REQUIRED',
25
+ risk,
26
+ };
27
+ }
28
+ return {
29
+ outcome: input.defaultOutcome ?? 'allow',
30
+ reason: 'MCP_GRANTED',
31
+ risk,
32
+ };
33
+ }
34
+ export class McpPolicyEngine {
35
+ uriPolicy = new McpUriPolicy();
36
+ grants;
37
+ constructor(input = []) {
38
+ this.grants = Array.isArray(input) ? input : (input.grants ?? []);
39
+ }
40
+ decide(request) {
41
+ if (request.capability === 'tools') {
42
+ return this.decideTool({
43
+ server: request.server.name,
44
+ toolName: request.target ?? request.tool?.name ?? '',
45
+ phase: request.phase,
46
+ classification: request.tool
47
+ ? classifyMcpTool(request.tool, { trust: request.server.trust ?? 'local' })
48
+ : undefined,
49
+ remote: request.server.trust === 'remote' || request.server.transport === 'http',
50
+ });
51
+ }
52
+ if (request.capability === 'resources') {
53
+ return this.decideResource({
54
+ server: request.server.name,
55
+ uri: request.target ?? '',
56
+ });
57
+ }
58
+ if (request.capability === 'prompts') {
59
+ return this.decidePrompt({
60
+ server: request.server.name,
61
+ name: request.target ?? '',
62
+ });
63
+ }
64
+ if (request.capability === 'roots') {
65
+ return this.decideRoots(request.server.name, request.target);
66
+ }
67
+ if (request.capability === 'sampling') {
68
+ return this.decideSampling(request.server.name, request.target);
69
+ }
70
+ return this.decideElicitation(request.server.name, request.target);
71
+ }
72
+ decideTool(input) {
73
+ const grant = this.grants.find((item) => 'kind' in item &&
74
+ item.kind === 'tool' &&
75
+ matchesServer(item.server, input.server) &&
76
+ matchesMcpPattern(input.toolName, item.namePattern));
77
+ const genericGrant = this.findGenericGrant('tools', input.server, 'call', input.toolName, input.phase);
78
+ const matchedGrant = grant ?? genericGrant;
79
+ if (!matchedGrant) {
80
+ return deny('MCP_TOOL_NOT_GRANTED', {
81
+ server: input.server,
82
+ capability: 'tools',
83
+ action: 'call',
84
+ });
85
+ }
86
+ const phaseAllowed = grant
87
+ ? input.phase !== undefined && grant.phases.includes(input.phase)
88
+ : matchesPhase(genericGrant?.phase, input.phase);
89
+ if (!phaseAllowed) {
90
+ return deny('MCP_TOOL_PHASE_DENIED', {
91
+ grant: matchedGrant,
92
+ server: input.server,
93
+ capability: 'tools',
94
+ action: 'call',
95
+ phase: input.phase,
96
+ });
97
+ }
98
+ const hasWriteRisk = input.classification?.riskLevel === 'high' ||
99
+ input.classification?.sideEffects.some((effect) => ['fs_write', 'git_write', 'process', 'network'].includes(effect));
100
+ const risk = input.remote
101
+ ? raiseRisk(input.classification?.riskLevel ?? 'medium')
102
+ : (input.classification?.riskLevel ?? 'medium');
103
+ const outcome = grant
104
+ ? legacyToolOutcome(grant.approval, Boolean(hasWriteRisk))
105
+ : (genericGrant?.outcome ??
106
+ decideMcpApprovalPolicy({ capability: 'tools', risk, remote: input.remote }).outcome);
107
+ return allowOrAsk(outcome, matchedGrant.reason ?? 'MCP_TOOL_GRANTED', {
108
+ grant: matchedGrant,
109
+ risk,
110
+ server: input.server,
111
+ capability: 'tools',
112
+ action: 'call',
113
+ phase: input.phase,
114
+ });
115
+ }
116
+ decideResource(input) {
117
+ const grant = this.grants.find((item) => 'kind' in item &&
118
+ item.kind === 'resource' &&
119
+ matchesServer(item.server, input.server) &&
120
+ this.uriPolicy.isAllowed(input.uri, [item.uriPattern]));
121
+ const genericGrant = grant ?? this.findGenericGrant('resources', input.server, 'read', input.uri);
122
+ if (!genericGrant) {
123
+ return deny('MCP_RESOURCE_URI_DENIED', {
124
+ server: input.server,
125
+ capability: 'resources',
126
+ action: 'read',
127
+ });
128
+ }
129
+ if (!('kind' in genericGrant)) {
130
+ const rules = Array.isArray(genericGrant.target)
131
+ ? genericGrant.target
132
+ : genericGrant.target
133
+ ? [genericGrant.target]
134
+ : [];
135
+ const uriDecision = evaluateMcpUriPolicy(input.uri, rules);
136
+ if (!uriDecision.allowed) {
137
+ return deny(uriDecision.reason, {
138
+ grant: genericGrant,
139
+ server: input.server,
140
+ capability: 'resources',
141
+ action: 'read',
142
+ });
143
+ }
144
+ }
145
+ return allowOrAsk('allow', genericGrant.reason ?? 'MCP_RESOURCE_GRANTED', {
146
+ grant: genericGrant,
147
+ risk: 'low',
148
+ server: input.server,
149
+ capability: 'resources',
150
+ action: 'read',
151
+ });
152
+ }
153
+ decidePrompt(input) {
154
+ const grant = this.grants.find((item) => 'kind' in item &&
155
+ item.kind === 'prompt' &&
156
+ matchesServer(item.server, input.server) &&
157
+ matchesMcpPattern(input.name, item.namePattern) &&
158
+ item.exposeAs !== 'none');
159
+ const genericGrant = grant ?? this.findGenericGrant('prompts', input.server, 'read', input.name);
160
+ return genericGrant
161
+ ? allowOrAsk('allow', genericGrant.reason ?? 'MCP_PROMPT_GRANTED', {
162
+ grant: genericGrant,
163
+ risk: 'low',
164
+ server: input.server,
165
+ capability: 'prompts',
166
+ action: 'read',
167
+ })
168
+ : deny('MCP_PROMPT_DENIED', {
169
+ server: input.server,
170
+ capability: 'prompts',
171
+ action: 'read',
172
+ });
173
+ }
174
+ decideRoots(server, target) {
175
+ const grant = this.grants.find((item) => 'kind' in item &&
176
+ item.kind === 'roots' &&
177
+ matchesServer(item.server, server) &&
178
+ item.mode !== 'none');
179
+ const genericGrant = grant ?? this.findGenericGrant('roots', server, 'read', target);
180
+ return genericGrant
181
+ ? allowOrAsk('allow', genericGrant.reason ?? 'MCP_ROOTS_GRANTED', {
182
+ grant: genericGrant,
183
+ risk: 'low',
184
+ server,
185
+ capability: 'roots',
186
+ action: 'read',
187
+ })
188
+ : deny('MCP_ROOTS_DENIED', { server, capability: 'roots', action: 'read' });
189
+ }
190
+ decideSampling(server, target) {
191
+ const grant = this.grants.find((item) => 'kind' in item &&
192
+ item.kind === 'sampling' &&
193
+ matchesServer(item.server, server) &&
194
+ item.enabled);
195
+ const genericGrant = grant ?? this.findGenericGrant('sampling', server, 'request', target);
196
+ if (!genericGrant) {
197
+ return deny('MCP_SAMPLING_DENIED', { server, capability: 'sampling', action: 'request' });
198
+ }
199
+ const outcome = 'kind' in genericGrant ? 'ask' : (genericGrant.outcome ?? 'deny');
200
+ return allowOrAsk(outcome, genericGrant.reason ?? 'MCP_SAMPLING_GRANTED', {
201
+ grant: genericGrant,
202
+ risk: 'high',
203
+ server,
204
+ capability: 'sampling',
205
+ action: 'request',
206
+ });
207
+ }
208
+ decideElicitation(server, target) {
209
+ const grant = this.grants.find((item) => 'kind' in item &&
210
+ item.kind === 'elicitation' &&
211
+ matchesServer(item.server, server) &&
212
+ item.enabled);
213
+ const genericGrant = grant ?? this.findGenericGrant('elicitation', server, 'request', target);
214
+ if (!genericGrant) {
215
+ return allowOrAsk('ask', 'MCP_ELICITATION_APPROVAL_REQUIRED_BY_DEFAULT', {
216
+ risk: 'medium',
217
+ server,
218
+ capability: 'elicitation',
219
+ action: 'request',
220
+ });
221
+ }
222
+ const outcome = 'kind' in genericGrant ? 'ask' : (genericGrant.outcome ?? 'ask');
223
+ return allowOrAsk(outcome, genericGrant.reason ?? 'MCP_ELICITATION_GRANTED', {
224
+ grant: genericGrant,
225
+ risk: 'medium',
226
+ server,
227
+ capability: 'elicitation',
228
+ action: 'request',
229
+ });
230
+ }
231
+ findGenericGrant(capability, server, action, target, phase) {
232
+ return this.grants.find((grant) => !('kind' in grant) &&
233
+ grant.capability === capability &&
234
+ matchesServer(grant.server, server) &&
235
+ matchesAction(grant.actions, action) &&
236
+ matchesGenericTarget(grant.target, target) &&
237
+ matchesPhase(grant.phase, phase));
238
+ }
239
+ }
240
+ export function buildMcpGrantsFromCapabilities(server, capabilities) {
241
+ const grants = [];
242
+ for (const namePattern of capabilities.tools.allow) {
243
+ grants.push({
244
+ kind: 'tool',
245
+ server,
246
+ namePattern,
247
+ phases: capabilities.tools.phases,
248
+ approval: capabilities.tools.approval,
249
+ });
250
+ }
251
+ for (const uriPattern of capabilities.resources.allowUris) {
252
+ grants.push({
253
+ kind: 'resource',
254
+ server,
255
+ uriPattern,
256
+ autoInclude: capabilities.resources.autoInclude,
257
+ });
258
+ }
259
+ for (const namePattern of capabilities.prompts.allow) {
260
+ grants.push({
261
+ kind: 'prompt',
262
+ server,
263
+ namePattern,
264
+ exposeAs: capabilities.prompts.exposeAs,
265
+ });
266
+ }
267
+ grants.push({ kind: 'roots', server, mode: capabilities.roots.mode });
268
+ grants.push({
269
+ kind: 'sampling',
270
+ server,
271
+ enabled: capabilities.sampling.enabled,
272
+ maxTokens: capabilities.sampling.maxTokens,
273
+ maxDepth: capabilities.sampling.maxDepth,
274
+ });
275
+ grants.push({ kind: 'elicitation', server, enabled: capabilities.elicitation.enabled });
276
+ return grants;
277
+ }
278
+ export function getCapabilityKindForGrant(grant) {
279
+ if ('capability' in grant)
280
+ return grant.capability;
281
+ if (grant.kind === 'tool')
282
+ return 'tools';
283
+ if (grant.kind === 'resource')
284
+ return 'resources';
285
+ if (grant.kind === 'prompt')
286
+ return 'prompts';
287
+ return grant.kind;
288
+ }
289
+ export function getGrantServer(grant) {
290
+ return grant.server;
291
+ }
292
+ export function grantOutcomeToApprovalMode(outcome) {
293
+ if (outcome === 'ask')
294
+ return 'ask';
295
+ return 'never';
296
+ }
297
+ function legacyToolOutcome(approval, hasWriteRisk) {
298
+ if (approval === 'ask')
299
+ return 'ask';
300
+ if (approval === 'write_requires_confirmation' && hasWriteRisk)
301
+ return 'ask';
302
+ return 'allow';
303
+ }
304
+ function allowOrAsk(outcome, reason, details) {
305
+ return {
306
+ ...details,
307
+ allowed: outcome === 'allow',
308
+ approvalRequired: outcome === 'ask',
309
+ needsApproval: outcome === 'ask',
310
+ outcome,
311
+ reason,
312
+ denyReason: outcome === 'deny' ? reason : undefined,
313
+ };
314
+ }
315
+ function deny(reason, details) {
316
+ return {
317
+ ...details,
318
+ allowed: false,
319
+ approvalRequired: false,
320
+ needsApproval: false,
321
+ outcome: 'deny',
322
+ reason,
323
+ denyReason: reason,
324
+ };
325
+ }
326
+ function matchesServer(pattern, server) {
327
+ return pattern === '*' || pattern === server;
328
+ }
329
+ function matchesAction(actions, action) {
330
+ return !actions?.length || actions.includes('*') || actions.includes(action);
331
+ }
332
+ function matchesPhase(grantPhase, requestPhase) {
333
+ if (!grantPhase)
334
+ return true;
335
+ if (!requestPhase)
336
+ return false;
337
+ return Array.isArray(grantPhase)
338
+ ? grantPhase.includes(requestPhase)
339
+ : grantPhase === requestPhase;
340
+ }
341
+ function matchesGenericTarget(target, requestedTarget) {
342
+ if (!target)
343
+ return true;
344
+ if (!requestedTarget)
345
+ return false;
346
+ const targets = Array.isArray(target) ? target : [target];
347
+ return targets.some((item) => typeof item === 'string'
348
+ ? matchesMcpPattern(requestedTarget, item)
349
+ : matchesMcpPattern(requestedTarget, item.pattern));
350
+ }
351
+ function raiseRisk(risk) {
352
+ if (risk === 'low')
353
+ return 'medium';
354
+ return 'high';
355
+ }
356
+ //# sourceMappingURL=grants.js.map
@@ -0,0 +1,60 @@
1
+ export function matchesMcpPattern(value, pattern) {
2
+ return matchesUriRule(value, pattern);
3
+ }
4
+ export function evaluateMcpUriPolicy(uri, rules) {
5
+ if (rules.length === 0) {
6
+ return {
7
+ allowed: false,
8
+ reason: 'MCP_RESOURCE_URI_DENIED_NO_RULES',
9
+ };
10
+ }
11
+ for (const rule of rules) {
12
+ const normalized = normalizeUriRule(rule);
13
+ if (matchesUriRule(uri, normalized)) {
14
+ return {
15
+ allowed: true,
16
+ reason: `MCP_RESOURCE_URI_ALLOWED_${normalized.kind.toUpperCase()}`,
17
+ matchedPattern: normalized.pattern,
18
+ matchKind: normalized.kind,
19
+ };
20
+ }
21
+ }
22
+ return {
23
+ allowed: false,
24
+ reason: 'MCP_RESOURCE_URI_DENIED_NO_MATCH',
25
+ };
26
+ }
27
+ export function matchesUriRule(uri, rule) {
28
+ const normalized = normalizeUriRule(rule);
29
+ if (normalized.kind === 'exact')
30
+ return uri === normalized.pattern;
31
+ if (normalized.kind === 'prefix')
32
+ return uri.startsWith(normalized.pattern);
33
+ return globLikeToRegExp(normalized.pattern).test(uri);
34
+ }
35
+ export function normalizeUriRule(rule) {
36
+ if (typeof rule === 'string') {
37
+ return { pattern: rule, kind: inferUriRuleKind(rule) };
38
+ }
39
+ return { pattern: rule.pattern, kind: rule.kind ?? inferUriRuleKind(rule.pattern) };
40
+ }
41
+ export class McpUriPolicy {
42
+ isAllowed(uri, patterns) {
43
+ return evaluateMcpUriPolicy(uri, patterns).allowed;
44
+ }
45
+ decide(uri, patterns) {
46
+ return evaluateMcpUriPolicy(uri, patterns);
47
+ }
48
+ }
49
+ function inferUriRuleKind(pattern) {
50
+ if (pattern.includes('*'))
51
+ return 'glob';
52
+ if (pattern.endsWith('/'))
53
+ return 'prefix';
54
+ return 'exact';
55
+ }
56
+ function globLikeToRegExp(pattern) {
57
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
58
+ return new RegExp(`^${escaped}$`);
59
+ }
60
+ //# sourceMappingURL=uri-policy.js.map