salmon-loop 0.3.0 → 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 +109 -153
- package/dist/cli/headless/json-protocol.js +1 -1
- package/dist/cli/headless/stream-json-protocol.js +3 -2
- package/dist/cli/slash/runtime.js +5 -1
- package/dist/core/adapters/fs/node-fs.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/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-serve.js +0 -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 +10 -0
- 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 +36 -11
- 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 +271 -603
- 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/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 -309
- 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,6 +1,6 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { getLogger,
|
|
3
|
+
import { getLogger, McpConnectionManager, } from '../../core/facades/cli-authorization-non-interactive.js';
|
|
4
4
|
import { text } from '../locales/index.js';
|
|
5
5
|
const DecisionSchema = z
|
|
6
6
|
.object({
|
|
@@ -21,22 +21,6 @@ function findMcpServer(extensions, name) {
|
|
|
21
21
|
return undefined;
|
|
22
22
|
return extensions.mcpServers.find((s) => s.enabled && s.name === name);
|
|
23
23
|
}
|
|
24
|
-
function toMcpClientConfig(server) {
|
|
25
|
-
if (server.transport === 'http') {
|
|
26
|
-
return {
|
|
27
|
-
name: server.name,
|
|
28
|
-
url: server.url,
|
|
29
|
-
headers: server.headers,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
name: server.name,
|
|
34
|
-
command: server.command,
|
|
35
|
-
args: server.args,
|
|
36
|
-
env: server.env,
|
|
37
|
-
cwd: server.cwd,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
24
|
function extractDecisionPayloadFromMcpResult(result) {
|
|
41
25
|
if (!result || typeof result !== 'object')
|
|
42
26
|
return result;
|
|
@@ -130,11 +114,13 @@ export async function requestNonInteractiveAuthorizationDecision(params) {
|
|
|
130
114
|
const timeoutMs = params.config.nonInteractive?.mcp?.timeoutMs ?? 10_000;
|
|
131
115
|
const controller = new AbortController();
|
|
132
116
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
133
|
-
const
|
|
117
|
+
const manager = params.mcpConnectionManagerFactory?.(server) ?? new McpConnectionManager([server]);
|
|
134
118
|
try {
|
|
135
|
-
await
|
|
119
|
+
await manager.startAll();
|
|
136
120
|
const result = await Promise.race([
|
|
137
|
-
|
|
121
|
+
manager.callTool(server.name, toolName, { request: params.request }, {
|
|
122
|
+
signal: controller.signal,
|
|
123
|
+
}),
|
|
138
124
|
new Promise((_, reject) => {
|
|
139
125
|
controller.signal.addEventListener('abort', () => reject(new Error('timeout')), {
|
|
140
126
|
once: true,
|
|
@@ -156,7 +142,7 @@ export async function requestNonInteractiveAuthorizationDecision(params) {
|
|
|
156
142
|
}
|
|
157
143
|
finally {
|
|
158
144
|
clearTimeout(timeout);
|
|
159
|
-
await
|
|
145
|
+
await manager.stopAll();
|
|
160
146
|
}
|
|
161
147
|
}
|
|
162
148
|
const unknown = String(params.config.nonInteractive?.strategy ?? '');
|
|
@@ -46,7 +46,7 @@ export async function handleChatCommand(options, command) {
|
|
|
46
46
|
const modeOptionSource = getOptionValueSourceWithGlobalFallback(command, 'mode');
|
|
47
47
|
const checkpointStrategyOptionSource = getOptionValueSourceWithGlobalFallback(command, 'checkpointStrategy');
|
|
48
48
|
const rawPermissionMode = (modeOptionSource === 'cli' ? allOptions.mode : undefined) ??
|
|
49
|
-
resolvedConfig.
|
|
49
|
+
normalizePermissionMode(resolvedConfig.raw?.mode) ??
|
|
50
50
|
defaultFlowProfile.defaultPermissionMode ??
|
|
51
51
|
'interactive';
|
|
52
52
|
const permissionMode = normalizePermissionMode(rawPermissionMode);
|
|
@@ -196,50 +196,55 @@ export const parallelCommand = {
|
|
|
196
196
|
authorizationMode: 'deferred',
|
|
197
197
|
extensions: extensionResolution.resolved,
|
|
198
198
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
const canWaitForAuth = typeof toolstack.router.waitForAuthorization === 'function';
|
|
216
|
-
let resumeAttempts = 0;
|
|
217
|
-
while (result.blockedApprovals.length > 0 && canWaitForAuth && !runSignal.aborted) {
|
|
218
|
-
resumeAttempts++;
|
|
219
|
-
if (resumeAttempts > 10)
|
|
220
|
-
break;
|
|
221
|
-
await Promise.all(result.blockedApprovals.map(async (a) => {
|
|
222
|
-
await toolstack.router.waitForAuthorization(a.nodeId, runSignal);
|
|
223
|
-
}));
|
|
224
|
-
result = await scheduler.run(state.plan, runtime, runSignal, {
|
|
225
|
-
initialResults: result.nodeResults,
|
|
199
|
+
try {
|
|
200
|
+
const scheduler = new ParallelScheduler(toolstack.router, new InMemoryLockManager());
|
|
201
|
+
const phase = state.runtime?.phase || 'PATCH';
|
|
202
|
+
const runtime = {
|
|
203
|
+
repoRoot: workspace.workPath,
|
|
204
|
+
worktreeRoot: workspace.workPath,
|
|
205
|
+
persistenceRoot: workspace.baseRepoPath,
|
|
206
|
+
attemptId: 0,
|
|
207
|
+
dryRun: false,
|
|
208
|
+
model: state.runtime?.model,
|
|
209
|
+
phase,
|
|
210
|
+
};
|
|
211
|
+
const runSignal = new AbortController().signal;
|
|
212
|
+
let result = await scheduler.run(state.plan, runtime, runSignal, {
|
|
213
|
+
initialResults: state.result.nodeResults,
|
|
226
214
|
resumeBlockedApprovals: true,
|
|
227
215
|
});
|
|
216
|
+
const canWaitForAuth = typeof toolstack.router.waitForAuthorization === 'function';
|
|
217
|
+
let resumeAttempts = 0;
|
|
218
|
+
while (result.blockedApprovals.length > 0 && canWaitForAuth && !runSignal.aborted) {
|
|
219
|
+
resumeAttempts++;
|
|
220
|
+
if (resumeAttempts > 10)
|
|
221
|
+
break;
|
|
222
|
+
await Promise.all(result.blockedApprovals.map(async (a) => {
|
|
223
|
+
await toolstack.router.waitForAuthorization(a.nodeId, runSignal);
|
|
224
|
+
}));
|
|
225
|
+
result = await scheduler.run(state.plan, runtime, runSignal, {
|
|
226
|
+
initialResults: result.nodeResults,
|
|
227
|
+
resumeBlockedApprovals: true,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
await PlanPersistence.save(workspace.baseRepoPath, state.plan, result, {
|
|
231
|
+
repoRoot: workspace.workPath,
|
|
232
|
+
worktreeRoot: workspace.workPath,
|
|
233
|
+
persistenceRoot: workspace.baseRepoPath,
|
|
234
|
+
phase,
|
|
235
|
+
model: state.runtime?.model,
|
|
236
|
+
});
|
|
237
|
+
emit({
|
|
238
|
+
type: 'log',
|
|
239
|
+
level: result.failed ? 'warn' : 'info',
|
|
240
|
+
message: text.cli.parallelResumed(planId, result.blockedApprovals.length, result.failed),
|
|
241
|
+
timestamp: new Date(),
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
await toolstack.dispose?.();
|
|
228
247
|
}
|
|
229
|
-
await PlanPersistence.save(workspace.baseRepoPath, state.plan, result, {
|
|
230
|
-
repoRoot: workspace.workPath,
|
|
231
|
-
worktreeRoot: workspace.workPath,
|
|
232
|
-
persistenceRoot: workspace.baseRepoPath,
|
|
233
|
-
phase,
|
|
234
|
-
model: state.runtime?.model,
|
|
235
|
-
});
|
|
236
|
-
emit({
|
|
237
|
-
type: 'log',
|
|
238
|
-
level: result.failed ? 'warn' : 'info',
|
|
239
|
-
message: text.cli.parallelResumed(planId, result.blockedApprovals.length, result.failed),
|
|
240
|
-
timestamp: new Date(),
|
|
241
|
-
});
|
|
242
|
-
return;
|
|
243
248
|
}
|
|
244
249
|
finally {
|
|
245
250
|
await WorkspaceManager.teardown(workspace, emit);
|
|
@@ -2,6 +2,9 @@ import { text } from '../../locales/index.js';
|
|
|
2
2
|
export function buildRunAssistantMessage(params) {
|
|
3
3
|
if (!params.result.success)
|
|
4
4
|
return text.cli.chatFailed(params.result.reason);
|
|
5
|
+
const answerMessage = params.mode === 'answer' ? params.result.assistantMessage?.trim() : '';
|
|
6
|
+
if (answerMessage)
|
|
7
|
+
return answerMessage;
|
|
5
8
|
if (params.mode === 'review')
|
|
6
9
|
return text.cli.chatReviewCompleted;
|
|
7
10
|
if (params.mode === 'research')
|
|
@@ -244,8 +244,9 @@ export async function handleRunCommand(options, command) {
|
|
|
244
244
|
}
|
|
245
245
|
const profile = resolveExecutionProfile(mode);
|
|
246
246
|
const permissionModeOptionSource = getOptionValueSourceWithGlobalFallback(command, 'mode');
|
|
247
|
+
const configuredPermissionMode = normalizePermissionMode(resolvedConfig.raw?.mode);
|
|
247
248
|
const rawPermissionMode = (permissionModeOptionSource === 'cli' ? allOptions.mode : undefined) ??
|
|
248
|
-
|
|
249
|
+
configuredPermissionMode ??
|
|
249
250
|
profile.defaultPermissionMode ??
|
|
250
251
|
'interactive';
|
|
251
252
|
const permissionMode = normalizePermissionMode(rawPermissionMode);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { normalizePermissionMode } from '../../core/config/index.js';
|
|
2
2
|
import { buildA2AAgentCard, createAcpFormalAgent, createAgentServerRuntime, createInteractionFacade, createSalmonTaskExecutor, createTaskEventBus, createPluginRegistry, createPromptRegistry, getUserAcpSessionStorePath, GitSnapshotCheckpointService, getLogger, mergeResolvedExtensions, PACKAGE_VERSION, PlainReporter, PluginLoader, resolveExtensions, resolveExecutionProfile, runSalmonLoop, setPluginRegistry, setPromptRegistry, startAcpStdioServer, StderrReporter, } from '../../core/facades/cli-serve.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { readPlan } from '../../core/plan/index.js';
|
|
4
|
+
import { toA2APublicSkills } from '../../core/public-capabilities/projections.js';
|
|
5
5
|
import { createTerminalAuthorizationProvider } from '../authorization/provider.js';
|
|
6
6
|
import { text } from '../locales/index.js';
|
|
7
7
|
import { getOptionValueSourceWithGlobalFallback } from '../utils/command-option-source.js';
|
|
@@ -41,7 +41,7 @@ function buildServeLoopExecutionDefaults(mode) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
function registerServeShutdown({ message, closeRuntime, closeAcpStdio, }) {
|
|
44
|
-
|
|
44
|
+
const shutdown = async () => {
|
|
45
45
|
getLogger().info(message);
|
|
46
46
|
try {
|
|
47
47
|
await closeRuntime?.();
|
|
@@ -50,39 +50,12 @@ function registerServeShutdown({ message, closeRuntime, closeAcpStdio, }) {
|
|
|
50
50
|
closeAcpStdio?.();
|
|
51
51
|
process.exit(0);
|
|
52
52
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const serve = program
|
|
57
|
-
.command('serve')
|
|
58
|
-
.description(text.cli.serveDescription)
|
|
59
|
-
.option('--a2a-host <host>', text.cli.a2aHostOption)
|
|
60
|
-
.option('--a2a-port <port>', text.cli.a2aPortOption)
|
|
61
|
-
.option('--a2a-token <token>', text.cli.a2aTokenOption, (value, previous) => previous.concat([value]), [])
|
|
62
|
-
.option('--no-acp-stdio', text.cli.acpStdioDisableOption)
|
|
63
|
-
.option('--no-color', text.cli.noColorOption)
|
|
64
|
-
.action(handleServeCommand);
|
|
65
|
-
serve
|
|
66
|
-
.command('acp')
|
|
67
|
-
.description(text.cli.serveAcpDescription)
|
|
68
|
-
.option('--no-color', text.cli.noColorOption)
|
|
69
|
-
.action(handleServeAcpCommand);
|
|
53
|
+
};
|
|
54
|
+
process.once('SIGINT', shutdown);
|
|
55
|
+
process.once('SIGTERM', shutdown);
|
|
70
56
|
}
|
|
71
|
-
|
|
72
|
-
const allOptions =
|
|
73
|
-
const configResult = await resolveCliConfig({
|
|
74
|
-
repo: allOptions.repo,
|
|
75
|
-
cwd: process.cwd(),
|
|
76
|
-
configPath: allOptions.config,
|
|
77
|
-
enableConfigFile: allOptions.configFile !== false,
|
|
78
|
-
auditScope: allOptions.auditScope,
|
|
79
|
-
logMode: allOptions.logMode,
|
|
80
|
-
});
|
|
81
|
-
if (!configResult.ok) {
|
|
82
|
-
getLogger().error(configResult.message, true);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
const { resolvedConfig, auditScope, repoPath: defaultRepoPath } = configResult;
|
|
57
|
+
async function resolveServeCommonSetup(params) {
|
|
58
|
+
const { command, allOptions, defaultRepoPath, resolvedConfig, auditScope } = params;
|
|
86
59
|
const defaultFlowMode = 'autopilot';
|
|
87
60
|
const defaultPermissionMode = resolveServePermissionMode({
|
|
88
61
|
command,
|
|
@@ -90,19 +63,6 @@ export async function handleServeCommand(_options, command) {
|
|
|
90
63
|
rawConfiguredPermissionMode: resolvedConfig.raw?.mode,
|
|
91
64
|
flowMode: defaultFlowMode,
|
|
92
65
|
});
|
|
93
|
-
const serverConfig = resolvedConfig.server;
|
|
94
|
-
const rawA2aHost = allOptions.a2aHost ?? serverConfig?.a2a?.host;
|
|
95
|
-
const a2aHost = String(rawA2aHost ?? '127.0.0.1');
|
|
96
|
-
const rawA2aPort = allOptions.a2aPort ?? serverConfig?.a2a?.port;
|
|
97
|
-
const a2aPort = parsePort(rawA2aPort, 7431);
|
|
98
|
-
if (!Number.isFinite(a2aPort) || a2aPort <= 0) {
|
|
99
|
-
getLogger().error(text.cli.invalidA2APort(String(rawA2aPort ?? '')), true);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
const acpStdioEnabled = allOptions.acpStdio !== false;
|
|
103
|
-
if (acpStdioEnabled) {
|
|
104
|
-
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
105
|
-
}
|
|
106
66
|
const languagePlugins = createPluginRegistry();
|
|
107
67
|
setPluginRegistry(languagePlugins);
|
|
108
68
|
setPromptRegistry(createPromptRegistry());
|
|
@@ -168,6 +128,87 @@ export async function handleServeCommand(_options, command) {
|
|
|
168
128
|
executeTask: executor.execute,
|
|
169
129
|
eventBus: sharedEventBus,
|
|
170
130
|
});
|
|
131
|
+
return {
|
|
132
|
+
defaultPermissionMode,
|
|
133
|
+
executor,
|
|
134
|
+
sharedEventBus,
|
|
135
|
+
checkpointService,
|
|
136
|
+
acpFacade,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function buildAcpAgentOptions(params) {
|
|
140
|
+
return {
|
|
141
|
+
agentInfo: { name: 'salmon-loop', version: PACKAGE_VERSION },
|
|
142
|
+
defaultModeId: 'autopilot',
|
|
143
|
+
defaultPermissionPolicy: resolveDefaultAcpPermissionPolicy(params.defaultPermissionMode),
|
|
144
|
+
checkpointReader: {
|
|
145
|
+
listBySession: async ({ repoPath, sessionId, limit, }) => await params.checkpointService.list({ repoPath, sessionId, limit }),
|
|
146
|
+
getById: async ({ repoPath, checkpointId }) => (await params.checkpointService.loadWithStatus({ repoPath, checkpointId })).handle,
|
|
147
|
+
probeById: async ({ repoPath, checkpointId }) => {
|
|
148
|
+
const status = await params.checkpointService.loadWithStatus({ repoPath, checkpointId });
|
|
149
|
+
return { valid: Boolean(status.handle), reason: status.reason };
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
planReader: {
|
|
153
|
+
readBySession: async ({ repoPath, sessionId }) => await readPlan({ persistenceRoot: repoPath, sessionId }),
|
|
154
|
+
},
|
|
155
|
+
facade: params.facade,
|
|
156
|
+
sessionPersistencePath: getUserAcpSessionStorePath(),
|
|
157
|
+
sessionStorePolicy: params.sessionStorePolicy,
|
|
158
|
+
eventBus: params.sharedEventBus,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export function registerServeCommands(program) {
|
|
162
|
+
const serve = program
|
|
163
|
+
.command('serve')
|
|
164
|
+
.description(text.cli.serveDescription)
|
|
165
|
+
.option('--a2a-host <host>', text.cli.a2aHostOption)
|
|
166
|
+
.option('--a2a-port <port>', text.cli.a2aPortOption)
|
|
167
|
+
.option('--a2a-token <token>', text.cli.a2aTokenOption, (value, previous) => previous.concat([value]), [])
|
|
168
|
+
.option('--no-acp-stdio', text.cli.acpStdioDisableOption)
|
|
169
|
+
.option('--no-color', text.cli.noColorOption)
|
|
170
|
+
.action(handleServeCommand);
|
|
171
|
+
serve
|
|
172
|
+
.command('acp')
|
|
173
|
+
.description(text.cli.serveAcpDescription)
|
|
174
|
+
.option('--no-color', text.cli.noColorOption)
|
|
175
|
+
.action(handleServeAcpCommand);
|
|
176
|
+
}
|
|
177
|
+
export async function handleServeCommand(_options, command) {
|
|
178
|
+
const allOptions = command.optsWithGlobals();
|
|
179
|
+
const configResult = await resolveCliConfig({
|
|
180
|
+
repo: allOptions.repo,
|
|
181
|
+
cwd: process.cwd(),
|
|
182
|
+
configPath: allOptions.config,
|
|
183
|
+
enableConfigFile: allOptions.configFile !== false,
|
|
184
|
+
auditScope: allOptions.auditScope,
|
|
185
|
+
logMode: allOptions.logMode,
|
|
186
|
+
});
|
|
187
|
+
if (!configResult.ok) {
|
|
188
|
+
getLogger().error(configResult.message, true);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
const { resolvedConfig, repoPath: defaultRepoPath } = configResult;
|
|
192
|
+
const serverConfig = resolvedConfig.server;
|
|
193
|
+
const rawA2aHost = allOptions.a2aHost ?? serverConfig?.a2a?.host;
|
|
194
|
+
const a2aHost = String(rawA2aHost ?? '127.0.0.1');
|
|
195
|
+
const rawA2aPort = allOptions.a2aPort ?? serverConfig?.a2a?.port;
|
|
196
|
+
const a2aPort = parsePort(rawA2aPort, 7431);
|
|
197
|
+
if (!Number.isFinite(a2aPort) || a2aPort <= 0) {
|
|
198
|
+
getLogger().error(text.cli.invalidA2APort(String(rawA2aPort ?? '')), true);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const acpStdioEnabled = allOptions.acpStdio !== false;
|
|
202
|
+
if (acpStdioEnabled) {
|
|
203
|
+
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
204
|
+
}
|
|
205
|
+
const { defaultPermissionMode, executor, sharedEventBus, checkpointService, acpFacade } = await resolveServeCommonSetup({
|
|
206
|
+
command,
|
|
207
|
+
allOptions,
|
|
208
|
+
defaultRepoPath,
|
|
209
|
+
resolvedConfig,
|
|
210
|
+
auditScope: configResult.auditScope,
|
|
211
|
+
});
|
|
171
212
|
const tokens = Array.isArray(allOptions.a2aToken)
|
|
172
213
|
? allOptions.a2aToken.filter((token) => typeof token === 'string')
|
|
173
214
|
: [];
|
|
@@ -188,33 +229,22 @@ export async function handleServeCommand(_options, command) {
|
|
|
188
229
|
next();
|
|
189
230
|
}
|
|
190
231
|
: undefined;
|
|
191
|
-
const
|
|
192
|
-
const a2aSkills = toA2APublicSkills(a2aPublicCapabilities);
|
|
232
|
+
const a2aSkills = toA2APublicSkills();
|
|
193
233
|
const agentCard = buildA2AAgentCard({
|
|
194
234
|
name: 'salmon-loop',
|
|
195
|
-
url: `http://${a2aHost}:${a2aPort}`,
|
|
235
|
+
url: `http://${a2aHost}:${a2aPort}/a2a/jsonrpc`,
|
|
196
236
|
capabilities: a2aSkills,
|
|
197
237
|
security: authTokens.length > 0 ? [{ type: 'http', scheme: 'bearer' }] : [],
|
|
198
238
|
});
|
|
199
239
|
if (acpStdioEnabled) {
|
|
200
|
-
|
|
201
|
-
conn,
|
|
202
|
-
agentInfo: { name: 'salmon-loop', version: PACKAGE_VERSION },
|
|
203
|
-
defaultModeId: 'autopilot',
|
|
204
|
-
defaultPermissionPolicy: resolveDefaultAcpPermissionPolicy(defaultPermissionMode),
|
|
205
|
-
checkpointReader: {
|
|
206
|
-
listBySession: async ({ repoPath, sessionId, limit }) => await checkpointService.list({ repoPath, sessionId, limit }),
|
|
207
|
-
getById: async ({ repoPath, checkpointId }) => (await checkpointService.loadWithStatus({ repoPath, checkpointId })).handle,
|
|
208
|
-
probeById: async ({ repoPath, checkpointId }) => {
|
|
209
|
-
const status = await checkpointService.loadWithStatus({ repoPath, checkpointId });
|
|
210
|
-
return { valid: Boolean(status.handle), reason: status.reason };
|
|
211
|
-
},
|
|
212
|
-
},
|
|
240
|
+
const agentOptions = buildAcpAgentOptions({
|
|
213
241
|
facade: acpFacade,
|
|
214
|
-
|
|
242
|
+
checkpointService,
|
|
243
|
+
defaultPermissionMode,
|
|
244
|
+
sharedEventBus,
|
|
215
245
|
sessionStorePolicy: resolvedConfig.server?.acp?.sessionStore,
|
|
216
|
-
|
|
217
|
-
}));
|
|
246
|
+
});
|
|
247
|
+
startAcpStdioServer((conn) => createAcpFormalAgent({ conn, ...agentOptions }));
|
|
218
248
|
getLogger().info(text.cli.acpStdioStarted('n/a (stdio)'));
|
|
219
249
|
}
|
|
220
250
|
const runtime = createAgentServerRuntime({
|
|
@@ -250,97 +280,23 @@ export async function handleServeAcpCommand(_options, command) {
|
|
|
250
280
|
getLogger().error(configResult.message, true);
|
|
251
281
|
process.exit(1);
|
|
252
282
|
}
|
|
253
|
-
const { resolvedConfig,
|
|
254
|
-
|
|
255
|
-
const defaultPermissionMode =
|
|
283
|
+
const { resolvedConfig, repoPath: defaultRepoPath } = configResult;
|
|
284
|
+
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
285
|
+
const { defaultPermissionMode, acpFacade, checkpointService, sharedEventBus } = await resolveServeCommonSetup({
|
|
256
286
|
command,
|
|
257
287
|
allOptions,
|
|
258
|
-
|
|
259
|
-
|
|
288
|
+
defaultRepoPath,
|
|
289
|
+
resolvedConfig,
|
|
290
|
+
auditScope: configResult.auditScope,
|
|
260
291
|
});
|
|
261
|
-
|
|
262
|
-
const languagePlugins = createPluginRegistry();
|
|
263
|
-
setPluginRegistry(languagePlugins);
|
|
264
|
-
setPromptRegistry(createPromptRegistry());
|
|
265
|
-
await PluginLoader.loadPlugins(languagePlugins, defaultRepoPath);
|
|
266
|
-
const extensions = await resolveExtensions({ repoRoot: defaultRepoPath });
|
|
267
|
-
const { llm } = createRuntimeLlmAndWarn({
|
|
268
|
-
llmConfig: resolvedConfig.llm,
|
|
269
|
-
langfuseEnabled: resolvedConfig.observability.langfuse.enabled,
|
|
270
|
-
});
|
|
271
|
-
const outcomeReporter = createOutcomeReporter({
|
|
272
|
-
enabled: resolvedConfig.observability.langfuse.outcome,
|
|
273
|
-
endpoint: resolvedConfig.observability.langfuse.endpoint,
|
|
274
|
-
llmBaseUrl: resolvedConfig.llm.api.baseUrl,
|
|
275
|
-
langfuseApiKey: resolvedConfig.observability.langfuse.apiKey,
|
|
276
|
-
});
|
|
277
|
-
const defaultAuthorizationProvider = createTerminalAuthorizationProvider({
|
|
278
|
-
config: resolvedConfig.toolAuthorization,
|
|
279
|
-
extensions: extensions.resolved,
|
|
280
|
-
forceNonInteractive: true,
|
|
281
|
-
});
|
|
282
|
-
const executor = createSalmonTaskExecutor({
|
|
283
|
-
runLoop: async ({ instruction, mode, repoPath, onEvent, signal, authorizationProvider, authorizationMode, extensions: taskExtensions, }) => {
|
|
284
|
-
const effectiveRepoPath = repoPath ?? defaultRepoPath;
|
|
285
|
-
const flowMode = mode;
|
|
286
|
-
const executionDefaults = buildServeLoopExecutionDefaults(flowMode);
|
|
287
|
-
const permissionMode = resolveServePermissionMode({
|
|
288
|
-
command,
|
|
289
|
-
allOptions,
|
|
290
|
-
rawConfiguredPermissionMode: resolvedConfig.raw?.mode,
|
|
291
|
-
flowMode,
|
|
292
|
-
});
|
|
293
|
-
return await runSalmonLoop({
|
|
294
|
-
instruction,
|
|
295
|
-
repoPath: effectiveRepoPath,
|
|
296
|
-
llm,
|
|
297
|
-
mode: flowMode,
|
|
298
|
-
verify: resolvedConfig.verify.command,
|
|
299
|
-
...executionDefaults,
|
|
300
|
-
llmOutput: resolvedConfig.llmOutput,
|
|
301
|
-
outcomeReporter,
|
|
302
|
-
langfuseSessionId: resolvedConfig.observability.langfuse.sessionId,
|
|
303
|
-
langfuseUserId: resolvedConfig.observability.langfuse.userId,
|
|
304
|
-
auditScope,
|
|
305
|
-
permissionMode,
|
|
306
|
-
languagePlugins,
|
|
307
|
-
authorizationProvider: authorizationProvider ?? defaultAuthorizationProvider,
|
|
308
|
-
authorizationMode,
|
|
309
|
-
extensions: mergeResolvedExtensions(extensions.resolved, taskExtensions),
|
|
310
|
-
onEvent,
|
|
311
|
-
signal,
|
|
312
|
-
});
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
const sharedEventBus = createTaskEventBus();
|
|
316
|
-
const checkpointService = new GitSnapshotCheckpointService(undefined, resolvedConfig.server?.acp?.checkpointManifest);
|
|
317
|
-
await checkpointService.gc({
|
|
318
|
-
repoPath: defaultRepoPath,
|
|
319
|
-
olderThanMs: 1000 * 60 * 60 * 24 * 14,
|
|
320
|
-
maxPerSession: 30,
|
|
321
|
-
});
|
|
322
|
-
const acpFacade = createInteractionFacade({
|
|
323
|
-
executeTask: executor.execute,
|
|
324
|
-
eventBus: sharedEventBus,
|
|
325
|
-
});
|
|
326
|
-
startAcpStdioServer((conn) => createAcpFormalAgent({
|
|
327
|
-
conn,
|
|
328
|
-
agentInfo: { name: 'salmon-loop', version: PACKAGE_VERSION },
|
|
329
|
-
defaultModeId: 'autopilot',
|
|
330
|
-
defaultPermissionPolicy: resolveDefaultAcpPermissionPolicy(defaultPermissionMode),
|
|
331
|
-
checkpointReader: {
|
|
332
|
-
listBySession: async ({ repoPath, sessionId, limit }) => await checkpointService.list({ repoPath, sessionId, limit }),
|
|
333
|
-
getById: async ({ repoPath, checkpointId }) => (await checkpointService.loadWithStatus({ repoPath, checkpointId })).handle,
|
|
334
|
-
probeById: async ({ repoPath, checkpointId }) => {
|
|
335
|
-
const status = await checkpointService.loadWithStatus({ repoPath, checkpointId });
|
|
336
|
-
return { valid: Boolean(status.handle), reason: status.reason };
|
|
337
|
-
},
|
|
338
|
-
},
|
|
292
|
+
const agentOptions = buildAcpAgentOptions({
|
|
339
293
|
facade: acpFacade,
|
|
340
|
-
|
|
294
|
+
checkpointService,
|
|
295
|
+
defaultPermissionMode,
|
|
296
|
+
sharedEventBus,
|
|
341
297
|
sessionStorePolicy: resolvedConfig.server?.acp?.sessionStore,
|
|
342
|
-
|
|
343
|
-
}));
|
|
298
|
+
});
|
|
299
|
+
startAcpStdioServer((conn) => createAcpFormalAgent({ conn, ...agentOptions }));
|
|
344
300
|
getLogger().info(text.cli.acpStdioStarted('n/a (stdio)'));
|
|
345
301
|
registerServeShutdown({
|
|
346
302
|
message: 'Received SIGINT, shutting down ACP server...',
|
|
@@ -63,7 +63,7 @@ export function encodeJsonResult(params) {
|
|
|
63
63
|
const safeHint = overrides?.safeHint ?? params.loopResult.safeHint ?? params.loopResult.reason;
|
|
64
64
|
const remediationSteps = overrides?.remediationSteps ?? params.loopResult.remediationSteps ?? [];
|
|
65
65
|
const diagnosticCode = overrides?.diagnosticCode ?? params.loopResult.diagnosticCode ?? params.loopResult.reasonCode;
|
|
66
|
-
const reason = overrides?.reason ??
|
|
66
|
+
const reason = overrides?.reason ?? params.loopResult.reason;
|
|
67
67
|
const reasonCode = overrides?.reasonCode ?? params.loopResult.reasonCode;
|
|
68
68
|
const errorCode = overrides?.errorCode ?? params.loopResult.errorCode;
|
|
69
69
|
const warnings = normalizeHeadlessWarnings(params.warnings);
|
|
@@ -76,6 +76,7 @@ export function encodeStreamLoopEvent(params) {
|
|
|
76
76
|
export function encodeStreamResult(params) {
|
|
77
77
|
const exitCode = getStreamExitCode(params.loopResult);
|
|
78
78
|
const warnings = normalizeHeadlessWarnings(params.warnings);
|
|
79
|
+
const safeHint = params.loopResult.safeHint ?? params.loopResult.reason;
|
|
79
80
|
const patchArtifact = params.loopResult.benchmarkPatchArtifact
|
|
80
81
|
? {
|
|
81
82
|
kind: params.loopResult.benchmarkPatchArtifact.kind,
|
|
@@ -102,10 +103,10 @@ export function encodeStreamResult(params) {
|
|
|
102
103
|
timestamp: toIso(params.at),
|
|
103
104
|
success: Boolean(params.loopResult.success),
|
|
104
105
|
exit_code: exitCode,
|
|
105
|
-
reason: params.loopResult.
|
|
106
|
+
reason: params.loopResult.reason,
|
|
106
107
|
reason_code: params.loopResult.reasonCode,
|
|
107
108
|
diagnostic_code: params.loopResult.diagnosticCode ?? params.loopResult.reasonCode,
|
|
108
|
-
safe_hint:
|
|
109
|
+
safe_hint: safeHint,
|
|
109
110
|
remediation_steps: params.loopResult.remediationSteps ?? [],
|
|
110
111
|
attempts: params.loopResult.attempts,
|
|
111
112
|
changed_files: params.loopResult.changedFiles ?? [],
|
|
@@ -151,9 +151,10 @@ export async function createCliSlashRuntime(options) {
|
|
|
151
151
|
dryRun: false,
|
|
152
152
|
verbose: undefined,
|
|
153
153
|
}, silentEmit);
|
|
154
|
+
let toolstack;
|
|
154
155
|
try {
|
|
155
156
|
await env.setup();
|
|
156
|
-
|
|
157
|
+
toolstack = await createStandardToolstack({
|
|
157
158
|
repoRoot: env.activeRepoPath,
|
|
158
159
|
persistenceRoot: options.repoRoot,
|
|
159
160
|
worktreeRoot: env.activeRepoPath,
|
|
@@ -188,6 +189,9 @@ export async function createCliSlashRuntime(options) {
|
|
|
188
189
|
return { kind: 'rewrite', input: res.injectedPrompt };
|
|
189
190
|
}
|
|
190
191
|
finally {
|
|
192
|
+
await toolstack
|
|
193
|
+
?.dispose?.()
|
|
194
|
+
.catch((error) => logIgnoredError('[SlashRuntime] toolstack cleanup failed', error));
|
|
191
195
|
await env
|
|
192
196
|
.teardown()
|
|
193
197
|
.catch((error) => logIgnoredError('[SlashRuntime] env teardown failed', error));
|
|
@@ -4,4 +4,5 @@ export const promises = fsPromises;
|
|
|
4
4
|
export const syncFs = Object.assign({}, fs, fsPromises);
|
|
5
5
|
export const { access, appendFile, chmod, chown, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, unlink, utimes, writeFile, } = fsPromises;
|
|
6
6
|
export const { existsSync, readFileSync, realpathSync } = fs;
|
|
7
|
+
export const { constants } = fs;
|
|
7
8
|
//# sourceMappingURL=node-fs.js.map
|
|
@@ -23,7 +23,7 @@ const DIFF_ARGS = [
|
|
|
23
23
|
'.',
|
|
24
24
|
];
|
|
25
25
|
function normalizeGitPatch(text) {
|
|
26
|
-
const normalized = text.replace(/\r\n/g, '\n').replace(/\
|
|
26
|
+
const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n+$/, '');
|
|
27
27
|
return normalized.length > 0 ? `${normalized}\n` : '';
|
|
28
28
|
}
|
|
29
29
|
function splitLines(text) {
|
|
@@ -163,7 +163,7 @@ export class ContextService {
|
|
|
163
163
|
const absoluteFile = defaultPathAdapter.resolve(repoPath, relativeFile);
|
|
164
164
|
try {
|
|
165
165
|
const stat = await this.fileAdapter.stat(absoluteFile);
|
|
166
|
-
parts.push(
|
|
166
|
+
parts.push(this.formatStatSignature(relativeFile, stat));
|
|
167
167
|
}
|
|
168
168
|
catch {
|
|
169
169
|
parts.push(`${relativeFile}:missing`);
|
|
@@ -182,7 +182,7 @@ export class ContextService {
|
|
|
182
182
|
const gitPath = defaultPathAdapter.resolve(repoPath, rel);
|
|
183
183
|
try {
|
|
184
184
|
const stat = await this.fileAdapter.stat(gitPath);
|
|
185
|
-
parts.push(
|
|
185
|
+
parts.push(this.formatStatSignature(rel, stat));
|
|
186
186
|
}
|
|
187
187
|
catch {
|
|
188
188
|
parts.push(`${rel}:missing`);
|
|
@@ -190,6 +190,9 @@ export class ContextService {
|
|
|
190
190
|
}
|
|
191
191
|
return parts;
|
|
192
192
|
}
|
|
193
|
+
formatStatSignature(relativePath, stat) {
|
|
194
|
+
return `${relativePath}:${stat.mtimeMs}:${stat.ctimeMs ?? 0}:${stat.size}`;
|
|
195
|
+
}
|
|
193
196
|
getEntryTimestamp(entry) {
|
|
194
197
|
return entry.lastAccessedAt ?? entry.createdAt ?? 0;
|
|
195
198
|
}
|