salmon-loop 0.3.0 → 0.3.2
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 +123 -154
- 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/cli/ui/components/CommandSuggestionList.js +1 -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 +36 -10
- 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 +65 -35
- 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,8 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import { normalizePermissionMode } from '../../core/config/index.js';
|
|
2
3
|
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 {
|
|
4
|
+
import { readPlan } from '../../core/plan/index.js';
|
|
5
|
+
import { toA2APublicSkills } from '../../core/public-capabilities/projections.js';
|
|
5
6
|
import { createTerminalAuthorizationProvider } from '../authorization/provider.js';
|
|
6
7
|
import { text } from '../locales/index.js';
|
|
7
8
|
import { getOptionValueSourceWithGlobalFallback } from '../utils/command-option-source.js';
|
|
@@ -41,7 +42,7 @@ function buildServeLoopExecutionDefaults(mode) {
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
function registerServeShutdown({ message, closeRuntime, closeAcpStdio, }) {
|
|
44
|
-
|
|
45
|
+
const shutdown = async () => {
|
|
45
46
|
getLogger().info(message);
|
|
46
47
|
try {
|
|
47
48
|
await closeRuntime?.();
|
|
@@ -50,39 +51,12 @@ function registerServeShutdown({ message, closeRuntime, closeAcpStdio, }) {
|
|
|
50
51
|
closeAcpStdio?.();
|
|
51
52
|
process.exit(0);
|
|
52
53
|
}
|
|
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);
|
|
54
|
+
};
|
|
55
|
+
process.once('SIGINT', shutdown);
|
|
56
|
+
process.once('SIGTERM', shutdown);
|
|
70
57
|
}
|
|
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;
|
|
58
|
+
async function resolveServeCommonSetup(params) {
|
|
59
|
+
const { command, allOptions, defaultRepoPath, resolvedConfig, auditScope } = params;
|
|
86
60
|
const defaultFlowMode = 'autopilot';
|
|
87
61
|
const defaultPermissionMode = resolveServePermissionMode({
|
|
88
62
|
command,
|
|
@@ -90,19 +64,6 @@ export async function handleServeCommand(_options, command) {
|
|
|
90
64
|
rawConfiguredPermissionMode: resolvedConfig.raw?.mode,
|
|
91
65
|
flowMode: defaultFlowMode,
|
|
92
66
|
});
|
|
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
67
|
const languagePlugins = createPluginRegistry();
|
|
107
68
|
setPluginRegistry(languagePlugins);
|
|
108
69
|
setPromptRegistry(createPromptRegistry());
|
|
@@ -168,6 +129,87 @@ export async function handleServeCommand(_options, command) {
|
|
|
168
129
|
executeTask: executor.execute,
|
|
169
130
|
eventBus: sharedEventBus,
|
|
170
131
|
});
|
|
132
|
+
return {
|
|
133
|
+
defaultPermissionMode,
|
|
134
|
+
executor,
|
|
135
|
+
sharedEventBus,
|
|
136
|
+
checkpointService,
|
|
137
|
+
acpFacade,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function buildAcpAgentOptions(params) {
|
|
141
|
+
return {
|
|
142
|
+
agentInfo: { name: 'salmon-loop', version: PACKAGE_VERSION },
|
|
143
|
+
defaultModeId: 'autopilot',
|
|
144
|
+
defaultPermissionPolicy: resolveDefaultAcpPermissionPolicy(params.defaultPermissionMode),
|
|
145
|
+
checkpointReader: {
|
|
146
|
+
listBySession: async ({ repoPath, sessionId, limit, }) => await params.checkpointService.list({ repoPath, sessionId, limit }),
|
|
147
|
+
getById: async ({ repoPath, checkpointId }) => (await params.checkpointService.loadWithStatus({ repoPath, checkpointId })).handle,
|
|
148
|
+
probeById: async ({ repoPath, checkpointId }) => {
|
|
149
|
+
const status = await params.checkpointService.loadWithStatus({ repoPath, checkpointId });
|
|
150
|
+
return { valid: Boolean(status.handle), reason: status.reason };
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
planReader: {
|
|
154
|
+
readBySession: async ({ repoPath, sessionId }) => await readPlan({ persistenceRoot: repoPath, sessionId }),
|
|
155
|
+
},
|
|
156
|
+
facade: params.facade,
|
|
157
|
+
sessionPersistencePath: getUserAcpSessionStorePath(),
|
|
158
|
+
sessionStorePolicy: params.sessionStorePolicy,
|
|
159
|
+
eventBus: params.sharedEventBus,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function registerServeCommands(program) {
|
|
163
|
+
const serve = program
|
|
164
|
+
.command('serve')
|
|
165
|
+
.description(text.cli.serveDescription)
|
|
166
|
+
.option('--a2a-host <host>', text.cli.a2aHostOption)
|
|
167
|
+
.option('--a2a-port <port>', text.cli.a2aPortOption)
|
|
168
|
+
.option('--a2a-token <token>', text.cli.a2aTokenOption, (value, previous) => previous.concat([value]), [])
|
|
169
|
+
.option('--no-acp-stdio', text.cli.acpStdioDisableOption)
|
|
170
|
+
.option('--no-color', text.cli.noColorOption)
|
|
171
|
+
.action(handleServeCommand);
|
|
172
|
+
serve
|
|
173
|
+
.command('acp')
|
|
174
|
+
.description(text.cli.serveAcpDescription)
|
|
175
|
+
.option('--no-color', text.cli.noColorOption)
|
|
176
|
+
.action(handleServeAcpCommand);
|
|
177
|
+
}
|
|
178
|
+
export async function handleServeCommand(_options, command) {
|
|
179
|
+
const allOptions = command.optsWithGlobals();
|
|
180
|
+
const configResult = await resolveCliConfig({
|
|
181
|
+
repo: allOptions.repo,
|
|
182
|
+
cwd: process.cwd(),
|
|
183
|
+
configPath: allOptions.config,
|
|
184
|
+
enableConfigFile: allOptions.configFile !== false,
|
|
185
|
+
auditScope: allOptions.auditScope,
|
|
186
|
+
logMode: allOptions.logMode,
|
|
187
|
+
});
|
|
188
|
+
if (!configResult.ok) {
|
|
189
|
+
getLogger().error(configResult.message, true);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const { resolvedConfig, repoPath: defaultRepoPath } = configResult;
|
|
193
|
+
const serverConfig = resolvedConfig.server;
|
|
194
|
+
const rawA2aHost = allOptions.a2aHost ?? serverConfig?.a2a?.host;
|
|
195
|
+
const a2aHost = String(rawA2aHost ?? '127.0.0.1');
|
|
196
|
+
const rawA2aPort = allOptions.a2aPort ?? serverConfig?.a2a?.port;
|
|
197
|
+
const a2aPort = parsePort(rawA2aPort, 7431);
|
|
198
|
+
if (!Number.isFinite(a2aPort) || a2aPort <= 0) {
|
|
199
|
+
getLogger().error(text.cli.invalidA2APort(String(rawA2aPort ?? '')), true);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const acpStdioEnabled = allOptions.acpStdio !== false;
|
|
203
|
+
if (acpStdioEnabled) {
|
|
204
|
+
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
205
|
+
}
|
|
206
|
+
const { defaultPermissionMode, executor, sharedEventBus, checkpointService, acpFacade } = await resolveServeCommonSetup({
|
|
207
|
+
command,
|
|
208
|
+
allOptions,
|
|
209
|
+
defaultRepoPath,
|
|
210
|
+
resolvedConfig,
|
|
211
|
+
auditScope: configResult.auditScope,
|
|
212
|
+
});
|
|
171
213
|
const tokens = Array.isArray(allOptions.a2aToken)
|
|
172
214
|
? allOptions.a2aToken.filter((token) => typeof token === 'string')
|
|
173
215
|
: [];
|
|
@@ -181,40 +223,41 @@ export async function handleServeCommand(_options, command) {
|
|
|
181
223
|
return;
|
|
182
224
|
}
|
|
183
225
|
const [scheme, token] = authHeader.split(' ');
|
|
184
|
-
|
|
226
|
+
let isAuthenticated = false;
|
|
227
|
+
if (scheme?.toLowerCase() === 'bearer' && token) {
|
|
228
|
+
const tokenBuffer = Buffer.from(token);
|
|
229
|
+
for (const authToken of authTokens) {
|
|
230
|
+
const authTokenBuffer = Buffer.from(authToken);
|
|
231
|
+
if (tokenBuffer.length === authTokenBuffer.length &&
|
|
232
|
+
crypto.timingSafeEqual(tokenBuffer, authTokenBuffer)) {
|
|
233
|
+
isAuthenticated = true;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!isAuthenticated) {
|
|
185
239
|
res.status(401).json({ error: 'Unauthorized' });
|
|
186
240
|
return;
|
|
187
241
|
}
|
|
188
242
|
next();
|
|
189
243
|
}
|
|
190
244
|
: undefined;
|
|
191
|
-
const
|
|
192
|
-
const a2aSkills = toA2APublicSkills(a2aPublicCapabilities);
|
|
245
|
+
const a2aSkills = toA2APublicSkills();
|
|
193
246
|
const agentCard = buildA2AAgentCard({
|
|
194
247
|
name: 'salmon-loop',
|
|
195
|
-
url: `http://${a2aHost}:${a2aPort}`,
|
|
248
|
+
url: `http://${a2aHost}:${a2aPort}/a2a/jsonrpc`,
|
|
196
249
|
capabilities: a2aSkills,
|
|
197
250
|
security: authTokens.length > 0 ? [{ type: 'http', scheme: 'bearer' }] : [],
|
|
198
251
|
});
|
|
199
252
|
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
|
-
},
|
|
253
|
+
const agentOptions = buildAcpAgentOptions({
|
|
213
254
|
facade: acpFacade,
|
|
214
|
-
|
|
255
|
+
checkpointService,
|
|
256
|
+
defaultPermissionMode,
|
|
257
|
+
sharedEventBus,
|
|
215
258
|
sessionStorePolicy: resolvedConfig.server?.acp?.sessionStore,
|
|
216
|
-
|
|
217
|
-
}));
|
|
259
|
+
});
|
|
260
|
+
startAcpStdioServer((conn) => createAcpFormalAgent({ conn, ...agentOptions }));
|
|
218
261
|
getLogger().info(text.cli.acpStdioStarted('n/a (stdio)'));
|
|
219
262
|
}
|
|
220
263
|
const runtime = createAgentServerRuntime({
|
|
@@ -250,97 +293,23 @@ export async function handleServeAcpCommand(_options, command) {
|
|
|
250
293
|
getLogger().error(configResult.message, true);
|
|
251
294
|
process.exit(1);
|
|
252
295
|
}
|
|
253
|
-
const { resolvedConfig,
|
|
254
|
-
|
|
255
|
-
const defaultPermissionMode =
|
|
296
|
+
const { resolvedConfig, repoPath: defaultRepoPath } = configResult;
|
|
297
|
+
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
298
|
+
const { defaultPermissionMode, acpFacade, checkpointService, sharedEventBus } = await resolveServeCommonSetup({
|
|
256
299
|
command,
|
|
257
300
|
allOptions,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
|
|
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,
|
|
301
|
+
defaultRepoPath,
|
|
302
|
+
resolvedConfig,
|
|
303
|
+
auditScope: configResult.auditScope,
|
|
321
304
|
});
|
|
322
|
-
const
|
|
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
|
-
},
|
|
305
|
+
const agentOptions = buildAcpAgentOptions({
|
|
339
306
|
facade: acpFacade,
|
|
340
|
-
|
|
307
|
+
checkpointService,
|
|
308
|
+
defaultPermissionMode,
|
|
309
|
+
sharedEventBus,
|
|
341
310
|
sessionStorePolicy: resolvedConfig.server?.acp?.sessionStore,
|
|
342
|
-
|
|
343
|
-
}));
|
|
311
|
+
});
|
|
312
|
+
startAcpStdioServer((conn) => createAcpFormalAgent({ conn, ...agentOptions }));
|
|
344
313
|
getLogger().info(text.cli.acpStdioStarted('n/a (stdio)'));
|
|
345
314
|
registerServeShutdown({
|
|
346
315
|
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));
|
|
@@ -14,7 +14,7 @@ export const CommandSuggestionList = ({ suggestions, selectedIndex, parentComman
|
|
|
14
14
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: COLORS.border.subtle, marginTop: 0, marginBottom: 0, paddingX: 0, width: "100%", children: [_jsx(Box, { flexDirection: "column", paddingY: 0, children: suggestions.map((item, index) => {
|
|
15
15
|
const isSelected = index === selectedIndex;
|
|
16
16
|
const hasSubcommands = !!item.command?.subcommands?.length;
|
|
17
|
-
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width: 2, children: _jsx(Text, { color: COLORS.semantic.salmon, children: isSelected ? '
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsx(Box, { width: 2, children: _jsx(Text, { color: COLORS.semantic.salmon, children: isSelected ? '❯ ' : ' ' }) }), _jsx(Box, { width: maxNameLength + 4, children: _jsx(Text, { color: isSelected ? COLORS.semantic.cyan : COLORS.semantic.blue, bold: isSelected, children: item.name }) }), _jsx(Box, { width: 2, marginRight: 1, children: hasSubcommands ? _jsx(Text, { color: COLORS.text.muted, children: "\u203A" }) : _jsx(Text, { children: " " }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: isSelected ? COLORS.text.primary : COLORS.text.muted, wrap: "truncate", children: item.description }) })] }, `${item.name}-${index}`));
|
|
18
18
|
}) }), suggestions[selectedIndex]?.command?.usage && (_jsxs(Box, { flexDirection: "row", borderStyle: "single", borderTop: true, borderLeft: false, borderRight: false, borderBottom: false, borderColor: COLORS.border.subtle, paddingX: 1, paddingY: 0, children: [_jsx(Text, { color: COLORS.semantic.blue, children: "TIP: " }), _jsx(Text, { color: COLORS.text.muted, children: "Usage: " }), _jsx(Text, { color: COLORS.text.primary, children: suggestions[selectedIndex].command?.usage })] })), _jsxs(Box, { flexDirection: "row", borderStyle: "single", borderTop: true, borderLeft: false, borderRight: false, borderBottom: false, borderColor: COLORS.border.subtle, paddingX: 1, paddingY: 0, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: COLORS.semantic.salmon, children: "\u2502 " }), _jsx(Text, { color: COLORS.semantic.blue, bold: true, children: title })] }), _jsx(Box, { children: _jsx(Text, { color: COLORS.text.muted, dimColor: true, children: "\u2191\u2193 nav \u00B7 \u23CE select \u00B7 esc close" }) })] })] }));
|
|
19
19
|
};
|
|
20
20
|
//# sourceMappingURL=CommandSuggestionList.js.map
|
|
@@ -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) {
|