@danieltmn/tamnora 0.11.2 → 0.12.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 +31 -0
- package/README.md +7 -1
- package/harness/packages/cli/src/commands/repl.js +18 -5
- package/harness/packages/cli/src/commands/run.js +11 -2
- package/harness/packages/cli/src/commands/tui.js +13 -3
- package/harness/packages/cli/src/lib/runtime.js +2 -0
- package/harness/packages/cli/src/tui/app.js +22 -0
- package/harness/packages/core/src/agent/compact.js +38 -0
- package/harness/packages/core/src/agent/context.js +13 -8
- package/harness/packages/core/src/agent/runAgent.js +15 -2
- package/harness/packages/core/src/agent/scope.js +15 -0
- package/harness/packages/core/src/config/index.js +4 -0
- package/harness/packages/core/src/index.js +10 -1
- package/harness/packages/core/src/providers/cache.js +29 -0
- package/harness/packages/core/src/providers/ollama.js +3 -2
- package/harness/packages/core/src/providers/openaiCompatible.js +9 -2
- package/harness/packages/core/src/providers/openrouter.js +6 -1
- package/harness/packages/core/src/providers/presets.js +7 -4
- package/harness/packages/core/src/providers/stream.js +45 -4
- package/harness/packages/core/src/skills/index.js +21 -0
- package/harness/packages/core/src/tools/executors.js +21 -13
- package/harness/packages/core/src/tools/index.js +2 -2
- package/harness/packages/core/src/tools/registry.js +3 -2
- package/harness/packages/core/test/agent.test.js +38 -0
- package/harness/packages/core/test/compact.test.js +33 -0
- package/harness/packages/core/test/context.test.js +14 -0
- package/harness/packages/core/test/providers.test.js +66 -0
- package/harness/packages/core/test/skills.test.js +10 -1
- package/harness/packages/core/test/stream.test.js +20 -0
- package/harness/packages/core/test/tools.test.js +18 -0
- package/harness/packages/server/public/app.js +93 -38
- package/harness/packages/server/public/index.html +8 -2
- package/harness/packages/server/public/markdown.js +57 -3
- package/harness/packages/server/public/styles.css +117 -13
- package/harness/packages/server/src/lib/agentRun.js +7 -7
- package/harness/packages/server/src/lib/agentTools.js +18 -3
- package/harness/packages/server/src/lib/approvals.js +7 -0
- package/harness/packages/server/src/lib/channels/telegram.js +9 -1
- package/harness/packages/server/src/lib/compact.js +77 -0
- package/harness/packages/server/src/lib/prompt.js +4 -1
- package/harness/packages/server/src/lib/skills.js +12 -1
- package/harness/packages/server/src/lib/tasks.js +2 -1
- package/harness/packages/server/src/routes/chat.js +21 -2
- package/harness/packages/server/src/routes/providers.js +4 -1
- package/harness/packages/server/src/routes/sessions.js +4 -50
- package/harness/packages/server/src/server.js +8 -2
- package/harness/packages/server/test/markdown.test.js +19 -2
- package/harness/packages/server/test/scope.test.js +68 -0
- package/harness/packages/server/test/server.test.js +130 -49
- package/package.json +1 -1
|
@@ -1016,41 +1016,119 @@ test('manages skills from the console', async () => {
|
|
|
1016
1016
|
}
|
|
1017
1017
|
});
|
|
1018
1018
|
|
|
1019
|
-
test('
|
|
1020
|
-
let captured = '';
|
|
1021
|
-
const captureProvider = {
|
|
1022
|
-
id: 'capture',
|
|
1023
|
-
async streamChat(_config, { messages }) {
|
|
1024
|
-
captured = messages?.[0]?.content ?? '';
|
|
1025
|
-
return { content: 'ok', reasoning: '', toolCalls: [] };
|
|
1026
|
-
},
|
|
1027
|
-
};
|
|
1028
|
-
const { instance, base } = await startServer({ resolveProvider: () => ({ provider: captureProvider, config: {} }) });
|
|
1029
|
-
const json = { 'Content-Type': 'application/json' };
|
|
1030
|
-
const chat = (sessionId) =>
|
|
1031
|
-
fetch(`${base}/api/chat`, { method: 'POST', headers: json, body: JSON.stringify({ sessionId, model: 'm', input: 'hola' }) });
|
|
1032
|
-
const newSession = async () => {
|
|
1033
|
-
const created = await (
|
|
1034
|
-
await fetch(`${base}/api/sessions`, { method: 'POST', headers: json, body: JSON.stringify({ title: 't', model: 'm' }) })
|
|
1035
|
-
).json();
|
|
1036
|
-
return created.session.id;
|
|
1037
|
-
};
|
|
1038
|
-
try {
|
|
1039
|
-
await fetch(`${base}/api/skills/demo`, {
|
|
1040
|
-
method: 'PUT',
|
|
1041
|
-
headers: json,
|
|
1042
|
-
body: JSON.stringify({ name: 'Demo', body: 'Regla secreta de la skill.' }),
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
await
|
|
1053
|
-
|
|
1019
|
+
test('skills: indice lazy por defecto (sin body)', async () => {
|
|
1020
|
+
let captured = '';
|
|
1021
|
+
const captureProvider = {
|
|
1022
|
+
id: 'capture',
|
|
1023
|
+
async streamChat(_config, { messages }) {
|
|
1024
|
+
captured = messages?.[0]?.content ?? '';
|
|
1025
|
+
return { content: 'ok', reasoning: '', toolCalls: [] };
|
|
1026
|
+
},
|
|
1027
|
+
};
|
|
1028
|
+
const { instance, base } = await startServer({ resolveProvider: () => ({ provider: captureProvider, config: {} }) });
|
|
1029
|
+
const json = { 'Content-Type': 'application/json' };
|
|
1030
|
+
const chat = (sessionId) =>
|
|
1031
|
+
fetch(`${base}/api/chat`, { method: 'POST', headers: json, body: JSON.stringify({ sessionId, model: 'm', input: 'hola' }) });
|
|
1032
|
+
const newSession = async () => {
|
|
1033
|
+
const created = await (
|
|
1034
|
+
await fetch(`${base}/api/sessions`, { method: 'POST', headers: json, body: JSON.stringify({ title: 't', model: 'm' }) })
|
|
1035
|
+
).json();
|
|
1036
|
+
return created.session.id;
|
|
1037
|
+
};
|
|
1038
|
+
try {
|
|
1039
|
+
await fetch(`${base}/api/skills/demo`, {
|
|
1040
|
+
method: 'PUT',
|
|
1041
|
+
headers: json,
|
|
1042
|
+
body: JSON.stringify({ name: 'Demo', body: 'Regla secreta de la skill.' }),
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// Lazy: se inyecta el indice (id), no el cuerpo.
|
|
1046
|
+
await (await chat(await newSession())).text();
|
|
1047
|
+
assert.match(captured, /demo/);
|
|
1048
|
+
assert.doesNotMatch(captured, /Regla secreta de la skill/);
|
|
1049
|
+
|
|
1050
|
+
// Deshabilitada: no aparece ni en el indice.
|
|
1051
|
+
await fetch(`${base}/api/skills/demo/toggle`, { method: 'POST' });
|
|
1052
|
+
await (await chat(await newSession())).text();
|
|
1053
|
+
assert.doesNotMatch(captured, /demo/);
|
|
1054
|
+
} finally {
|
|
1055
|
+
await instance.close();
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test('skills: modo eager inyecta el body en el system prompt', async () => {
|
|
1060
|
+
let captured = '';
|
|
1061
|
+
const captureProvider = {
|
|
1062
|
+
id: 'capture',
|
|
1063
|
+
async streamChat(_config, { messages }) {
|
|
1064
|
+
captured = messages?.[0]?.content ?? '';
|
|
1065
|
+
return { content: 'ok', reasoning: '', toolCalls: [] };
|
|
1066
|
+
},
|
|
1067
|
+
};
|
|
1068
|
+
const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-srv-'));
|
|
1069
|
+
await writeFile(path.join(cwd, 'tamnora.json'), JSON.stringify({ skills: { mode: 'eager' } }));
|
|
1070
|
+
const { instance, base } = await startServer({ cwd, resolveProvider: () => ({ provider: captureProvider, config: {} }) });
|
|
1071
|
+
const json = { 'Content-Type': 'application/json' };
|
|
1072
|
+
try {
|
|
1073
|
+
await fetch(`${base}/api/skills/demo`, {
|
|
1074
|
+
method: 'PUT',
|
|
1075
|
+
headers: json,
|
|
1076
|
+
body: JSON.stringify({ name: 'Demo', body: 'Regla secreta de la skill.' }),
|
|
1077
|
+
});
|
|
1078
|
+
const created = await (
|
|
1079
|
+
await fetch(`${base}/api/sessions`, { method: 'POST', headers: json, body: JSON.stringify({ title: 't', model: 'm' }) })
|
|
1080
|
+
).json();
|
|
1081
|
+
await fetch(`${base}/api/chat`, {
|
|
1082
|
+
method: 'POST',
|
|
1083
|
+
headers: json,
|
|
1084
|
+
body: JSON.stringify({ sessionId: created.session.id, model: 'm', input: 'hola' }),
|
|
1085
|
+
}).then((r) => r.text());
|
|
1086
|
+
assert.match(captured, /Regla secreta de la skill/);
|
|
1087
|
+
} finally {
|
|
1088
|
+
await instance.close();
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
test('auto-compact resume el historial cuando el contexto se pasa', async () => {
|
|
1093
|
+
let captured = '';
|
|
1094
|
+
const provider = {
|
|
1095
|
+
id: 'capture',
|
|
1096
|
+
async streamChat(_config, { messages }) {
|
|
1097
|
+
captured = (messages ?? [])
|
|
1098
|
+
.map((m) => (typeof m.content === 'string' ? m.content : ''))
|
|
1099
|
+
.join('\n');
|
|
1100
|
+
return { content: 'RESUMEN OK', reasoning: '', toolCalls: [] };
|
|
1101
|
+
},
|
|
1102
|
+
};
|
|
1103
|
+
const json = { 'Content-Type': 'application/json' };
|
|
1104
|
+
const { instance, base } = await startServer({ resolveProvider: () => ({ provider, config: {} }) });
|
|
1105
|
+
try {
|
|
1106
|
+
const created = await (
|
|
1107
|
+
await fetch(`${base}/api/sessions`, {
|
|
1108
|
+
method: 'POST',
|
|
1109
|
+
headers: json,
|
|
1110
|
+
body: JSON.stringify({ title: 't', model: 'm' }),
|
|
1111
|
+
})
|
|
1112
|
+
).json();
|
|
1113
|
+
const id = created.session.id;
|
|
1114
|
+
const big = 'x'.repeat(4000);
|
|
1115
|
+
instance.store.addMessages(
|
|
1116
|
+
id,
|
|
1117
|
+
Array.from({ length: 12 }, (_, i) => ({ role: i % 2 ? 'assistant' : 'user', content: `m${i} ${big}` }))
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
await fetch(`${base}/api/chat`, {
|
|
1121
|
+
method: 'POST',
|
|
1122
|
+
headers: json,
|
|
1123
|
+
body: JSON.stringify({ sessionId: id, model: 'm', input: 'hola', contextTokens: 1000 }),
|
|
1124
|
+
}).then((r) => r.text());
|
|
1125
|
+
|
|
1126
|
+
const meta = instance.store.getSession(id).meta;
|
|
1127
|
+
assert.ok(meta.summaryCursor, 'debe guardar el cursor de compactación');
|
|
1128
|
+
assert.match(captured, /Resumen de la conversación anterior/);
|
|
1129
|
+
} finally {
|
|
1130
|
+
await instance.close();
|
|
1131
|
+
}
|
|
1054
1132
|
});
|
|
1055
1133
|
|
|
1056
1134
|
test('detects, runs and stops dev processes', async () => {
|
|
@@ -1792,20 +1870,23 @@ test('plan mode exposes only read-only tools and adds guidance', async () => {
|
|
|
1792
1870
|
body: JSON.stringify({ input: 'planificá', model: 'm', mode: 'plan' }),
|
|
1793
1871
|
})
|
|
1794
1872
|
).text();
|
|
1795
|
-
assert.deepEqual(captured.tools, [
|
|
1796
|
-
'console_errors',
|
|
1797
|
-
'fetch_url',
|
|
1798
|
-
'git_diff',
|
|
1799
|
-
'git_log',
|
|
1800
|
-
'git_status',
|
|
1801
|
-
'glob',
|
|
1802
|
-
'grep',
|
|
1803
|
-
'list',
|
|
1804
|
-
'
|
|
1805
|
-
'
|
|
1806
|
-
'
|
|
1807
|
-
'
|
|
1808
|
-
'
|
|
1873
|
+
assert.deepEqual(captured.tools, [
|
|
1874
|
+
'console_errors',
|
|
1875
|
+
'fetch_url',
|
|
1876
|
+
'git_diff',
|
|
1877
|
+
'git_log',
|
|
1878
|
+
'git_status',
|
|
1879
|
+
'glob',
|
|
1880
|
+
'grep',
|
|
1881
|
+
'list',
|
|
1882
|
+
'list_mcp',
|
|
1883
|
+
'list_skills',
|
|
1884
|
+
'preview_url',
|
|
1885
|
+
'read',
|
|
1886
|
+
'screenshot',
|
|
1887
|
+
'skill',
|
|
1888
|
+
'speak',
|
|
1889
|
+
'task',
|
|
1809
1890
|
]);
|
|
1810
1891
|
assert.match(captured.system, /modo PLAN/);
|
|
1811
1892
|
} finally {
|
package/package.json
CHANGED