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.
Files changed (92) hide show
  1. package/dist/cli/authorization/non-interactive.js +7 -21
  2. package/dist/cli/commands/chat.js +1 -1
  3. package/dist/cli/commands/parallel.js +46 -41
  4. package/dist/cli/commands/run/assistant-message.js +3 -0
  5. package/dist/cli/commands/run/handler.js +2 -1
  6. package/dist/cli/commands/serve.js +109 -153
  7. package/dist/cli/headless/json-protocol.js +1 -1
  8. package/dist/cli/headless/stream-json-protocol.js +3 -2
  9. package/dist/cli/slash/runtime.js +5 -1
  10. package/dist/core/adapters/fs/node-fs.js +1 -0
  11. package/dist/core/benchmark/patch-artifact.js +1 -1
  12. package/dist/core/context/service.js +5 -2
  13. package/dist/core/extensions/index.js +2 -35
  14. package/dist/core/extensions/redact.js +9 -3
  15. package/dist/core/extensions/schemas.js +2 -51
  16. package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
  17. package/dist/core/facades/cli-serve.js +0 -1
  18. package/dist/core/grizzco/dsl/strategies.js +1 -3
  19. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
  20. package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
  21. package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
  22. package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
  23. package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
  24. package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
  25. package/dist/core/grizzco/steps/apply.js +0 -7
  26. package/dist/core/grizzco/steps/autopilot.js +108 -6
  27. package/dist/core/grizzco/steps/preflight.js +10 -0
  28. package/dist/core/grizzco/steps/tool-runtime.js +1 -0
  29. package/dist/core/interaction/events/bus.js +14 -0
  30. package/dist/core/interaction/orchestration/facade.js +10 -0
  31. package/dist/core/mcp/bridge/index.js +4 -0
  32. package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
  33. package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
  34. package/dist/core/mcp/bridge/tool-bridge.js +303 -0
  35. package/dist/core/mcp/cache/resource-cache.js +41 -0
  36. package/dist/core/mcp/catalog/discovery.js +51 -0
  37. package/dist/core/mcp/catalog/notification-router.js +28 -0
  38. package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
  39. package/dist/core/mcp/catalog/resource-catalog.js +7 -0
  40. package/dist/core/mcp/catalog/tool-catalog.js +4 -0
  41. package/dist/core/mcp/client/connection-manager.js +239 -0
  42. package/dist/core/mcp/client/lifecycle.js +13 -0
  43. package/dist/core/mcp/client/transport-factory.js +168 -0
  44. package/dist/core/mcp/config/index.js +32 -0
  45. package/dist/core/mcp/config/schema-v2.js +129 -0
  46. package/dist/core/mcp/host/elicitation-provider.js +209 -0
  47. package/dist/core/mcp/host/roots-provider.js +70 -0
  48. package/dist/core/mcp/host/sampling-provider.js +170 -0
  49. package/dist/core/mcp/index.js +4 -0
  50. package/dist/core/mcp/observability/events.js +19 -0
  51. package/dist/core/mcp/policy/approval-policy.js +2 -0
  52. package/dist/core/mcp/policy/classifier.js +172 -0
  53. package/dist/core/mcp/policy/grants.js +356 -0
  54. package/dist/core/mcp/policy/uri-policy.js +60 -0
  55. package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
  56. package/dist/core/mcp/types.js +2 -0
  57. package/dist/core/protocols/a2a/agent-card.js +36 -11
  58. package/dist/core/protocols/a2a/sdk/executor.js +105 -36
  59. package/dist/core/protocols/a2a/sdk/server.js +1311 -3
  60. package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
  61. package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
  62. package/dist/core/protocols/acp/acp-types.js +17 -0
  63. package/dist/core/protocols/acp/formal-agent.js +271 -603
  64. package/dist/core/protocols/acp/handlers.js +3 -0
  65. package/dist/core/protocols/acp/permission-provider.js +11 -39
  66. package/dist/core/protocols/acp/stdio-server.js +20 -1
  67. package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
  68. package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
  69. package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
  70. package/dist/core/public-capabilities/projections.js +1 -0
  71. package/dist/core/runtime/agent-server-runtime.js +2 -3
  72. package/dist/core/runtime/spawn-command.js +8 -2
  73. package/dist/core/runtime/spawn-interactive.js +26 -0
  74. package/dist/core/session/manager.js +48 -25
  75. package/dist/core/tools/builtin/index.js +6 -1
  76. package/dist/core/tools/builtin/proposal.js +0 -7
  77. package/dist/core/tools/builtin/workspace.js +76 -0
  78. package/dist/core/tools/dispatcher.js +1 -0
  79. package/dist/core/tools/loader.js +92 -46
  80. package/dist/core/verification/runner.js +60 -31
  81. package/dist/core/workspace/capabilities.js +80 -0
  82. package/dist/locales/en.js +17 -3
  83. package/package.json +4 -2
  84. package/dist/core/protocols/a2a/mapper.js +0 -14
  85. package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
  86. package/dist/core/protocols/a2a/task-projection.js +0 -45
  87. package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
  88. package/dist/core/tools/mcp/client.js +0 -309
  89. package/dist/core/tools/mcp/loader.js +0 -110
  90. package/dist/core/tools/mcp/schema.js +0 -54
  91. package/dist/core/tools/mcp/streamable-http.js +0 -101
  92. package/dist/core/tools/mcp/types.js +0 -26
@@ -0,0 +1,13 @@
1
+ export async function withMcpConnections(manager, fn) {
2
+ await manager.startAll();
3
+ try {
4
+ return await fn();
5
+ }
6
+ finally {
7
+ await manager.stopAll();
8
+ }
9
+ }
10
+ export async function stopMcpConnections(manager) {
11
+ await manager.stopAll();
12
+ }
13
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1,168 @@
1
+ import { PassThrough } from 'node:stream';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
4
+ import { spawnInteractiveProcess } from '../../runtime/process-runner.js';
5
+ export function buildStrictStdioEnvironment(env) {
6
+ return { ...env };
7
+ }
8
+ export class StrictStdioClientTransport {
9
+ server;
10
+ child;
11
+ readBuffer = new ReadBuffer();
12
+ stderrStream = new PassThrough();
13
+ onclose;
14
+ onerror;
15
+ onmessage;
16
+ constructor(server) {
17
+ this.server = server;
18
+ }
19
+ get stderr() {
20
+ return this.stderrStream;
21
+ }
22
+ get pid() {
23
+ return this.child?.pid ?? null;
24
+ }
25
+ async start() {
26
+ if (this.child) {
27
+ throw new Error('StrictStdioClientTransport already started');
28
+ }
29
+ await new Promise((resolve, reject) => {
30
+ let settled = false;
31
+ const child = spawnInteractiveProcess({
32
+ command: this.server.command,
33
+ args: this.server.args,
34
+ cwd: this.server.cwd,
35
+ env: buildStrictStdioEnvironment(this.server.env),
36
+ windowsHide: true,
37
+ });
38
+ this.child = child;
39
+ const failStart = (error) => {
40
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
41
+ if (!settled) {
42
+ settled = true;
43
+ reject(normalizedError);
44
+ }
45
+ this.onerror?.(normalizedError);
46
+ };
47
+ child.once('error', failStart);
48
+ child.once('spawn', () => {
49
+ if (!settled) {
50
+ settled = true;
51
+ resolve();
52
+ }
53
+ });
54
+ child.once('close', () => {
55
+ this.child = undefined;
56
+ this.onclose?.();
57
+ });
58
+ if (!child.stdin || !child.stdout) {
59
+ failStart(new Error('MCP stdio transport failed to open stdio streams'));
60
+ return;
61
+ }
62
+ child.stdin?.on?.('error', (error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));
63
+ child.stdout.on('data', (chunk) => {
64
+ this.readBuffer.append(chunk);
65
+ this.processReadBuffer();
66
+ });
67
+ child.stdout.on('error', (error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));
68
+ child.stderr?.pipe(this.stderrStream, { end: false });
69
+ });
70
+ }
71
+ async close() {
72
+ const child = this.child;
73
+ if (!child) {
74
+ this.readBuffer.clear();
75
+ return;
76
+ }
77
+ this.child = undefined;
78
+ const closePromise = new Promise((resolve) => {
79
+ child.once('close', () => resolve());
80
+ });
81
+ try {
82
+ child.stdin?.end?.();
83
+ }
84
+ catch {
85
+ // best-effort shutdown
86
+ }
87
+ await Promise.race([closePromise, unrefTimeout(2_000)]);
88
+ if (child.exitCode === null) {
89
+ try {
90
+ child.kill('SIGTERM');
91
+ }
92
+ catch {
93
+ // best-effort shutdown
94
+ }
95
+ await Promise.race([closePromise, unrefTimeout(2_000)]);
96
+ }
97
+ if (child.exitCode === null) {
98
+ try {
99
+ child.kill('SIGKILL');
100
+ }
101
+ catch {
102
+ // best-effort shutdown
103
+ }
104
+ }
105
+ this.readBuffer.clear();
106
+ }
107
+ async send(message) {
108
+ const child = this.child;
109
+ if (!child)
110
+ throw new Error('MCP stdio transport is not connected');
111
+ const stdin = child.stdin;
112
+ if (!stdin)
113
+ throw new Error('MCP stdio transport stdin is not available');
114
+ await new Promise((resolve, reject) => {
115
+ const onError = (error) => {
116
+ stdin.off?.('error', onError);
117
+ reject(error);
118
+ };
119
+ stdin.once?.('error', onError);
120
+ if (stdin.write?.(serializeMessage(message))) {
121
+ stdin.off?.('error', onError);
122
+ resolve();
123
+ return;
124
+ }
125
+ stdin.once?.('drain', () => {
126
+ stdin.off?.('error', onError);
127
+ resolve();
128
+ });
129
+ });
130
+ }
131
+ processReadBuffer() {
132
+ while (true) {
133
+ try {
134
+ const message = this.readBuffer.readMessage();
135
+ if (message === null)
136
+ break;
137
+ this.onmessage?.(message);
138
+ }
139
+ catch (error) {
140
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
141
+ }
142
+ }
143
+ }
144
+ }
145
+ export function createMcpTransport(server) {
146
+ if (server.transport.type === 'http') {
147
+ return new StreamableHTTPClientTransport(new URL(server.transport.url), {
148
+ requestInit: {
149
+ headers: server.transport.headers,
150
+ },
151
+ });
152
+ }
153
+ const transport = new StrictStdioClientTransport({
154
+ command: server.transport.command,
155
+ args: server.transport.args,
156
+ env: server.transport.env,
157
+ cwd: server.transport.cwd,
158
+ });
159
+ transport.stderr.on('data', () => { });
160
+ return transport;
161
+ }
162
+ function unrefTimeout(ms) {
163
+ return new Promise((resolve) => {
164
+ const timer = setTimeout(resolve, ms);
165
+ timer.unref?.();
166
+ });
167
+ }
168
+ //# sourceMappingURL=transport-factory.js.map
@@ -0,0 +1,32 @@
1
+ import { expandHome, resolveRepoRelative, resolveUserRelative } from '../../extensions/paths.js';
2
+ export { McpCapabilitiesV2Schema, McpConfigV2Schema, McpServerEntryV2Schema, } from './schema-v2.js';
3
+ function resolvePathForScope(value, scope, repoRoot) {
4
+ if (!value)
5
+ return undefined;
6
+ const expanded = expandHome(value);
7
+ return scope === 'repo' ? resolveRepoRelative(repoRoot, expanded) : resolveUserRelative(expanded);
8
+ }
9
+ function defaultEnabled(scope) {
10
+ return scope === 'repo';
11
+ }
12
+ export function buildResolvedMcpServersV2(entries, repoRoot) {
13
+ return entries.map((entry) => {
14
+ const source = entry.entry;
15
+ const transport = source.transport.type === 'stdio'
16
+ ? {
17
+ ...source.transport,
18
+ cwd: resolvePathForScope(source.transport.cwd, entry.scope, repoRoot),
19
+ }
20
+ : source.transport;
21
+ return {
22
+ name: entry.key,
23
+ enabled: source.enabled ?? defaultEnabled(entry.scope),
24
+ transport,
25
+ auth: source.auth,
26
+ trust: source.trust,
27
+ capabilities: source.capabilities,
28
+ scope: entry.scope,
29
+ };
30
+ });
31
+ }
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,129 @@
1
+ import { z } from 'zod';
2
+ import { Phase } from '../../types/runtime.js';
3
+ const phases = Object.values(Phase);
4
+ const transportSchema = z.discriminatedUnion('type', [
5
+ z
6
+ .object({
7
+ type: z.literal('stdio'),
8
+ command: z.string().min(1),
9
+ args: z.array(z.string()).optional().default([]),
10
+ env: z.record(z.string(), z.string()),
11
+ cwd: z.string().optional(),
12
+ })
13
+ .strict(),
14
+ z
15
+ .object({
16
+ type: z.literal('http'),
17
+ url: z.string().url(),
18
+ headers: z.record(z.string(), z.string()).optional().default({}),
19
+ })
20
+ .strict(),
21
+ ]);
22
+ const authSchema = z
23
+ .object({
24
+ type: z.enum(['none', 'oauth']).optional().default('none'),
25
+ scopes: z.array(z.string()).optional().default([]),
26
+ })
27
+ .strict()
28
+ .default({ type: 'none', scopes: [] });
29
+ const toolsCapabilitySchema = z
30
+ .object({
31
+ exposeToModel: z.boolean().optional().default(false),
32
+ allow: z.array(z.string()).optional().default([]),
33
+ phases: z.array(z.enum(phases)).optional().default([]),
34
+ approval: z
35
+ .enum(['never', 'ask', 'write_requires_confirmation'])
36
+ .optional()
37
+ .default('ask'),
38
+ sideEffectOverrides: z
39
+ .record(z.string(), z.array(z.enum([
40
+ 'none',
41
+ 'fs_read',
42
+ 'fs_write',
43
+ 'runtime_write',
44
+ 'process',
45
+ 'network',
46
+ 'git_read',
47
+ 'git_write',
48
+ 'snapshot_mutate',
49
+ ])))
50
+ .optional(),
51
+ })
52
+ .strict();
53
+ const resourcesCapabilitySchema = z
54
+ .object({
55
+ allowUris: z.array(z.string()).optional().default([]),
56
+ autoInclude: z.boolean().optional().default(false),
57
+ subscribe: z.boolean().optional().default(false),
58
+ maxBytes: z.number().int().positive().optional().default(64_000),
59
+ ttlMs: z.number().int().nonnegative().optional().default(30_000),
60
+ })
61
+ .strict();
62
+ const promptsCapabilitySchema = z
63
+ .object({
64
+ exposeAs: z
65
+ .enum(['slash', 'recipe', 'none'])
66
+ .optional()
67
+ .default('none'),
68
+ allow: z.array(z.string()).optional().default([]),
69
+ })
70
+ .strict();
71
+ const rootsCapabilitySchema = z
72
+ .object({
73
+ mode: z
74
+ .enum(['none', 'repo', 'worktree'])
75
+ .optional()
76
+ .default('none'),
77
+ })
78
+ .strict();
79
+ const samplingCapabilitySchema = z
80
+ .object({
81
+ enabled: z.boolean().optional().default(false),
82
+ maxTokens: z.number().int().nonnegative().optional().default(0),
83
+ maxDepth: z.number().int().nonnegative().optional().default(0),
84
+ })
85
+ .strict();
86
+ const elicitationCapabilitySchema = z
87
+ .object({
88
+ enabled: z.boolean().optional().default(false),
89
+ })
90
+ .strict();
91
+ export const McpCapabilitiesV2Schema = z
92
+ .object({
93
+ tools: toolsCapabilitySchema.optional(),
94
+ resources: resourcesCapabilitySchema.optional(),
95
+ prompts: promptsCapabilitySchema.optional(),
96
+ roots: rootsCapabilitySchema.optional(),
97
+ sampling: samplingCapabilitySchema.optional(),
98
+ elicitation: elicitationCapabilitySchema.optional(),
99
+ })
100
+ .strict()
101
+ .optional()
102
+ .transform((value) => ({
103
+ tools: toolsCapabilitySchema.parse(value?.tools ?? {}),
104
+ resources: resourcesCapabilitySchema.parse(value?.resources ?? {}),
105
+ prompts: promptsCapabilitySchema.parse(value?.prompts ?? {}),
106
+ roots: rootsCapabilitySchema.parse(value?.roots ?? {}),
107
+ sampling: samplingCapabilitySchema.parse(value?.sampling ?? {}),
108
+ elicitation: elicitationCapabilitySchema.parse(value?.elicitation ?? {}),
109
+ }));
110
+ export const McpServerEntryV2Schema = z
111
+ .object({
112
+ enabled: z.boolean().optional(),
113
+ transport: transportSchema,
114
+ auth: authSchema,
115
+ trust: z.enum(['local', 'remote']).optional(),
116
+ capabilities: McpCapabilitiesV2Schema,
117
+ })
118
+ .strict()
119
+ .transform((value) => ({
120
+ ...value,
121
+ trust: value.trust ?? (value.transport.type === 'stdio' ? 'local' : 'remote'),
122
+ }));
123
+ export const McpConfigV2Schema = z
124
+ .object({
125
+ version: z.literal(2),
126
+ servers: z.record(z.string(), McpServerEntryV2Schema).optional().default({}),
127
+ })
128
+ .strict();
129
+ //# sourceMappingURL=schema-v2.js.map
@@ -0,0 +1,209 @@
1
+ import { redactSensitiveValue } from '../../security/redaction.js';
2
+ function isElicitationOptions(value) {
3
+ return Boolean(value &&
4
+ typeof value === 'object' &&
5
+ 'userInputProvider' in value &&
6
+ !('decideElicitation' in value));
7
+ }
8
+ function option(label, description) {
9
+ return { label, description: description || label };
10
+ }
11
+ function enumValues(schema) {
12
+ if (Array.isArray(schema.enum) && schema.enum.every((value) => typeof value === 'string')) {
13
+ return schema.enum;
14
+ }
15
+ if (Array.isArray(schema.oneOf) &&
16
+ schema.oneOf.every((value) => value &&
17
+ typeof value === 'object' &&
18
+ typeof value.const === 'string')) {
19
+ return schema.oneOf.map((value) => String(value.const));
20
+ }
21
+ return undefined;
22
+ }
23
+ function arrayEnumValues(schema) {
24
+ if (schema.type !== 'array' || !schema.items || typeof schema.items !== 'object') {
25
+ return undefined;
26
+ }
27
+ const items = schema.items;
28
+ if (Array.isArray(items.enum) && items.enum.every((value) => typeof value === 'string')) {
29
+ return items.enum;
30
+ }
31
+ if (Array.isArray(items.anyOf) &&
32
+ items.anyOf.every((value) => value &&
33
+ typeof value === 'object' &&
34
+ typeof value.const === 'string')) {
35
+ return items.anyOf.map((value) => String(value.const));
36
+ }
37
+ return undefined;
38
+ }
39
+ function questionHeader(key, schema) {
40
+ return String(schema.title || key);
41
+ }
42
+ function questionText(key, schema, required) {
43
+ const description = typeof schema.description === 'string' ? schema.description : '';
44
+ const suffix = required ? '' : ' (optional)';
45
+ return description || `${questionHeader(key, schema)}${suffix}`;
46
+ }
47
+ function buildQuestionForProperty(key, schema, required) {
48
+ if (schema.type === 'boolean') {
49
+ return {
50
+ question: questionText(key, schema, required),
51
+ header: questionHeader(key, schema),
52
+ options: [option('true', 'Yes'), option('false', 'No')],
53
+ multiSelect: false,
54
+ };
55
+ }
56
+ const values = schema.type === 'array' ? arrayEnumValues(schema) : enumValues(schema);
57
+ if (values && values.length >= 2 && values.length <= 4) {
58
+ return {
59
+ question: questionText(key, schema, required),
60
+ header: questionHeader(key, schema),
61
+ options: values.map((value) => option(value)),
62
+ multiSelect: schema.type === 'array',
63
+ };
64
+ }
65
+ return undefined;
66
+ }
67
+ function buildFormQuestions(params) {
68
+ const required = new Set(params.requestedSchema.required ?? []);
69
+ const questions = [];
70
+ const keyByQuestion = new Map();
71
+ for (const [key, schema] of Object.entries(params.requestedSchema.properties)) {
72
+ const question = buildQuestionForProperty(key, schema, required.has(key));
73
+ if (!question)
74
+ return undefined;
75
+ questions.push(question);
76
+ keyByQuestion.set(question.question, key);
77
+ }
78
+ if (questions.length === 0)
79
+ return undefined;
80
+ return { input: { questions }, keyByQuestion };
81
+ }
82
+ function buildUrlQuestion(params) {
83
+ const question = {
84
+ question: params.message,
85
+ header: 'MCP URL request',
86
+ options: [option('accept', params.url), option('decline', 'Decline')],
87
+ multiSelect: false,
88
+ };
89
+ return {
90
+ input: { questions: [question] },
91
+ keyByQuestion: new Map([[question.question, 'url']]),
92
+ };
93
+ }
94
+ function responseContent(output, keyByQuestion) {
95
+ const content = {};
96
+ for (const [question, answer] of Object.entries(output.answers)) {
97
+ const key = keyByQuestion.get(question);
98
+ if (!key)
99
+ continue;
100
+ if (answer.includes(',')) {
101
+ content[key] = answer
102
+ .split(',')
103
+ .map((part) => part.trim())
104
+ .filter(Boolean);
105
+ }
106
+ else if (answer === 'true') {
107
+ content[key] = true;
108
+ }
109
+ else if (answer === 'false') {
110
+ content[key] = false;
111
+ }
112
+ else {
113
+ content[key] = answer;
114
+ }
115
+ }
116
+ return content;
117
+ }
118
+ async function askUser(provider, input, options = {}) {
119
+ return provider.askUser(input, { signal: options.signal });
120
+ }
121
+ export class McpElicitationProvider {
122
+ policy;
123
+ userInputProvider;
124
+ constructor(optionsOrPolicy, userInputProvider) {
125
+ if (isElicitationOptions(optionsOrPolicy) || !optionsOrPolicy) {
126
+ this.userInputProvider = optionsOrPolicy?.userInputProvider;
127
+ return;
128
+ }
129
+ this.policy = optionsOrPolicy;
130
+ this.userInputProvider = userInputProvider;
131
+ }
132
+ async ask(input) {
133
+ if (!this.policy)
134
+ throw new Error('MCP_ELICITATION_POLICY_UNAVAILABLE');
135
+ const decision = this.policy.decideElicitation(input.server);
136
+ if (!decision.allowed)
137
+ throw new Error(decision.denyReason ?? 'MCP_ELICITATION_DENIED');
138
+ if (!this.userInputProvider)
139
+ throw new Error('MCP_ELICITATION_UNAVAILABLE');
140
+ const questions = input.questions.map((question) => ({
141
+ question: question.id,
142
+ header: question.label,
143
+ options: [option('answer', question.label), option('skip', 'Skip')],
144
+ multiSelect: false,
145
+ }));
146
+ const response = await askUser(this.userInputProvider, { questions });
147
+ return {
148
+ answers: response.answers,
149
+ audit: {
150
+ server: input.server,
151
+ questionCount: input.questions.length,
152
+ answeredCount: Object.keys(response.answers).length,
153
+ },
154
+ };
155
+ }
156
+ async elicit(params, options = {}) {
157
+ const mode = params.mode === 'url' ? 'url' : 'form';
158
+ const sanitizedRequest = redactSensitiveValue(params).value;
159
+ if (!this.userInputProvider) {
160
+ return {
161
+ result: { action: 'decline' },
162
+ audit: {
163
+ event: 'mcp.elicitation.create',
164
+ mode,
165
+ action: 'decline',
166
+ questionCount: 0,
167
+ request: sanitizedRequest,
168
+ deniedReason: 'provider_unavailable',
169
+ },
170
+ };
171
+ }
172
+ const built = mode === 'url'
173
+ ? buildUrlQuestion(params)
174
+ : buildFormQuestions(params);
175
+ if (!built) {
176
+ return {
177
+ result: { action: 'decline' },
178
+ audit: {
179
+ event: 'mcp.elicitation.create',
180
+ mode,
181
+ action: 'decline',
182
+ questionCount: 0,
183
+ request: sanitizedRequest,
184
+ deniedReason: 'unsupported_schema',
185
+ },
186
+ };
187
+ }
188
+ const output = await askUser(this.userInputProvider, built.input, { signal: options.signal });
189
+ const content = responseContent(output, built.keyByQuestion);
190
+ const action = mode === 'url' && content.url === 'decline'
191
+ ? 'decline'
192
+ : Object.keys(content).length > 0
193
+ ? 'accept'
194
+ : 'cancel';
195
+ const result = action === 'accept' ? { action, content: mode === 'url' ? undefined : content } : { action };
196
+ return {
197
+ result,
198
+ audit: {
199
+ event: 'mcp.elicitation.create',
200
+ mode,
201
+ action,
202
+ questionCount: built.input.questions.length,
203
+ request: sanitizedRequest,
204
+ response: redactSensitiveValue(result).value,
205
+ },
206
+ };
207
+ }
208
+ }
209
+ //# sourceMappingURL=elicitation-provider.js.map
@@ -0,0 +1,70 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ const WRITE_MODES = new Set(['patch', 'debug', 'autopilot']);
3
+ function rootName(kind, flowMode) {
4
+ if (kind === 'worktreeRoot')
5
+ return flowMode ? `${flowMode}-worktree` : 'worktree';
6
+ return 'repository';
7
+ }
8
+ function toRoot(path, kind, flowMode) {
9
+ return {
10
+ uri: pathToFileURL(path).toString(),
11
+ name: rootName(kind, flowMode),
12
+ _meta: {
13
+ kind,
14
+ flowMode,
15
+ },
16
+ };
17
+ }
18
+ function mapMode(input) {
19
+ if (input.mode === 'none')
20
+ return 'none';
21
+ if (input.mode === 'worktree')
22
+ return 'write';
23
+ if (input.mode === 'repo') {
24
+ return WRITE_MODES.has(input.flowMode ?? 'patch') ? 'write' : 'read-only';
25
+ }
26
+ return input.mode ?? 'none';
27
+ }
28
+ function buildResult(input) {
29
+ const mode = mapMode(input);
30
+ const audit = {
31
+ event: 'mcp.roots.list',
32
+ mode,
33
+ flowMode: input.flowMode,
34
+ exposed: [],
35
+ };
36
+ if (mode === 'none') {
37
+ audit.deniedReason = 'none_mode';
38
+ return { roots: [], _meta: { audit } };
39
+ }
40
+ if (mode === 'read-only') {
41
+ audit.exposed.push('repoRoot');
42
+ return {
43
+ roots: [toRoot(input.repoRoot, 'repoRoot', input.flowMode)],
44
+ _meta: { audit },
45
+ };
46
+ }
47
+ if (!input.worktreeRoot) {
48
+ audit.deniedReason = 'missing_worktree_root';
49
+ return { roots: [], _meta: { audit } };
50
+ }
51
+ audit.exposed.push('worktreeRoot');
52
+ return {
53
+ roots: [toRoot(input.worktreeRoot, 'worktreeRoot', input.flowMode)],
54
+ _meta: { audit },
55
+ };
56
+ }
57
+ export class McpRootsProvider {
58
+ options;
59
+ constructor(options) {
60
+ this.options = options;
61
+ }
62
+ listRoots(input) {
63
+ if (input)
64
+ return buildResult(input).roots;
65
+ if (!this.options)
66
+ return buildResult({ repoRoot: '', mode: 'none' });
67
+ return buildResult(this.options);
68
+ }
69
+ }
70
+ //# sourceMappingURL=roots-provider.js.map