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.
- package/dist/cli/authorization/non-interactive.js +7 -21
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/parallel.js +46 -41
- package/dist/cli/commands/run/assistant-message.js +3 -0
- package/dist/cli/commands/run/handler.js +2 -1
- package/dist/cli/commands/serve.js +112 -156
- package/dist/cli/headless/json-protocol.js +1 -1
- package/dist/cli/headless/stream-json-protocol.js +3 -2
- package/dist/cli/program-bootstrap.js +2 -2
- package/dist/cli/slash/runtime.js +5 -1
- package/dist/core/adapters/fs/node-fs.js +1 -0
- package/dist/core/backends/salmon-loop/task-executor.js +1 -0
- package/dist/core/benchmark/patch-artifact.js +1 -1
- package/dist/core/context/service.js +5 -2
- package/dist/core/extensions/index.js +2 -35
- package/dist/core/extensions/merge.js +14 -0
- package/dist/core/extensions/redact.js +9 -3
- package/dist/core/extensions/schemas.js +2 -51
- package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
- package/dist/core/facades/cli-program-bootstrap.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -1
- package/dist/core/grizzco/dsl/strategies.js +1 -3
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
- package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
- package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
- package/dist/core/grizzco/steps/apply.js +0 -7
- package/dist/core/grizzco/steps/autopilot.js +108 -6
- package/dist/core/grizzco/steps/preflight.js +10 -0
- package/dist/core/grizzco/steps/tool-runtime.js +1 -0
- package/dist/core/interaction/events/bus.js +14 -0
- package/dist/core/interaction/orchestration/facade.js +11 -1
- package/dist/core/llm/ai-sdk/request-params.js +40 -1
- package/dist/core/mcp/bridge/index.js +4 -0
- package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
- package/dist/core/mcp/bridge/tool-bridge.js +303 -0
- package/dist/core/mcp/cache/resource-cache.js +41 -0
- package/dist/core/mcp/catalog/discovery.js +51 -0
- package/dist/core/mcp/catalog/notification-router.js +28 -0
- package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
- package/dist/core/mcp/catalog/resource-catalog.js +7 -0
- package/dist/core/mcp/catalog/tool-catalog.js +4 -0
- package/dist/core/mcp/client/connection-manager.js +239 -0
- package/dist/core/mcp/client/lifecycle.js +13 -0
- package/dist/core/mcp/client/transport-factory.js +168 -0
- package/dist/core/mcp/config/index.js +32 -0
- package/dist/core/mcp/config/schema-v2.js +129 -0
- package/dist/core/mcp/host/elicitation-provider.js +209 -0
- package/dist/core/mcp/host/roots-provider.js +70 -0
- package/dist/core/mcp/host/sampling-provider.js +170 -0
- package/dist/core/mcp/index.js +4 -0
- package/dist/core/mcp/observability/events.js +19 -0
- package/dist/core/mcp/policy/approval-policy.js +2 -0
- package/dist/core/mcp/policy/classifier.js +172 -0
- package/dist/core/mcp/policy/grants.js +356 -0
- package/dist/core/mcp/policy/uri-policy.js +60 -0
- package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
- package/dist/core/mcp/types.js +2 -0
- package/dist/core/protocols/a2a/agent-card.js +38 -12
- package/dist/core/protocols/a2a/sdk/executor.js +105 -36
- package/dist/core/protocols/a2a/sdk/server.js +1311 -3
- package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
- package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
- package/dist/core/protocols/acp/acp-types.js +17 -0
- package/dist/core/protocols/acp/formal-agent.js +389 -502
- package/dist/core/protocols/acp/handlers.js +3 -0
- package/dist/core/protocols/acp/permission-provider.js +11 -39
- package/dist/core/protocols/acp/stdio-server.js +20 -1
- package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
- package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
- package/dist/core/public-capabilities/projections.js +1 -0
- package/dist/core/runtime/agent-server-runtime.js +2 -3
- package/dist/core/runtime/spawn-command.js +8 -2
- package/dist/core/runtime/spawn-interactive.js +26 -0
- package/dist/core/session/manager.js +48 -25
- package/dist/core/tools/builtin/index.js +6 -1
- package/dist/core/tools/builtin/proposal.js +0 -7
- package/dist/core/tools/builtin/workspace.js +76 -0
- package/dist/core/tools/dispatcher.js +1 -0
- package/dist/core/tools/loader.js +92 -46
- package/dist/core/verification/runner.js +60 -31
- package/dist/core/version.js +17 -0
- package/dist/core/workspace/capabilities.js +80 -0
- package/dist/locales/en.js +17 -3
- package/package.json +4 -2
- package/dist/core/protocols/a2a/mapper.js +0 -14
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
- package/dist/core/protocols/a2a/task-projection.js +0 -45
- package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
- package/dist/core/tools/mcp/client.js +0 -308
- package/dist/core/tools/mcp/loader.js +0 -110
- package/dist/core/tools/mcp/schema.js +0 -54
- package/dist/core/tools/mcp/streamable-http.js +0 -101
- package/dist/core/tools/mcp/types.js +0 -26
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { text } from '../../locales/index.js';
|
|
2
|
+
import { registerMcpV2Tools } from '../mcp/bridge/tool-bridge.js';
|
|
3
|
+
import { McpConnectionManager } from '../mcp/client/connection-manager.js';
|
|
4
|
+
import { buildMcpGrantsFromCapabilities, McpPolicyEngine } from '../mcp/policy/grants.js';
|
|
2
5
|
import { skillToToolSpec } from '../skills/bridge.js';
|
|
3
6
|
import { SkillLoader } from '../skills/loader.js';
|
|
4
7
|
import { ToolAuditLogger } from './audit.js';
|
|
5
8
|
import { BudgetGuard } from './budget.js';
|
|
6
9
|
import { registerAllBuiltins } from './builtin/index.js';
|
|
7
10
|
import { ToolDispatcher } from './dispatcher.js';
|
|
8
|
-
import { registerMcpTools } from './mcp/loader.js';
|
|
9
11
|
import { compilePermissionRules, getVisibleToolNamesFromAllowRules, shouldFilterRegistryByAllowRules, } from './permissions/permission-rules.js';
|
|
10
12
|
import { registerPluginTools } from './plugins/loader.js';
|
|
11
13
|
import { ToolPolicy } from './policy.js';
|
|
@@ -54,6 +56,7 @@ export async function createStandardToolstack(options) {
|
|
|
54
56
|
// e. Create ToolRouter with filtered registry
|
|
55
57
|
// f. Fill RouterBox.router — skill executors now have a valid reference
|
|
56
58
|
const routerBox = { router: null };
|
|
59
|
+
let mcpManager;
|
|
57
60
|
// 3a. Load skill catalog (Tier 1: lightweight metadata only) and register
|
|
58
61
|
// bridge tool specs with lazy activation. Full skill content is loaded
|
|
59
62
|
// on demand via SkillLoader.activateSkill() (Tier 2) when the executor
|
|
@@ -69,55 +72,98 @@ export async function createStandardToolstack(options) {
|
|
|
69
72
|
registry.register(skillToToolSpec({ entry, loader: skillLoader }, routerBox));
|
|
70
73
|
}
|
|
71
74
|
// 3b. Register MCP + plugin tools
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
try {
|
|
76
|
+
if (extensions) {
|
|
77
|
+
if (extensions.mcpServers.length > 0) {
|
|
78
|
+
mcpManager = new McpConnectionManager(extensions.mcpServers, {
|
|
79
|
+
roots: extensions.mcpServers.some((server) => server.capabilities.roots.mode !== 'none'),
|
|
80
|
+
sampling: extensions.mcpServers.some((server) => server.capabilities.sampling.enabled),
|
|
81
|
+
elicitation: extensions.mcpServers.some((server) => server.capabilities.elicitation.enabled),
|
|
82
|
+
});
|
|
83
|
+
const mcpPolicy = new McpPolicyEngine(extensions.mcpServers.flatMap((server) => buildMcpGrantsFromCapabilities(server.name, server.capabilities)));
|
|
84
|
+
await registerMcpV2Tools({
|
|
85
|
+
registry,
|
|
86
|
+
servers: extensions.mcpServers,
|
|
87
|
+
manager: mcpManager,
|
|
88
|
+
policy: mcpPolicy,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
await registerPluginTools(registry, extensions.toolPlugins);
|
|
92
|
+
}
|
|
93
|
+
// 3c. Apply allowlist / permission-rule filtering — skills are now included
|
|
94
|
+
const allowSets = [];
|
|
95
|
+
if (Array.isArray(options.allowedToolNames) && options.allowedToolNames.length > 0) {
|
|
96
|
+
allowSets.push(new Set(options.allowedToolNames));
|
|
97
|
+
}
|
|
98
|
+
if (shouldFilterRegistryByAllowRules(compiledPermissionRules)) {
|
|
99
|
+
allowSets.push(getVisibleToolNamesFromAllowRules(compiledPermissionRules));
|
|
100
|
+
}
|
|
101
|
+
if (allowSets.length > 0) {
|
|
102
|
+
const intersect = new Set(allowSets[0]);
|
|
103
|
+
for (const next of allowSets.slice(1)) {
|
|
104
|
+
for (const name of Array.from(intersect)) {
|
|
105
|
+
if (!next.has(name))
|
|
106
|
+
intersect.delete(name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const filtered = new ToolRegistry();
|
|
110
|
+
for (const name of intersect) {
|
|
111
|
+
const spec = registry.getSpec(name);
|
|
112
|
+
if (spec)
|
|
113
|
+
filtered.register(spec);
|
|
90
114
|
}
|
|
115
|
+
registry = filtered;
|
|
91
116
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
117
|
+
if (options.workspaceCapabilities) {
|
|
118
|
+
const capabilities = options.workspaceCapabilities;
|
|
119
|
+
const filtered = new ToolRegistry();
|
|
120
|
+
for (const spec of registry.listAll()) {
|
|
121
|
+
const needsGit = spec.name.startsWith('git.') ||
|
|
122
|
+
spec.sideEffects.includes('git_read') ||
|
|
123
|
+
spec.sideEffects.includes('git_write');
|
|
124
|
+
const needsReadableFilesystem = spec.sideEffects.includes('fs_read');
|
|
125
|
+
const needsWritableFilesystem = spec.sideEffects.includes('fs_write');
|
|
126
|
+
if (needsGit && !capabilities.git.insideWorkTree)
|
|
127
|
+
continue;
|
|
128
|
+
if (needsReadableFilesystem && !capabilities.filesystem.readable)
|
|
129
|
+
continue;
|
|
130
|
+
if (needsWritableFilesystem && !capabilities.filesystem.writable)
|
|
131
|
+
continue;
|
|
96
132
|
filtered.register(spec);
|
|
133
|
+
}
|
|
134
|
+
registry = filtered;
|
|
97
135
|
}
|
|
98
|
-
registry
|
|
136
|
+
// 3d. Create Router with the FINAL (filtered) registry — skills included
|
|
137
|
+
const router = new ToolRouter(registry, policy, budget, audit, sanitize, options.authorizationProvider, { authorizationMode: options.authorizationMode, permissionRules: compiledPermissionRules });
|
|
138
|
+
// 3e. Fill the RouterBox — skill executors can now resolve the router
|
|
139
|
+
routerBox.router = router;
|
|
140
|
+
// 4. Create Dispatcher (The high-level coordinator for LLM text)
|
|
141
|
+
const dispatcher = new ToolDispatcher(router, {
|
|
142
|
+
repoRoot: options.repoRoot,
|
|
143
|
+
persistenceRoot: options.persistenceRoot,
|
|
144
|
+
worktreeRoot: options.worktreeRoot,
|
|
145
|
+
workspaceCapabilities: options.workspaceCapabilities,
|
|
146
|
+
attemptId: options.attemptId,
|
|
147
|
+
dryRun: options.dryRun,
|
|
148
|
+
model: options.model,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
registry,
|
|
152
|
+
router,
|
|
153
|
+
dispatcher,
|
|
154
|
+
budget,
|
|
155
|
+
audit,
|
|
156
|
+
policy,
|
|
157
|
+
sanitize,
|
|
158
|
+
mcp: mcpManager,
|
|
159
|
+
async dispose() {
|
|
160
|
+
await mcpManager?.stopAll();
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
await mcpManager?.stopAll();
|
|
166
|
+
throw error;
|
|
99
167
|
}
|
|
100
|
-
// 3d. Create Router with the FINAL (filtered) registry — skills included
|
|
101
|
-
const router = new ToolRouter(registry, policy, budget, audit, sanitize, options.authorizationProvider, { authorizationMode: options.authorizationMode, permissionRules: compiledPermissionRules });
|
|
102
|
-
// 3e. Fill the RouterBox — skill executors can now resolve the router
|
|
103
|
-
routerBox.router = router;
|
|
104
|
-
// 4. Create Dispatcher (The high-level coordinator for LLM text)
|
|
105
|
-
const dispatcher = new ToolDispatcher(router, {
|
|
106
|
-
repoRoot: options.repoRoot,
|
|
107
|
-
persistenceRoot: options.persistenceRoot,
|
|
108
|
-
worktreeRoot: options.worktreeRoot,
|
|
109
|
-
attemptId: options.attemptId,
|
|
110
|
-
dryRun: options.dryRun,
|
|
111
|
-
model: options.model,
|
|
112
|
-
});
|
|
113
|
-
return {
|
|
114
|
-
registry,
|
|
115
|
-
router,
|
|
116
|
-
dispatcher,
|
|
117
|
-
budget,
|
|
118
|
-
audit,
|
|
119
|
-
policy,
|
|
120
|
-
sanitize,
|
|
121
|
-
};
|
|
122
168
|
}
|
|
123
169
|
//# sourceMappingURL=loader.js.map
|
|
@@ -8,6 +8,7 @@ import { tryGetPluginRegistry } from '../plugin/registry.js';
|
|
|
8
8
|
import { isCommandAvailable, spawnCommand } from '../runtime/process-runner.js';
|
|
9
9
|
import { ErrorType } from '../types/index.js';
|
|
10
10
|
import { getPlatformShellInvocation } from '../utils/platform-shell.js';
|
|
11
|
+
import { detectWorkspaceCapabilities } from '../workspace/capabilities.js';
|
|
11
12
|
/**
|
|
12
13
|
* Classify the error type based on the output of the verification command
|
|
13
14
|
*/
|
|
@@ -186,38 +187,65 @@ export async function verifyFileContent(repoPath, filePath, expected, onEvent) {
|
|
|
186
187
|
}
|
|
187
188
|
export async function preflight(workspace, onEvent, options) {
|
|
188
189
|
const now = () => new Date();
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
const requireGit = options?.requireGit !== false;
|
|
191
|
+
const requireWrite = options?.requireWrite !== false;
|
|
192
|
+
const workspacePath = workspace.workPath || workspace.baseRepoPath;
|
|
193
|
+
const capabilities = workspace.capabilities ?? (await detectWorkspaceCapabilities(workspacePath));
|
|
194
|
+
if (!capabilities.filesystem.readable) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
reason: capabilities.filesystem.reason || 'Workspace is not readable',
|
|
198
|
+
reasonCode: 'LOOP_FAILED',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (requireWrite && !capabilities.filesystem.writable) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
reason: capabilities.filesystem.reason || 'Workspace is not writable',
|
|
205
|
+
reasonCode: 'LOOP_FAILED',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (!capabilities.git.insideWorkTree) {
|
|
209
|
+
if (!requireGit) {
|
|
210
|
+
onEvent?.({
|
|
211
|
+
type: 'resource.status',
|
|
212
|
+
resource: 'git',
|
|
213
|
+
status: 'skipped',
|
|
214
|
+
message: capabilities.git.reason || text.loop.preflightFailedNotGit,
|
|
215
|
+
timestamp: now(),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else if (!capabilities.git.available) {
|
|
198
219
|
return { ok: false, reason: text.loop.gitNotFound, reasonCode: 'PREFLIGHT_NOT_GIT' };
|
|
199
220
|
}
|
|
200
|
-
|
|
221
|
+
else {
|
|
201
222
|
return {
|
|
202
223
|
ok: false,
|
|
203
|
-
reason:
|
|
224
|
+
reason: capabilities.git.reason
|
|
225
|
+
? text.loop.preflightGitCheckFailed(capabilities.git.reason)
|
|
226
|
+
: text.loop.preflightFailedNotGit,
|
|
204
227
|
reasonCode: 'PREFLIGHT_NOT_GIT',
|
|
205
228
|
};
|
|
206
229
|
}
|
|
207
|
-
return { ok: false, reason: text.loop.preflightFailedNotGit, reasonCode: 'PREFLIGHT_NOT_GIT' };
|
|
208
230
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
if (!capabilities.git.insideWorkTree) {
|
|
232
|
+
if (!(await isCommandAvailable('rg'))) {
|
|
233
|
+
onEvent?.({
|
|
234
|
+
type: 'resource.status',
|
|
235
|
+
resource: 'ripgrep',
|
|
236
|
+
status: 'warning',
|
|
237
|
+
message: text.verify.ripgrepNotFoundWarning,
|
|
238
|
+
timestamp: now(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return { ok: true, capabilities };
|
|
215
242
|
}
|
|
216
|
-
|
|
243
|
+
const git = new GitAdapter(workspacePath);
|
|
244
|
+
// Check if workspace is dirty (only for direct strategy)
|
|
217
245
|
// Allow dirty workspace by default for worktree strategy
|
|
218
246
|
if (workspace.strategy === 'direct' && !options?.ignoreDirty) {
|
|
219
247
|
const statusCheck = await git.execMeta(['status', '--porcelain'], {
|
|
220
|
-
cwd:
|
|
248
|
+
cwd: workspacePath,
|
|
221
249
|
limits: { maxStdoutBytes: 64_000, maxStderrChars: 4_096 },
|
|
222
250
|
timeoutMs: LIMITS.gitTimeoutMs,
|
|
223
251
|
});
|
|
@@ -243,17 +271,18 @@ export async function preflight(workspace, onEvent, options) {
|
|
|
243
271
|
reasonCode: 'PREFLIGHT_DIRTY',
|
|
244
272
|
};
|
|
245
273
|
}
|
|
246
|
-
return { ok: true };
|
|
274
|
+
return { ok: true, capabilities };
|
|
247
275
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
if (workspace.strategy !== 'direct' || options?.ignoreDirty) {
|
|
277
|
+
onEvent?.({
|
|
278
|
+
type: 'resource.status',
|
|
279
|
+
resource: 'git',
|
|
280
|
+
status: 'skipped',
|
|
281
|
+
message: text.verify.worktreeStrategyActive,
|
|
282
|
+
timestamp: now(),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// Check if ripgrep is installed (optional but recommended)
|
|
257
286
|
if (!(await isCommandAvailable('rg'))) {
|
|
258
287
|
onEvent?.({
|
|
259
288
|
type: 'resource.status',
|
|
@@ -263,6 +292,6 @@ export async function preflight(workspace, onEvent, options) {
|
|
|
263
292
|
timestamp: now(),
|
|
264
293
|
});
|
|
265
294
|
}
|
|
266
|
-
return { ok: true };
|
|
295
|
+
return { ok: true, capabilities };
|
|
267
296
|
}
|
|
268
297
|
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
function readPackageVersion() {
|
|
4
|
+
try {
|
|
5
|
+
const pkg = require('../../package.json');
|
|
6
|
+
if (typeof pkg.version === 'string' && pkg.version.trim()) {
|
|
7
|
+
return pkg.version;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Fall back for non-package runtime embeddings.
|
|
12
|
+
}
|
|
13
|
+
return '0.0.0';
|
|
14
|
+
}
|
|
15
|
+
export const PACKAGE_NAME = 'salmon-loop';
|
|
16
|
+
export const PACKAGE_VERSION = readPackageVersion();
|
|
17
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { access, constants } from '../adapters/fs/node-fs.js';
|
|
2
|
+
import { GitAdapter } from '../adapters/git/git-adapter.js';
|
|
3
|
+
import { LIMITS } from '../config/limits.js';
|
|
4
|
+
const PROBE_LIMITS = { maxStdoutBytes: 4_096, maxStderrChars: 4_096 };
|
|
5
|
+
function gitFailureReason(result) {
|
|
6
|
+
if (result.error?.code === 'ENOENT')
|
|
7
|
+
return 'git executable not found';
|
|
8
|
+
if (result.error?.message)
|
|
9
|
+
return result.error.message;
|
|
10
|
+
if (result.stdoutTruncated)
|
|
11
|
+
return `git output exceeded ${PROBE_LIMITS.maxStdoutBytes} bytes`;
|
|
12
|
+
const stderr = result.stderr?.trim();
|
|
13
|
+
const stdout = result.stdout?.toString('utf8').trim();
|
|
14
|
+
if (stdout)
|
|
15
|
+
return `git reported --is-inside-work-tree=${stdout}`;
|
|
16
|
+
return stderr || 'not a git work tree';
|
|
17
|
+
}
|
|
18
|
+
async function detectFileSystemCapability(workspacePath) {
|
|
19
|
+
try {
|
|
20
|
+
await access(workspacePath, constants.R_OK);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
readable: false,
|
|
25
|
+
writable: false,
|
|
26
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await access(workspacePath, constants.W_OK);
|
|
31
|
+
return { readable: true, writable: true };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
readable: true,
|
|
36
|
+
writable: false,
|
|
37
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function detectWorkspaceCapabilities(workspacePath) {
|
|
42
|
+
const git = new GitAdapter(workspacePath);
|
|
43
|
+
const gitCheck = await git.execMeta(['rev-parse', '--is-inside-work-tree'], {
|
|
44
|
+
cwd: workspacePath,
|
|
45
|
+
limits: PROBE_LIMITS,
|
|
46
|
+
timeoutMs: LIMITS.gitTimeoutMs,
|
|
47
|
+
});
|
|
48
|
+
let gitCapability;
|
|
49
|
+
const insideWorkTree = gitCheck.ok ? gitCheck.stdout.toString('utf8').trim() === 'true' : false;
|
|
50
|
+
if (!gitCheck.ok || gitCheck.stdoutTruncated || !insideWorkTree) {
|
|
51
|
+
gitCapability = {
|
|
52
|
+
available: gitCheck.error?.code !== 'ENOENT',
|
|
53
|
+
insideWorkTree: false,
|
|
54
|
+
reason: gitFailureReason(gitCheck),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const headResult = await git.execMeta(['rev-parse', '--verify', 'HEAD'], {
|
|
59
|
+
cwd: workspacePath,
|
|
60
|
+
limits: PROBE_LIMITS,
|
|
61
|
+
timeoutMs: LIMITS.gitTimeoutMs,
|
|
62
|
+
});
|
|
63
|
+
gitCapability = {
|
|
64
|
+
available: true,
|
|
65
|
+
insideWorkTree: true,
|
|
66
|
+
head: headResult.ok ? headResult.stdout.toString('utf8').trim() : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
git: gitCapability,
|
|
71
|
+
filesystem: await detectFileSystemCapability(workspacePath),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function requiresGitWorkspace(params) {
|
|
75
|
+
if (params.strategy === 'worktree' || params.strategy === 'tempCommit') {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return params.mode !== 'autopilot';
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=capabilities.js.map
|
package/dist/locales/en.js
CHANGED
|
@@ -141,7 +141,6 @@ export const en = {
|
|
|
141
141
|
acp: {
|
|
142
142
|
slashHelpDescription: 'Show available ACP slash commands',
|
|
143
143
|
slashHelpResponse: (commands) => `Available commands: ${commands}`,
|
|
144
|
-
slashUnknownCommand: (commandName) => `Unknown command: /${commandName}`,
|
|
145
144
|
askUserHeader: 'User input required',
|
|
146
145
|
askUserQuestion: (question) => `Question: ${question}`,
|
|
147
146
|
askUserOptionsHeader: 'Options:',
|
|
@@ -155,8 +154,23 @@ export const en = {
|
|
|
155
154
|
permissionPolicyDenyAllDescription: 'Automatically deny side-effecting operations.',
|
|
156
155
|
permissionPolicyAllowAllName: 'Allow all',
|
|
157
156
|
permissionPolicyAllowAllDescription: 'Automatically allow side-effecting operations.',
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
executionFlowName: 'Execution Flow',
|
|
158
|
+
executionFlowDescription: 'Choose how the agent should execute this session.',
|
|
159
|
+
permissionOptionAllowOnce: 'Allow once',
|
|
160
|
+
permissionOptionAllowSession: 'Allow for session',
|
|
161
|
+
permissionOptionRejectOnce: 'Reject once',
|
|
162
|
+
permissionOptionRejectSession: 'Reject for session',
|
|
163
|
+
taskCancelled: 'Task cancelled.',
|
|
164
|
+
taskCompleted: 'Task completed.',
|
|
165
|
+
taskFailed: 'Task failed.',
|
|
166
|
+
taskFailedWithReason: (reason) => `Task failed: ${reason}`,
|
|
167
|
+
taskAwaitingInput: 'Task awaiting input.',
|
|
168
|
+
checkpointNotFound: 'Checkpoint not found. Start a new session.',
|
|
169
|
+
checkpointManifestParseError: 'Checkpoint metadata is corrupted. Recreate checkpoint metadata and retry.',
|
|
170
|
+
checkpointManifestIoError: 'Checkpoint metadata is unreadable due to filesystem I/O issues.',
|
|
171
|
+
checkpointManifestLockTimeout: 'Checkpoint metadata is busy (lock timeout). Retry shortly.',
|
|
172
|
+
checkpointManifestUnavailable: 'Checkpoint metadata is unavailable in current runtime.',
|
|
173
|
+
checkpointResumeUnavailable: 'Checkpoint resume is unavailable. Start a new session or retry.',
|
|
160
174
|
},
|
|
161
175
|
prompts: {
|
|
162
176
|
definitionHint: 'Definitions should be modified with extreme caution',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salmon-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A chat-first coding agent CLI for safe, reviewable repository changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:runtime-boundary": "bun test --preload ./tests/setup-bun.ts tests/unit/scripts/target-runtime-boundary.test.ts tests/unit/scripts/bun-purity.test.ts",
|
|
38
38
|
"test:worktree-smoke": "bun scripts/worktree-smoke.js",
|
|
39
39
|
"test:headless-smoke": "bun scripts/headless-smoke.ts",
|
|
40
|
+
"smoke:swebench": "bun scripts/swebench-smoke.ts",
|
|
40
41
|
"test:contract-smoke": "bun test --timeout 15000 --preload ./tests/setup-bun.ts tests/unit/architecture/request-assembly-invariant.test.ts tests/unit/architecture/replacement-preview-boundary-invariant.test.ts tests/unit/architecture/sub-agent-prefix-consistency-invariant.test.ts tests/unit/architecture/tool-naming-contract.test.ts tests/unit/architecture/verify-contract-smoke-gate.test.ts tests/unit/tools/session-streaming.test.ts tests/unit/core/grizzco/steps/plan-patch-toolcalling.test.ts tests/unit/core/session/replacement-state.test.ts",
|
|
41
42
|
"setup:hooks": "git config core.hooksPath .githooks",
|
|
42
43
|
"postinstall": "node scripts/fix-es-abstract-compat.js",
|
|
@@ -126,10 +127,11 @@
|
|
|
126
127
|
},
|
|
127
128
|
"dependencies": {
|
|
128
129
|
"@a2a-js/sdk": "0.3.10",
|
|
129
|
-
"@agentclientprotocol/sdk": "
|
|
130
|
+
"@agentclientprotocol/sdk": "0.22.1",
|
|
130
131
|
"@ai-sdk/openai": "^3.0.23",
|
|
131
132
|
"@ai-sdk/openai-compatible": "^2.0.24",
|
|
132
133
|
"@inquirer/prompts": "^8.2.0",
|
|
134
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
133
135
|
"ai": "^6.0.62",
|
|
134
136
|
"ajv": "^8.18.0",
|
|
135
137
|
"chalk": "^5.4.1",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function mapA2ATaskToCanonicalTask(input) {
|
|
2
|
-
const instruction = input.message.parts
|
|
3
|
-
.filter((part) => part.type === 'text' && typeof part.text === 'string')
|
|
4
|
-
.map((part) => part.text)
|
|
5
|
-
.join('\n');
|
|
6
|
-
return {
|
|
7
|
-
id: input.id,
|
|
8
|
-
capability: 'patch',
|
|
9
|
-
state: 'accepted',
|
|
10
|
-
request: { instruction },
|
|
11
|
-
createdAt: new Date().toISOString(),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=mapper.js.map
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { UserBuilder } from '@a2a-js/sdk/server/express';
|
|
2
|
-
/**
|
|
3
|
-
* Converts A2AAuthPolicyMiddleware to Express middleware
|
|
4
|
-
* Handles authentication failures by returning 401 Unauthorized
|
|
5
|
-
*/
|
|
6
|
-
export function createAuthMiddlewareFromPolicy(_authPolicy) {
|
|
7
|
-
return async (req, res, next) => {
|
|
8
|
-
try {
|
|
9
|
-
// Note: A2AAuthPolicyMiddleware expects Fetch API Request, but Express provides its own Request
|
|
10
|
-
// For now, we skip the policy check and just pass through
|
|
11
|
-
// In a real implementation, you would need to adapt the Express Request to Fetch API Request
|
|
12
|
-
next();
|
|
13
|
-
}
|
|
14
|
-
catch (_error) {
|
|
15
|
-
res.status(500).json({ error: 'Authentication failed' });
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Creates a UserBuilder from Express request with auth context
|
|
21
|
-
*/
|
|
22
|
-
export function createUserBuilderFromAuthContext() {
|
|
23
|
-
return (req) => {
|
|
24
|
-
const authContext = req.authContext;
|
|
25
|
-
if (authContext) {
|
|
26
|
-
return UserBuilder.noAuthentication();
|
|
27
|
-
}
|
|
28
|
-
return UserBuilder.noAuthentication();
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=auth-middleware.js.map
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
function projectTaskState(state) {
|
|
2
|
-
if (state === 'accepted')
|
|
3
|
-
return 'submitted';
|
|
4
|
-
if (state === 'running' || state === 'streaming')
|
|
5
|
-
return 'working';
|
|
6
|
-
if (state === 'awaiting_input')
|
|
7
|
-
return 'input-required';
|
|
8
|
-
if (state === 'completed')
|
|
9
|
-
return 'completed';
|
|
10
|
-
if (state === 'failed')
|
|
11
|
-
return 'failed';
|
|
12
|
-
if (state === 'cancelled')
|
|
13
|
-
return 'canceled';
|
|
14
|
-
return 'working';
|
|
15
|
-
}
|
|
16
|
-
export function projectCanonicalTaskToA2ATask(task) {
|
|
17
|
-
return {
|
|
18
|
-
id: task.id,
|
|
19
|
-
state: task.state,
|
|
20
|
-
status: {
|
|
21
|
-
state: projectTaskState(task.state),
|
|
22
|
-
timestamp: task.createdAt ?? new Date().toISOString(),
|
|
23
|
-
message: task.statusMessage,
|
|
24
|
-
},
|
|
25
|
-
failure: task.failure,
|
|
26
|
-
requiredAction: task.inputRequired,
|
|
27
|
-
artifacts: (task.artifacts ?? []).map((artifact) => ({
|
|
28
|
-
artifactId: artifact.id,
|
|
29
|
-
name: artifact.name,
|
|
30
|
-
kind: artifact.kind,
|
|
31
|
-
mimeType: artifact.mimeType,
|
|
32
|
-
content: artifact.content,
|
|
33
|
-
delivery: artifact.delivery,
|
|
34
|
-
handle: artifact.handle,
|
|
35
|
-
url: artifact.url,
|
|
36
|
-
expiresAt: artifact.expiresAt,
|
|
37
|
-
})),
|
|
38
|
-
metadata: {
|
|
39
|
-
capability: task.capability,
|
|
40
|
-
tenantId: task.tenantId,
|
|
41
|
-
attempt: task.attempt,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=task-projection.js.map
|