terminal-smart-cli 0.97.8 → 0.97.10
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/lib/agent.js +24 -1
- package/lib/mission-tool-cache.js +57 -0
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -13,6 +13,7 @@ const policy = require('./policy');
|
|
|
13
13
|
const checkpoint = require('./checkpoint');
|
|
14
14
|
const skillIndex = require('./skill-index');
|
|
15
15
|
const tools = require('./tools');
|
|
16
|
+
const { MissionToolCache, cacheReference } = require('./mission-tool-cache');
|
|
16
17
|
|
|
17
18
|
// SKILLS INSTALADAS (~/.ts/skills/<slug>/SKILL.md): lê nome+descrição do frontmatter pra
|
|
18
19
|
// oferecer ao agente. O agente LÊ o SKILL.md completo (com ler_arquivo) quando a skill é útil.
|
|
@@ -494,6 +495,7 @@ async function run(task, opts = {}) {
|
|
|
494
495
|
// ação (mesmo comando falhando, ciclo A/B/A/B). WARN = empurra a mudar de abordagem; 2º strike
|
|
495
496
|
// (ou já avisado e ainda em ciclo) = encerra o loop e cai no fechamento honesto garantido.
|
|
496
497
|
const _callCounts = new Map(); const _recentSigs = []; const _warnedSigs = new Set();
|
|
498
|
+
const _missionCache = new MissionToolCache();
|
|
497
499
|
let _loopWarned = false, loopedOut = false;
|
|
498
500
|
const LOOP_WARN = Number(process.env.TS_LOOP_WARN) > 0 ? Number(process.env.TS_LOOP_WARN) : 3;
|
|
499
501
|
const LOOP_BREAK = Number(process.env.TS_LOOP_BREAK) > 0 ? Number(process.env.TS_LOOP_BREAK) : 5;
|
|
@@ -675,6 +677,7 @@ async function run(task, opts = {}) {
|
|
|
675
677
|
const name = (tc.function && tc.function.name) || '';
|
|
676
678
|
let input = {}; try { input = JSON.parse((tc.function && tc.function.arguments) || '{}'); } catch (_) {}
|
|
677
679
|
let result;
|
|
680
|
+
let _cachedContent = '';
|
|
678
681
|
let _ran = false; // true só quando uma ferramenta REALMENTE executou (não gate/bloqueio) → status ✓/✗
|
|
679
682
|
|
|
680
683
|
guardStopped = _guard();
|
|
@@ -971,6 +974,16 @@ async function run(task, opts = {}) {
|
|
|
971
974
|
result = { resumo: sub.resumo };
|
|
972
975
|
steps++; _ran = true;
|
|
973
976
|
}
|
|
977
|
+
if (result === undefined && READONLY.has(name)) {
|
|
978
|
+
const _cached = _missionCache.get(name, input);
|
|
979
|
+
if (_cached) {
|
|
980
|
+
const _visible = messages.some(m => m && m.role === 'tool' && m.tool_call_id === _cached.toolCallId);
|
|
981
|
+
_cachedContent = cacheReference(_cached, name, _visible);
|
|
982
|
+
if (_visible) _missionCache.recordSavings(Math.max(0, String(_cached.content || '').length - _cachedContent.length));
|
|
983
|
+
result = { cached: true, tool: name };
|
|
984
|
+
onStep({ name, detail: 'cache da missão: ' + argsShort(name, input) });
|
|
985
|
+
}
|
|
986
|
+
}
|
|
974
987
|
if (result === undefined) {
|
|
975
988
|
onStep({ name, detail: argsShort(name, input) });
|
|
976
989
|
result = await tools.execute(name, input, { confineDir, baseDir: cwd, token, allowedTools: _allowedToolNames });
|
|
@@ -1023,6 +1036,12 @@ async function run(task, opts = {}) {
|
|
|
1023
1036
|
if (result && (result._backup !== undefined || result._acao !== undefined)) {
|
|
1024
1037
|
result = Object.assign({}, result); delete result._backup; delete result._acao;
|
|
1025
1038
|
}
|
|
1039
|
+
const _cacheClass = core.classifyToolResult(result);
|
|
1040
|
+
if (_cacheClass.ok && READONLY.has(name)) {
|
|
1041
|
+
_missionCache.remember(name, input, { toolCallId: tc.id, content: JSON.stringify(result).slice(0, TOOL_RESULT_CAP) });
|
|
1042
|
+
} else if (_cacheClass.ok && !READONLY.has(name)) {
|
|
1043
|
+
_missionCache.invalidate();
|
|
1044
|
+
}
|
|
1026
1045
|
}
|
|
1027
1046
|
// AVISO SUAVE de convergência: a partir de 2/3 do teto de pesquisa, empurra o modelo a
|
|
1028
1047
|
// concluir (o limite DURO acima corta de vez; este só sinaliza antes, sem bloquear).
|
|
@@ -1042,7 +1061,7 @@ async function run(task, opts = {}) {
|
|
|
1042
1061
|
_logEp('human');
|
|
1043
1062
|
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, cwd, context: lastCtx, needHuman: { motivo: result.motivo, o_que_fazer: result.o_que_fazer } };
|
|
1044
1063
|
}
|
|
1045
|
-
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result).slice(0, TOOL_RESULT_CAP) });
|
|
1064
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: _cachedContent || JSON.stringify(result).slice(0, TOOL_RESULT_CAP) });
|
|
1046
1065
|
if (guardStopped) break;
|
|
1047
1066
|
}
|
|
1048
1067
|
if (loopedOut || guardStopped) break; // watchdog/trava de orçamento cortou → fechamento determinístico
|
|
@@ -1124,6 +1143,10 @@ async function run(task, opts = {}) {
|
|
|
1124
1143
|
const _syncTasks = [
|
|
1125
1144
|
_memoryBus.recordContext(token, _snapshot),
|
|
1126
1145
|
_memoryBus.saveHandoff(token, _handoff),
|
|
1146
|
+
api('/api/telemetry/mission-cache', {
|
|
1147
|
+
method: 'POST', token, timeoutMs: 3000, retry: false,
|
|
1148
|
+
body: Object.assign({ surface: 'cli', model: usedModel }, _missionCache.stats()),
|
|
1149
|
+
}),
|
|
1127
1150
|
];
|
|
1128
1151
|
if (usedModel && usedModel !== 'smart') _syncTasks.push(api('/api/intelligence/observe', {
|
|
1129
1152
|
method: 'POST',
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function stable(value) {
|
|
4
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
const out = {};
|
|
7
|
+
for (const key of Object.keys(value).sort()) out[key] = stable(value[key]);
|
|
8
|
+
return out;
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function signature(tool, input) {
|
|
14
|
+
return String(tool || '') + '|' + JSON.stringify(stable(input || {}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class MissionToolCache {
|
|
18
|
+
constructor({ maxEntries = 64 } = {}) {
|
|
19
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
20
|
+
this.entries = new Map();
|
|
21
|
+
this.hits = 0;
|
|
22
|
+
this.invalidations = 0;
|
|
23
|
+
this.charactersSaved = 0;
|
|
24
|
+
}
|
|
25
|
+
get(tool, input) {
|
|
26
|
+
const key = signature(tool, input);
|
|
27
|
+
const entry = this.entries.get(key);
|
|
28
|
+
if (!entry) return null;
|
|
29
|
+
this.entries.delete(key); this.entries.set(key, entry);
|
|
30
|
+
this.hits++;
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
remember(tool, input, entry) {
|
|
34
|
+
const key = signature(tool, input);
|
|
35
|
+
this.entries.delete(key);
|
|
36
|
+
this.entries.set(key, Object.assign({}, entry));
|
|
37
|
+
while (this.entries.size > this.maxEntries) this.entries.delete(this.entries.keys().next().value);
|
|
38
|
+
}
|
|
39
|
+
invalidate() {
|
|
40
|
+
if (this.entries.size) this.invalidations++;
|
|
41
|
+
this.entries.clear();
|
|
42
|
+
}
|
|
43
|
+
recordSavings(characters) { this.charactersSaved += Math.max(0, Number(characters) || 0); }
|
|
44
|
+
stats() { return { entries: this.entries.size, hits: this.hits, invalidations: this.invalidations, charactersSaved: this.charactersSaved, estimatedTokensSaved: Math.ceil(this.charactersSaved / 4) }; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function cacheReference(entry, tool, originalStillVisible) {
|
|
48
|
+
if (!originalStillVisible && entry && entry.content) return entry.content;
|
|
49
|
+
return JSON.stringify({
|
|
50
|
+
cached: true,
|
|
51
|
+
tool,
|
|
52
|
+
message: 'Resultado idêntico já fornecido nesta missão; a fonte não foi consultada novamente. Use o resultado anterior.',
|
|
53
|
+
originalToolCallId: entry && entry.toolCallId,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { MissionToolCache, signature, cacheReference };
|