@danieltmn/tamnora 0.16.1 → 0.18.0
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/CHANGELOG.md +45 -0
- package/README.md +58 -4
- package/SECURITY.md +8 -0
- package/harness/packages/cli/src/commands/mcp.js +31 -4
- package/harness/packages/cli/src/commands/repl.js +7 -2
- package/harness/packages/cli/src/commands/run.js +5 -2
- package/harness/packages/cli/src/commands/tui.js +9 -2
- package/harness/packages/cli/src/lib/runtime.js +23 -1
- package/harness/packages/cli/test/tunnel.test.js +13 -1
- package/harness/packages/core/src/agent/modes.js +3 -0
- package/harness/packages/core/src/config/index.js +3 -1
- package/harness/packages/core/src/index.js +17 -3
- package/harness/packages/core/src/mcp/catalog.js +79 -0
- package/harness/packages/core/src/mcp/index.js +14 -2
- package/harness/packages/core/src/mcp/registry.js +29 -1
- package/harness/packages/core/src/procs/detect.js +59 -8
- package/harness/packages/core/src/procs/kill.js +26 -5
- package/harness/packages/core/src/redact.js +25 -0
- package/harness/packages/core/src/skills/catalog.js +78 -0
- package/harness/packages/core/src/skills/remote.js +51 -0
- package/harness/packages/core/src/tools/executors.js +93 -2
- package/harness/packages/core/src/tools/index.js +27 -6
- package/harness/packages/core/src/tools/labels.js +2 -0
- package/harness/packages/core/src/tools/mcp.js +91 -0
- package/harness/packages/core/src/tools/permissions.js +53 -8
- package/harness/packages/core/src/tools/registry.js +14 -7
- package/harness/packages/core/src/tools/schema.js +86 -0
- package/harness/packages/core/src/tts/edge.js +28 -7
- package/harness/packages/core/src/tunnel.js +51 -5
- package/harness/packages/core/test/compact.test.js +2 -0
- package/harness/packages/core/test/labels.test.js +1 -0
- package/harness/packages/core/test/mcp-registry.test.js +37 -1
- package/harness/packages/core/test/mcp-routing.test.js +115 -0
- package/harness/packages/core/test/procs.test.js +70 -2
- package/harness/packages/core/test/redact.test.js +22 -0
- package/harness/packages/core/test/skills.test.js +11 -0
- package/harness/packages/core/test/tools.test.js +97 -3
- package/harness/packages/server/public/app.js +629 -28
- package/harness/packages/server/public/index.html +44 -2
- package/harness/packages/server/public/styles.css +106 -7
- package/harness/packages/server/src/lib/agentTools.js +45 -3
- package/harness/packages/server/src/lib/channels/telegram.js +2 -0
- package/harness/packages/server/src/lib/procs.js +9 -3
- package/harness/packages/server/src/lib/prompt.js +4 -1
- package/harness/packages/server/src/lib/questions.js +72 -0
- package/harness/packages/server/src/lib/runManager.js +29 -2
- package/harness/packages/server/src/lib/scheduler.js +12 -1
- package/harness/packages/server/src/lib/tasks.js +3 -1
- package/harness/packages/server/src/routes/chat.js +40 -3
- package/harness/packages/server/src/routes/mcp.js +54 -2
- package/harness/packages/server/src/routes/preview.js +49 -28
- package/harness/packages/server/src/routes/procs.js +20 -3
- package/harness/packages/server/src/routes/providers.js +12 -0
- package/harness/packages/server/src/routes/sessions.js +3 -1
- package/harness/packages/server/src/routes/skills.js +48 -1
- package/harness/packages/server/src/routes/state.js +8 -1
- package/harness/packages/server/src/server.js +37 -1
- package/harness/packages/server/test/preview.test.js +111 -0
- package/harness/packages/server/test/server.test.js +285 -34
- package/harness/packages/server/test/telegram-features.test.js +2 -0
- package/harness/packages/server/test/telegram-voz.test.js +2 -0
- package/harness/packages/server/test/telegram.test.js +2 -0
- package/harness/tamnora.example.json +3 -1
- package/package.json +1 -1
|
@@ -262,7 +262,73 @@ test('keeps a run alive after the client disconnects and can stop it', async ()
|
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
test('interrupt cancels the running turn and starts the new message right away', async () => {
|
|
266
|
+
const calls = { n: 0 };
|
|
267
|
+
let abortedFirst = false;
|
|
268
|
+
const provider = {
|
|
269
|
+
id: 'interrupt',
|
|
270
|
+
async streamChat(_config, { onEvent, signal }) {
|
|
271
|
+
calls.n += 1;
|
|
272
|
+
if (calls.n === 1) {
|
|
273
|
+
onEvent?.({ type: 'text', delta: 'trabajando', full: 'trabajando' });
|
|
274
|
+
await new Promise((resolve, reject) => {
|
|
275
|
+
const timer = setTimeout(resolve, 5000);
|
|
276
|
+
const onAbort = () => {
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
abortedFirst = true;
|
|
279
|
+
reject(Object.assign(new Error('aborted'), { name: 'AbortError' }));
|
|
280
|
+
};
|
|
281
|
+
if (signal?.aborted) return onAbort();
|
|
282
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
onEvent?.({ type: 'text', delta: 'listo', full: 'listo' });
|
|
286
|
+
return { content: 'listo', reasoning: '', toolCalls: [] };
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
const { instance, base } = await startServer({ resolveProvider: () => ({ provider, config: {} }) });
|
|
290
|
+
const json = { 'Content-Type': 'application/json' };
|
|
291
|
+
try {
|
|
292
|
+
const created = await (
|
|
293
|
+
await fetch(`${base}/api/sessions`, {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: json,
|
|
296
|
+
body: JSON.stringify({ model: 'm' }),
|
|
297
|
+
})
|
|
298
|
+
).json();
|
|
299
|
+
const id = created.session.id;
|
|
300
|
+
|
|
301
|
+
const first = fetch(`${base}/api/chat`, {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: json,
|
|
304
|
+
body: JSON.stringify({ input: 'uno', model: 'm', sessionId: id }),
|
|
305
|
+
})
|
|
306
|
+
.then((res) => res.text())
|
|
307
|
+
.catch(() => null);
|
|
308
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
309
|
+
|
|
310
|
+
const second = await (
|
|
311
|
+
await fetch(`${base}/api/chat`, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: json,
|
|
314
|
+
body: JSON.stringify({ input: 'dos', model: 'm', sessionId: id, interrupt: true }),
|
|
315
|
+
})
|
|
316
|
+
).text();
|
|
317
|
+
|
|
318
|
+
assert.match(second, /event: text/);
|
|
319
|
+
assert.match(second, /listo/);
|
|
320
|
+
assert.equal(abortedFirst, true);
|
|
321
|
+
await first;
|
|
322
|
+
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
324
|
+
const after = await (await fetch(`${base}/api/sessions/${id}`)).json();
|
|
325
|
+
assert.equal(after.running, false);
|
|
326
|
+
} finally {
|
|
327
|
+
await instance.close();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/** Lee un SSE hasta que `until(data)` se cumpla, y devuelve lo acumulado. */
|
|
266
332
|
async function readSse(url, options, until, timeoutMs = 5000) {
|
|
267
333
|
const controller = new AbortController();
|
|
268
334
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -877,13 +943,74 @@ test('manages MCP servers from the console', async () => {
|
|
|
877
943
|
});
|
|
878
944
|
assert.equal(bad.status, 400);
|
|
879
945
|
|
|
880
|
-
res = await fetch(`${base}/api/mcp/servers/echo`, { method: 'DELETE' });
|
|
881
|
-
assert.equal(res.status, 200);
|
|
882
|
-
data = await res.json();
|
|
883
|
-
assert.equal(data.servers.length, 0);
|
|
884
|
-
} finally {
|
|
885
|
-
await instance.close();
|
|
886
|
-
}
|
|
946
|
+
res = await fetch(`${base}/api/mcp/servers/echo`, { method: 'DELETE' });
|
|
947
|
+
assert.equal(res.status, 200);
|
|
948
|
+
data = await res.json();
|
|
949
|
+
assert.equal(data.servers.length, 0);
|
|
950
|
+
} finally {
|
|
951
|
+
await instance.close();
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
test('sirve el catálogo de MCP/skills y los instala deshabilitados', async () => {
|
|
956
|
+
const { instance, base } = await startServer();
|
|
957
|
+
const json = { 'Content-Type': 'application/json' };
|
|
958
|
+
try {
|
|
959
|
+
const catalog = await (await fetch(`${base}/api/mcp/catalog`)).json();
|
|
960
|
+
assert.ok(catalog.catalog.some((e) => e.id === 'playwright' && e.installed === false));
|
|
961
|
+
|
|
962
|
+
const add = await fetch(`${base}/api/mcp/import`, {
|
|
963
|
+
method: 'POST',
|
|
964
|
+
headers: json,
|
|
965
|
+
body: JSON.stringify({ catalog: 'playwright' }),
|
|
966
|
+
});
|
|
967
|
+
assert.equal(add.status, 200);
|
|
968
|
+
const added = await add.json();
|
|
969
|
+
assert.equal(added.imported, 'playwright');
|
|
970
|
+
assert.equal(added.servers.find((s) => s.id === 'playwright').enabled, false);
|
|
971
|
+
|
|
972
|
+
const again = await (await fetch(`${base}/api/mcp/catalog`)).json();
|
|
973
|
+
assert.equal(again.catalog.find((e) => e.id === 'playwright').installed, true);
|
|
974
|
+
|
|
975
|
+
const skills = await (await fetch(`${base}/api/skills/catalog`)).json();
|
|
976
|
+
assert.ok(skills.catalog.some((e) => e.id === 'code-review' && e.installed === false));
|
|
977
|
+
const skillAdd = await fetch(`${base}/api/skills`, {
|
|
978
|
+
method: 'POST',
|
|
979
|
+
headers: json,
|
|
980
|
+
body: JSON.stringify({ id: 'code-review' }),
|
|
981
|
+
});
|
|
982
|
+
assert.equal(skillAdd.status, 200);
|
|
983
|
+
const skillData = await skillAdd.json();
|
|
984
|
+
assert.equal(skillData.installed, 'code-review');
|
|
985
|
+
assert.equal(skillData.skills.find((s) => s.id === 'code-review').enabled, false);
|
|
986
|
+
|
|
987
|
+
const unknown = await fetch(`${base}/api/mcp/import`, {
|
|
988
|
+
method: 'POST',
|
|
989
|
+
headers: json,
|
|
990
|
+
body: JSON.stringify({ catalog: 'nope' }),
|
|
991
|
+
});
|
|
992
|
+
assert.equal(unknown.status, 404);
|
|
993
|
+
const unknownSkill = await fetch(`${base}/api/skills`, {
|
|
994
|
+
method: 'POST',
|
|
995
|
+
headers: json,
|
|
996
|
+
body: JSON.stringify({ id: 'nope' }),
|
|
997
|
+
});
|
|
998
|
+
assert.equal(unknownSkill.status, 404);
|
|
999
|
+
} finally {
|
|
1000
|
+
await instance.close();
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
test('los buscadores devuelven vacío sin query', async () => {
|
|
1005
|
+
const { instance, base } = await startServer();
|
|
1006
|
+
try {
|
|
1007
|
+
const mcp = await (await fetch(`${base}/api/mcp/search`)).json();
|
|
1008
|
+
assert.deepEqual(mcp.results, []);
|
|
1009
|
+
const skills = await (await fetch(`${base}/api/skills/search`)).json();
|
|
1010
|
+
assert.deepEqual(skills.results, []);
|
|
1011
|
+
} finally {
|
|
1012
|
+
await instance.close();
|
|
1013
|
+
}
|
|
887
1014
|
});
|
|
888
1015
|
|
|
889
1016
|
test('MCP: el env se enmascara y no se pisa al guardar sin cambios', async () => {
|
|
@@ -949,6 +1076,36 @@ test('guarda los límites del agente desde Ajustes', async () => {
|
|
|
949
1076
|
}
|
|
950
1077
|
});
|
|
951
1078
|
|
|
1079
|
+
test('guarda el modo de carga y las tools fijas de MCP', async () => {
|
|
1080
|
+
const { instance, base } = await startServer();
|
|
1081
|
+
const json = { 'Content-Type': 'application/json' };
|
|
1082
|
+
try {
|
|
1083
|
+
const res = await fetch(`${base}/api/settings`, {
|
|
1084
|
+
method: 'PUT',
|
|
1085
|
+
headers: json,
|
|
1086
|
+
body: JSON.stringify({ mcpMode: 'lazy', mcpPinned: 'mcp__postgres__query, postgres' }),
|
|
1087
|
+
});
|
|
1088
|
+
const data = await res.json();
|
|
1089
|
+
assert.equal(data.settings.mcpMode, 'lazy');
|
|
1090
|
+
assert.deepEqual(data.settings.mcpPinned, ['mcp__postgres__query', 'postgres']);
|
|
1091
|
+
|
|
1092
|
+
const mcp = await (await fetch(`${base}/api/mcp`)).json();
|
|
1093
|
+
assert.equal(mcp.mode, 'lazy');
|
|
1094
|
+
assert.deepEqual(mcp.pinned, ['mcp__postgres__query', 'postgres']);
|
|
1095
|
+
assert.equal(mcp.threshold, 12);
|
|
1096
|
+
assert.equal(mcp.toolCount, null);
|
|
1097
|
+
|
|
1098
|
+
const bad = await fetch(`${base}/api/settings`, {
|
|
1099
|
+
method: 'PUT',
|
|
1100
|
+
headers: json,
|
|
1101
|
+
body: JSON.stringify({ mcpMode: 'nope' }),
|
|
1102
|
+
});
|
|
1103
|
+
assert.equal((await bad.json()).settings.mcpMode, 'auto');
|
|
1104
|
+
} finally {
|
|
1105
|
+
await instance.close();
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
952
1109
|
test('imports an MCP server from a JSON snippet', async () => {
|
|
953
1110
|
const { instance, base } = await startServer();
|
|
954
1111
|
const json = { 'Content-Type': 'application/json' };
|
|
@@ -1561,20 +1718,38 @@ test('runs a console shell command and kills a port', async () => {
|
|
|
1561
1718
|
});
|
|
1562
1719
|
assert.equal(missing.status, 400);
|
|
1563
1720
|
|
|
1564
|
-
const badPort = await fetch(`${base}/api/procs/kill`, {
|
|
1565
|
-
method: 'POST',
|
|
1566
|
-
headers: json,
|
|
1567
|
-
body: JSON.stringify({ port: 0 }),
|
|
1568
|
-
});
|
|
1569
|
-
assert.equal(badPort.status, 400);
|
|
1570
|
-
|
|
1571
|
-
const none = await fetch(`${base}/api/procs/kill`, {
|
|
1572
|
-
method: 'POST',
|
|
1573
|
-
headers: json,
|
|
1574
|
-
body: JSON.stringify({ port: 47555 }),
|
|
1575
|
-
});
|
|
1576
|
-
assert.equal(none.status, 200);
|
|
1577
|
-
assert.deepEqual((await none.json()).pids, []);
|
|
1721
|
+
const badPort = await fetch(`${base}/api/procs/kill`, {
|
|
1722
|
+
method: 'POST',
|
|
1723
|
+
headers: json,
|
|
1724
|
+
body: JSON.stringify({ port: 0 }),
|
|
1725
|
+
});
|
|
1726
|
+
assert.equal(badPort.status, 400);
|
|
1727
|
+
|
|
1728
|
+
const none = await fetch(`${base}/api/procs/kill`, {
|
|
1729
|
+
method: 'POST',
|
|
1730
|
+
headers: json,
|
|
1731
|
+
body: JSON.stringify({ port: 47555 }),
|
|
1732
|
+
});
|
|
1733
|
+
assert.equal(none.status, 200);
|
|
1734
|
+
assert.deepEqual((await none.json()).pids, []);
|
|
1735
|
+
|
|
1736
|
+
const status = await fetch(`${base}/api/ports/47555`);
|
|
1737
|
+
assert.equal(status.status, 200);
|
|
1738
|
+
const info = await status.json();
|
|
1739
|
+
assert.equal(info.port, 47555);
|
|
1740
|
+
assert.equal(info.listening, false);
|
|
1741
|
+
assert.deepEqual(info.pids, []);
|
|
1742
|
+
|
|
1743
|
+
const aliasKill = await fetch(`${base}/api/ports/kill`, {
|
|
1744
|
+
method: 'POST',
|
|
1745
|
+
headers: json,
|
|
1746
|
+
body: JSON.stringify({ port: 47555 }),
|
|
1747
|
+
});
|
|
1748
|
+
assert.equal(aliasKill.status, 200);
|
|
1749
|
+
assert.deepEqual((await aliasKill.json()).pids, []);
|
|
1750
|
+
|
|
1751
|
+
const badStatus = await fetch(`${base}/api/ports/0`);
|
|
1752
|
+
assert.equal(badStatus.status, 400);
|
|
1578
1753
|
} finally {
|
|
1579
1754
|
await instance.close();
|
|
1580
1755
|
}
|
|
@@ -1982,18 +2157,89 @@ test('asks for approval before running a gated tool and runs it once approved',
|
|
|
1982
2157
|
}
|
|
1983
2158
|
});
|
|
1984
2159
|
|
|
1985
|
-
test('rejects unknown permission requests', async () => {
|
|
1986
|
-
const { instance, base } = await startServer();
|
|
1987
|
-
try {
|
|
1988
|
-
const res = await fetch(`${base}/api/permissions/does-not-exist`, {
|
|
1989
|
-
method: 'POST',
|
|
1990
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1991
|
-
body: JSON.stringify({ allow: true }),
|
|
1992
|
-
});
|
|
1993
|
-
assert.equal(res.status, 404);
|
|
1994
|
-
} finally {
|
|
1995
|
-
await instance.close();
|
|
1996
|
-
}
|
|
2160
|
+
test('rejects unknown permission requests', async () => {
|
|
2161
|
+
const { instance, base } = await startServer();
|
|
2162
|
+
try {
|
|
2163
|
+
const res = await fetch(`${base}/api/permissions/does-not-exist`, {
|
|
2164
|
+
method: 'POST',
|
|
2165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2166
|
+
body: JSON.stringify({ allow: true }),
|
|
2167
|
+
});
|
|
2168
|
+
assert.equal(res.status, 404);
|
|
2169
|
+
} finally {
|
|
2170
|
+
await instance.close();
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
test('ask_user muestra un formulario y recibe las respuestas', async () => {
|
|
2175
|
+
const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-askuser-'));
|
|
2176
|
+
let calls = 0;
|
|
2177
|
+
let captured = null;
|
|
2178
|
+
const provider = {
|
|
2179
|
+
id: 'formy',
|
|
2180
|
+
async streamChat(_config, opts) {
|
|
2181
|
+
calls += 1;
|
|
2182
|
+
if (calls === 1) {
|
|
2183
|
+
return {
|
|
2184
|
+
content: '',
|
|
2185
|
+
reasoning: '',
|
|
2186
|
+
toolCalls: [
|
|
2187
|
+
{
|
|
2188
|
+
id: 'q1',
|
|
2189
|
+
function: {
|
|
2190
|
+
name: 'ask_user',
|
|
2191
|
+
arguments: JSON.stringify({
|
|
2192
|
+
questions: [
|
|
2193
|
+
{ id: 'nombre', label: '¿Tu nombre?' },
|
|
2194
|
+
{ id: 'color', label: 'Color', type: 'select', options: ['rojo', 'verde'] },
|
|
2195
|
+
],
|
|
2196
|
+
}),
|
|
2197
|
+
},
|
|
2198
|
+
},
|
|
2199
|
+
],
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
captured = opts.messages;
|
|
2203
|
+
return { content: 'Gracias', reasoning: '', toolCalls: [] };
|
|
2204
|
+
},
|
|
2205
|
+
};
|
|
2206
|
+
const { instance, base } = await startServer({ cwd, resolveProvider: () => ({ provider, config: {} }) });
|
|
2207
|
+
try {
|
|
2208
|
+
const res = await fetch(`${base}/api/chat`, {
|
|
2209
|
+
method: 'POST',
|
|
2210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2211
|
+
body: JSON.stringify({ input: 'preguntame', model: 'm' }),
|
|
2212
|
+
});
|
|
2213
|
+
const reader = res.body.getReader();
|
|
2214
|
+
const decoder = new TextDecoder();
|
|
2215
|
+
let buffer = '';
|
|
2216
|
+
let askId = null;
|
|
2217
|
+
let done = false;
|
|
2218
|
+
while (!done) {
|
|
2219
|
+
const { value } = await reader.read();
|
|
2220
|
+
if (!value) break;
|
|
2221
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2222
|
+
if (!askId && buffer.includes('event: question')) {
|
|
2223
|
+
const match = buffer.match(/"askId":"([0-9a-f-]+)"/);
|
|
2224
|
+
if (match) {
|
|
2225
|
+
askId = match[1];
|
|
2226
|
+
const answer = await fetch(`${base}/api/questions/${askId}`, {
|
|
2227
|
+
method: 'POST',
|
|
2228
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2229
|
+
body: JSON.stringify({ answers: { nombre: 'Ana', color: 'verde' } }),
|
|
2230
|
+
});
|
|
2231
|
+
assert.equal(answer.status, 200);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
if (buffer.includes('event: done')) done = true;
|
|
2235
|
+
}
|
|
2236
|
+
assert.ok(askId, 'emitió el evento question');
|
|
2237
|
+
const toolMsg = (captured ?? []).find((m) => m.role === 'tool');
|
|
2238
|
+
assert.match(String(toolMsg?.content ?? ''), /Ana/);
|
|
2239
|
+
assert.match(String(toolMsg?.content ?? ''), /verde/);
|
|
2240
|
+
} finally {
|
|
2241
|
+
await instance.close();
|
|
2242
|
+
}
|
|
1997
2243
|
});
|
|
1998
2244
|
|
|
1999
2245
|
async function readEvents(res, onFrame) {
|
|
@@ -2237,6 +2483,7 @@ test('plan mode exposes only read-only tools and adds guidance', async () => {
|
|
|
2237
2483
|
})
|
|
2238
2484
|
).text();
|
|
2239
2485
|
assert.deepEqual(captured.tools, [
|
|
2486
|
+
'ask_user',
|
|
2240
2487
|
'console_errors',
|
|
2241
2488
|
'fetch_url',
|
|
2242
2489
|
'git_diff',
|
|
@@ -2247,9 +2494,13 @@ test('plan mode exposes only read-only tools and adds guidance', async () => {
|
|
|
2247
2494
|
'list',
|
|
2248
2495
|
'list_mcp',
|
|
2249
2496
|
'list_skills',
|
|
2497
|
+
'offer_choices',
|
|
2250
2498
|
'preview_url',
|
|
2251
2499
|
'read',
|
|
2252
2500
|
'screenshot',
|
|
2501
|
+
'search_mcp',
|
|
2502
|
+
'search_skills',
|
|
2503
|
+
'show_plan',
|
|
2253
2504
|
'skill',
|
|
2254
2505
|
'speak',
|
|
2255
2506
|
'task',
|
|
@@ -79,6 +79,8 @@ function makeCtx(cwd, { app = {}, presence } = {}) {
|
|
|
79
79
|
effectiveProviderId: () => 'fake',
|
|
80
80
|
effectiveModel: () => 'm',
|
|
81
81
|
effectiveMode: () => 'agent',
|
|
82
|
+
effectiveMcpMode: () => 'auto',
|
|
83
|
+
effectiveMcpPinned: () => [],
|
|
82
84
|
providerSettings: () => ({ baseUrl: 'http://127.0.0.1:1', apiKey: '' }),
|
|
83
85
|
apiKeyFor: () => '',
|
|
84
86
|
customProviderDefs: () => [],
|
|
@@ -81,6 +81,8 @@ function makeCtx(cwd, app = {}) {
|
|
|
81
81
|
effectiveProviderId: () => 'fake',
|
|
82
82
|
effectiveModel: () => 'm',
|
|
83
83
|
effectiveMode: () => 'agent',
|
|
84
|
+
effectiveMcpMode: () => 'auto',
|
|
85
|
+
effectiveMcpPinned: () => [],
|
|
84
86
|
providerSettings: () => ({ baseUrl: 'http://127.0.0.1:1', apiKey: '' }),
|
|
85
87
|
apiKeyFor: () => '',
|
|
86
88
|
customProviderDefs: () => [],
|
|
@@ -48,6 +48,8 @@ function makeCtx(cwd) {
|
|
|
48
48
|
effectiveProviderId: () => 'fake',
|
|
49
49
|
effectiveModel: () => 'm',
|
|
50
50
|
effectiveMode: () => 'agent',
|
|
51
|
+
effectiveMcpMode: () => 'auto',
|
|
52
|
+
effectiveMcpPinned: () => [],
|
|
51
53
|
providerSettings: () => ({ baseUrl: 'http://127.0.0.1:1', apiKey: '' }),
|
|
52
54
|
apiKeyFor: () => '',
|
|
53
55
|
customProviderDefs: () => [],
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"plugins": [],
|
|
16
16
|
"tools": [],
|
|
17
17
|
"permissions": {},
|
|
18
|
+
"mcpMode": "auto",
|
|
19
|
+
"mcpPinned": [],
|
|
18
20
|
"mcp": [
|
|
19
21
|
{
|
|
20
22
|
"id": "echo",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"enabled": true,
|
|
52
54
|
"allow": ["npm", "node", "npx", "pnpm", "yarn", "bun", "php", "python", "composer"]
|
|
53
55
|
},
|
|
54
|
-
"preview": { "provider": "
|
|
56
|
+
"preview": { "provider": "tailscale", "tailscalePort": 8443, "fallback": true },
|
|
55
57
|
"telegram": { "token": "", "allowedChatIds": [], "transcribeModel": "whisper-1" },
|
|
56
58
|
"agents": { "maxRounds": 6 }
|
|
57
59
|
}
|
package/package.json
CHANGED