@nac3/forge-cli 1.23.4 → 1.23.6
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/bin/yf.d.ts.map +1 -1
- package/dist/bin/yf.js +2 -0
- package/dist/bin/yf.js.map +1 -1
- package/dist/chat/doctrine_digest.d.ts.map +1 -1
- package/dist/chat/doctrine_digest.js +4 -1
- package/dist/chat/doctrine_digest.js.map +1 -1
- package/dist/chat/tools/evidence.d.ts +34 -2
- package/dist/chat/tools/evidence.d.ts.map +1 -1
- package/dist/chat/tools/evidence.js +90 -12
- package/dist/chat/tools/evidence.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +62 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/temp.d.ts +36 -0
- package/dist/commands/temp.d.ts.map +1 -0
- package/dist/commands/temp.js +83 -0
- package/dist/commands/temp.js.map +1 -0
- package/dist/commands/urazuke.d.ts.map +1 -1
- package/dist/commands/urazuke.js +27 -0
- package/dist/commands/urazuke.js.map +1 -1
- package/dist/core/error_catalog.d.ts +4 -0
- package/dist/core/error_catalog.d.ts.map +1 -1
- package/dist/core/error_catalog.js +16 -0
- package/dist/core/error_catalog.js.map +1 -1
- package/dist/core/loopback_post.d.ts +101 -0
- package/dist/core/loopback_post.d.ts.map +1 -0
- package/dist/core/loopback_post.js +156 -0
- package/dist/core/loopback_post.js.map +1 -0
- package/dist/core/temp_janitor.d.ts +83 -0
- package/dist/core/temp_janitor.d.ts.map +1 -0
- package/dist/core/temp_janitor.js +160 -0
- package/dist/core/temp_janitor.js.map +1 -0
- package/dist/docs/doctrine/urazuke.md +50 -38
- package/dist/docs/doctrine/yf-commands.md +2 -0
- package/dist/mcp/forge_panel_server.d.ts +1 -26
- package/dist/mcp/forge_panel_server.d.ts.map +1 -1
- package/dist/mcp/forge_panel_server.js +5 -26
- package/dist/mcp/forge_panel_server.js.map +1 -1
- package/dist/mcp/forge_verbs_server.d.ts +1 -11
- package/dist/mcp/forge_verbs_server.d.ts.map +1 -1
- package/dist/mcp/forge_verbs_server.js +7 -23
- package/dist/mcp/forge_verbs_server.js.map +1 -1
- package/dist/mcp/panel_invoke.d.ts +51 -0
- package/dist/mcp/panel_invoke.d.ts.map +1 -0
- package/dist/mcp/panel_invoke.js +102 -0
- package/dist/mcp/panel_invoke.js.map +1 -0
- package/dist/nac3/internal_manifest.d.ts +2 -0
- package/dist/nac3/internal_manifest.d.ts.map +1 -1
- package/dist/nac3/internal_manifest.js +18 -3
- package/dist/nac3/internal_manifest.js.map +1 -1
- package/dist/nac3/verb_budget.d.ts +110 -0
- package/dist/nac3/verb_budget.d.ts.map +1 -0
- package/dist/nac3/verb_budget.js +142 -0
- package/dist/nac3/verb_budget.js.map +1 -0
- package/dist/relay/loopback_turn.d.ts +15 -42
- package/dist/relay/loopback_turn.d.ts.map +1 -1
- package/dist/relay/loopback_turn.js +27 -115
- package/dist/relay/loopback_turn.js.map +1 -1
- package/dist/urazuke/evidence.d.ts +22 -0
- package/dist/urazuke/evidence.d.ts.map +1 -1
- package/dist/urazuke/evidence.js +42 -3
- package/dist/urazuke/evidence.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/doctrine/urazuke.md +50 -38
- package/docs/doctrine/yf-commands.md +2 -0
- package/package.json +1 -1
- package/src/i18n/catalogs/ar.json +3 -0
- package/src/i18n/catalogs/de.json +3 -0
- package/src/i18n/catalogs/en.json +3 -0
- package/src/i18n/catalogs/es.json +3 -0
- package/src/i18n/catalogs/fr.json +3 -0
- package/src/i18n/catalogs/hi.json +3 -0
- package/src/i18n/catalogs/it.json +3 -0
- package/src/i18n/catalogs/ja.json +3 -0
- package/src/i18n/catalogs/pt.json +3 -0
- package/src/i18n/catalogs/zh.json +3 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El POST por loopback que dura lo que dura el trabajo.
|
|
3
|
+
*
|
|
4
|
+
* Forge habla consigo mismo por HTTP en 127.0.0.1 en dos lugares distintos y
|
|
5
|
+
* por el mismo motivo: el que pide no comparte memoria con el que ejecuta.
|
|
6
|
+
*
|
|
7
|
+
* - El consumidor de pedidos del telefono POSTea a su propio /api/chat
|
|
8
|
+
* (relay/loopback_turn.ts).
|
|
9
|
+
* - Los dos servers MCP -- nietos de Forge, lanzados por `claude -p` --
|
|
10
|
+
* POSTean cada invocacion de verbo a /api/forge/panel-invoke
|
|
11
|
+
* (mcp/forge_verbs_server.ts, mcp/forge_panel_server.ts).
|
|
12
|
+
*
|
|
13
|
+
* Las dos respuestas son un JSON que recien se arma cuando el trabajo entero
|
|
14
|
+
* termino, asi que sus CABECERAS no salen antes. Con `fetch` sin opciones ese
|
|
15
|
+
* POST hereda el headersTimeout de undici, un limite pensado para una llamada
|
|
16
|
+
* corta, y pasado ese punto muere el fetch -- no el trabajo. El servidor sigue
|
|
17
|
+
* trabajando y termina; el que esperaba ya recibio "fetch failed".
|
|
18
|
+
*
|
|
19
|
+
* EL LIMITE ESTA MEDIDO, NO ESTIMADO: contra un servidor que acepta la
|
|
20
|
+
* conexion y nunca responde, el fetch global de Node 22.16 murio a los
|
|
21
|
+
* 305.677 ms con name=TypeError, message="fetch failed" y
|
|
22
|
+
* cause.code=UND_ERR_HEADERS_TIMEOUT.
|
|
23
|
+
*
|
|
24
|
+
* El defecto se arreglo primero en el camino del telefono (PND-234) y volvio a
|
|
25
|
+
* aparecer, identico, en el camino de los verbos (PND-235): el mismo techo de
|
|
26
|
+
* cinco minutos sobre `yujin.evidence.run`, que es justo el verbo que acredita
|
|
27
|
+
* una bateria de siete minutos. Un arreglo que no se comparte se vuelve a
|
|
28
|
+
* pagar, asi que el transporte vive aca y los dos lo usan.
|
|
29
|
+
*
|
|
30
|
+
* POR QUE node:http Y NO fetch. Es la unica forma de mandar los tiempos sin
|
|
31
|
+
* depender de undici: Node lo trae adentro pero no lo expone como paquete, y
|
|
32
|
+
* construir un Agent con otro headersTimeout exigiria declarar una dependencia
|
|
33
|
+
* nueva para algo que el stdlib ya hace. La conexion es loopback y en claro,
|
|
34
|
+
* asi que no se pierde nada por bajar de capa.
|
|
35
|
+
*
|
|
36
|
+
* EL PRESUPUESTO NO DESAPARECE, cambia de escala -- y lo fija el LLAMADOR,
|
|
37
|
+
* porque el unico que sabe cuanto puede durar legitimamente el trabajo es el
|
|
38
|
+
* que lo pidio. Lo que no se usa es `req.setTimeout`, que mide INACTIVIDAD del
|
|
39
|
+
* socket: un trabajo que piensa diez minutos no manda un byte, y ese
|
|
40
|
+
* temporizador lo mataria sin motivo, que es la forma exacta del bug que esto
|
|
41
|
+
* cierra. Lo que se mide es el tiempo TOTAL.
|
|
42
|
+
*
|
|
43
|
+
* ASCII puro.
|
|
44
|
+
*/
|
|
45
|
+
import http from 'node:http';
|
|
46
|
+
/** Piso del presupuesto: ningun POST por loopback se corta antes de esto. */
|
|
47
|
+
export const LOOPBACK_MIN_BUDGET_MS = 30_000;
|
|
48
|
+
/** El corte, con lo que el llamador necesita para redactar su propio mensaje
|
|
49
|
+
* (cada camino tiene su catalogo de errores y su idioma). */
|
|
50
|
+
export class LoopbackCut extends Error {
|
|
51
|
+
kind;
|
|
52
|
+
/** Se vencio el presupuesto (y no: el socket se cayo por su cuenta). */
|
|
53
|
+
expired;
|
|
54
|
+
budget_ms;
|
|
55
|
+
elapsed_ms;
|
|
56
|
+
constructor(kind, args) {
|
|
57
|
+
super('loopback ' + kind + (args.expired ? ' (presupuesto de ' + args.budgetMs + ' ms vencido)' : ''), args.cause === undefined ? undefined : { cause: args.cause });
|
|
58
|
+
this.name = 'LoopbackCut';
|
|
59
|
+
this.kind = kind;
|
|
60
|
+
this.expired = args.expired;
|
|
61
|
+
this.budget_ms = args.budgetMs;
|
|
62
|
+
this.elapsed_ms = args.elapsedMs;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* El socket nunca llego a conectar: no hay servidor escuchando ahi.
|
|
67
|
+
*
|
|
68
|
+
* Es la unica evidencia de que el trabajo NO corrio. Se mira el errno y no el
|
|
69
|
+
* texto porque el texto cambia entre versiones de Node y entre sistemas.
|
|
70
|
+
*/
|
|
71
|
+
export function isConnectionRefusal(e) {
|
|
72
|
+
const code = e?.code;
|
|
73
|
+
if (!code)
|
|
74
|
+
return false;
|
|
75
|
+
return code === 'ECONNREFUSED' || code === 'ENOTFOUND'
|
|
76
|
+
|| code === 'EADDRNOTAVAIL' || code === 'EHOSTUNREACH';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* POST de un JSON al loopback, esperando lo que tarde.
|
|
80
|
+
*
|
|
81
|
+
* Devuelve el status y el cuerpo crudo; parsear es del llamador, que es el
|
|
82
|
+
* unico que conoce la forma de la respuesta. Un status que no es 200 NO es una
|
|
83
|
+
* excepcion: esconderlo seria esconder el dato que permite diagnosticarlo.
|
|
84
|
+
*/
|
|
85
|
+
export async function postLoopbackJson(args) {
|
|
86
|
+
const body = Buffer.from(JSON.stringify(args.payload), 'utf8');
|
|
87
|
+
const arranco = Date.now();
|
|
88
|
+
/* Un presupuesto en cero, negativo o no numerico NO se obedece: se cae al
|
|
89
|
+
* piso. Obedecerlo seria matar la conexion antes de abrirla -- cada
|
|
90
|
+
* invocacion fallaria al instante y el sintoma (todo falla, siempre) no se
|
|
91
|
+
* parece en nada a la causa (un numero mal calculado un modulo mas arriba).
|
|
92
|
+
* Es la misma decision que `mobileTurnBudgetMs` toma con su variable de
|
|
93
|
+
* entorno: un cero en un numero de configuracion es casi siempre un
|
|
94
|
+
* descuido. */
|
|
95
|
+
const budget = Number.isFinite(args.budgetMs) && args.budgetMs > 0
|
|
96
|
+
? args.budgetMs : LOOPBACK_MIN_BUDGET_MS;
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
let settled = false;
|
|
99
|
+
/* Que se vencio el presupuesto se recuerda aparte del error del socket:
|
|
100
|
+
* `req.destroy()` llega al handler de error como un ECONNRESET cualquiera,
|
|
101
|
+
* y sin esta marca el vencimiento se clasificaria como "no se entrego" --
|
|
102
|
+
* el caso que afirma que no corrio nada -- justamente cuando si corrio. */
|
|
103
|
+
let vencido = false;
|
|
104
|
+
const req = http.request({
|
|
105
|
+
host: args.host ?? '127.0.0.1',
|
|
106
|
+
port: args.port,
|
|
107
|
+
path: args.path,
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'content-type': 'application/json',
|
|
111
|
+
'content-length': String(body.byteLength),
|
|
112
|
+
...(args.headers ?? {}),
|
|
113
|
+
},
|
|
114
|
+
}, (res) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
res.on('data', (c) => chunks.push(c));
|
|
117
|
+
res.on('end', () => {
|
|
118
|
+
if (settled)
|
|
119
|
+
return;
|
|
120
|
+
settled = true;
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve({
|
|
123
|
+
status: res.statusCode ?? 0,
|
|
124
|
+
body: Buffer.concat(chunks).toString('utf8'),
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
/* Un corte MIENTRAS baja el cuerpo tampoco es "no se entrego": el
|
|
128
|
+
* trabajo ya corrio y el servidor ya estaba contestando. */
|
|
129
|
+
res.on('error', (e) => fail(e, true));
|
|
130
|
+
});
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
vencido = true;
|
|
133
|
+
req.destroy(new Error('presupuesto de loopback vencido'));
|
|
134
|
+
}, budget);
|
|
135
|
+
/* Que este temporizador no sea lo que mantiene vivo al proceso. */
|
|
136
|
+
if (typeof timer.unref === 'function') {
|
|
137
|
+
timer.unref();
|
|
138
|
+
}
|
|
139
|
+
const fail = (cause, yaCorrio) => {
|
|
140
|
+
if (settled)
|
|
141
|
+
return;
|
|
142
|
+
settled = true;
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
const noEntrego = !vencido && !yaCorrio && isConnectionRefusal(cause);
|
|
145
|
+
reject(new LoopbackCut(noEntrego ? 'undelivered' : 'unanswered', {
|
|
146
|
+
expired: vencido,
|
|
147
|
+
budgetMs: budget,
|
|
148
|
+
elapsedMs: Date.now() - arranco,
|
|
149
|
+
cause,
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
req.on('error', (e) => fail(e, false));
|
|
153
|
+
req.end(body);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=loopback_post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback_post.js","sourceRoot":"","sources":["../../src/core/loopback_post.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAmB7C;8DAC8D;AAC9D,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAC/B,wEAAwE;IAC/D,OAAO,CAAU;IACjB,SAAS,CAAS;IAClB,UAAU,CAAS;IAE5B,YAAY,IAAqB,EAAE,IAAgF;QACjH,KAAK,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,GAAG,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,EACnG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;IACnC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAU;IAC5C,MAAM,IAAI,GAAI,CAA2B,EAAE,IAAI,CAAC;IAChD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,WAAW;WAC/C,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,cAAc,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAStC;IACC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B;;;;;;mBAMe;IACf,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC;QAChE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAE3C,OAAO,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACvD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB;;;mFAG2E;QAC3E,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW;YAC9B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;gBACzC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;aACxB;SACF,EAAE,CAAC,GAAG,EAAE,EAAE;YACT,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC;oBACN,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC;oBAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;iBAC7C,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH;wEAC4D;YAC5D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,OAAO,GAAG,IAAI,CAAC;YACf,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;QAC5D,CAAC,EAAE,MAAM,CAAC,CAAC;QACX,mEAAmE;QACnE,IAAI,OAAQ,KAAgC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACjE,KAA+B,CAAC,KAAK,EAAE,CAAC;QAC3C,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,QAAiB,EAAQ,EAAE;YACvD,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,SAAS,GAAG,CAAC,OAAO,IAAI,CAAC,QAAQ,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE;gBAC/D,OAAO,EAAE,OAAO;gBAChB,QAAQ,EAAE,MAAM;gBAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;gBAC/B,KAAK;aACN,CAAC,CAAC,CAAC;QACN,CAAC,CAAC;QAEF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Prefijo del padre por corrida que instala el cajon de arena. */
|
|
2
|
+
export declare const SANDBOX_PREFIX = "yf-run-";
|
|
3
|
+
/** Edad minima por defecto para considerar huerfano a un temporal: 2 h.
|
|
4
|
+
*
|
|
5
|
+
* No es cosmetico. En esta maquina casi siempre hay OTRA bateria corriendo
|
|
6
|
+
* al lado (y cuatro paneles vivos), asi que borrar por nombre sin mirar la
|
|
7
|
+
* edad mata la corrida del vecino -- el riesgo que el propio PND-241 pide
|
|
8
|
+
* evitar. Una bateria completa tarda ~10 min medidos; 2 h deja doce veces
|
|
9
|
+
* ese margen. */
|
|
10
|
+
export declare const DEFAULT_MIN_AGE_MS: number;
|
|
11
|
+
/** Edad a la que el cajon de arena considera abandonado un padre de otra
|
|
12
|
+
* corrida (la suya la borra por nombre, no por edad). */
|
|
13
|
+
export declare const STALE_SANDBOX_MS: number;
|
|
14
|
+
export interface TempEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
mtimeMs: number;
|
|
18
|
+
/** true si es un padre por corrida (yf-run-*), no un temporal suelto. */
|
|
19
|
+
sandbox: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SweepOptions {
|
|
22
|
+
/** Donde buscar. Por defecto el %TEMP% real. */
|
|
23
|
+
tmpRoot?: string;
|
|
24
|
+
/** Reloj, inyectable para los tests. */
|
|
25
|
+
now?: number;
|
|
26
|
+
/** Solo se borra lo mas viejo que esto. */
|
|
27
|
+
minAgeMs?: number;
|
|
28
|
+
/** Contar sin borrar. */
|
|
29
|
+
dryRun?: boolean;
|
|
30
|
+
/** Nombres (no caminos) que no hay que tocar nunca. */
|
|
31
|
+
keep?: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
export interface SweepResult {
|
|
34
|
+
tmpRoot: string;
|
|
35
|
+
/** Temporales de Forge encontrados. */
|
|
36
|
+
scanned: number;
|
|
37
|
+
/** Candidatos por edad. */
|
|
38
|
+
eligible: number;
|
|
39
|
+
/** Borrados de verdad (0 en dryRun). */
|
|
40
|
+
removed: number;
|
|
41
|
+
/** Se intento y no se pudo -- tipicamente un escritor vivo. */
|
|
42
|
+
failed: {
|
|
43
|
+
name: string;
|
|
44
|
+
error: string;
|
|
45
|
+
}[];
|
|
46
|
+
/** Salteados por ser demasiado recientes (posible corrida viva). */
|
|
47
|
+
skippedFresh: number;
|
|
48
|
+
/** Salteados porque los pidio `keep`. */
|
|
49
|
+
skippedKept: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* La regla de que cuenta como temporal de Forge, en un solo lugar.
|
|
53
|
+
*
|
|
54
|
+
* Deliberadamente ANCLADA al arranque: `startsWith('yf-')` y no un
|
|
55
|
+
* `includes`, para no barrer el temporal de otra herramienta que mencione
|
|
56
|
+
* yf- en el medio de su nombre.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isForgeTempName(name: string): boolean;
|
|
59
|
+
/** Nombre del padre por corrida. Lleva pid + reloj + azar para que dos
|
|
60
|
+
* baterias simultaneas no compartan cajon. */
|
|
61
|
+
export declare function newSandboxName(pid: number, now: number, rand?: number): string;
|
|
62
|
+
/**
|
|
63
|
+
* Cuenta los nombres `yf-*` de `tmpRoot` SIN hacer un stat por cada uno.
|
|
64
|
+
*
|
|
65
|
+
* Existe por costo: `listForgeTempDirs` hace un stat por entrada para poder
|
|
66
|
+
* mirar la edad, y en la maquina donde se abrio PND-241 eso eran 26.034
|
|
67
|
+
* stats. Un comando de diagnostico no puede pagar eso para decir "hay
|
|
68
|
+
* mucho". El que necesita la edad usa la otra; el que solo necesita el
|
|
69
|
+
* tamano del stock usa esta.
|
|
70
|
+
*/
|
|
71
|
+
export declare function countForgeTempNames(tmpRoot?: string): Promise<number>;
|
|
72
|
+
/** Lista los temporales de Forge que hay en `tmpRoot`. */
|
|
73
|
+
export declare function listForgeTempDirs(tmpRoot?: string): Promise<TempEntry[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Barre los temporales de Forge huerfanos.
|
|
76
|
+
*
|
|
77
|
+
* Fail-open a proposito: un directorio que no se puede borrar (escritor
|
|
78
|
+
* vivo, permiso) se anota en `failed` y el barrido SIGUE. Un portero que
|
|
79
|
+
* aborta en el primer EBUSY deja el resto del stock en pie, que es
|
|
80
|
+
* exactamente el estado del que venimos.
|
|
81
|
+
*/
|
|
82
|
+
export declare function sweepForgeTemp(opts?: SweepOptions): Promise<SweepResult>;
|
|
83
|
+
//# sourceMappingURL=temp_janitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp_janitor.d.ts","sourceRoot":"","sources":["../../src/core/temp_janitor.ts"],"names":[],"mappings":"AAuCA,mEAAmE;AACnE,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC;;;;;;iBAMiB;AACjB,eAAO,MAAM,kBAAkB,QAAqB,CAAC;AAErD;yDACyD;AACzD,eAAO,MAAM,gBAAgB,QAAqB,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;8CAC8C;AAC9C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,SAAgB,GAAG,MAAM,CAGrF;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAOhF;AAED,0DAA0D;AAC1D,wBAAsB,iBAAiB,CAAC,OAAO,SAAc,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAuBnF;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAoClF"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El portero de %TEMP%: cuenta y barre los directorios temporales que deja
|
|
3
|
+
* la bateria (PND-241).
|
|
4
|
+
*
|
|
5
|
+
* MEDIDO el 31-oct-2026 en C:\Users\ASUS\AppData\Local\Temp: 26.034
|
|
6
|
+
* directorios yf-* huerfanos, sobre 47.484 entradas en total. La nota del
|
|
7
|
+
* commit 5f30f42 habia contado 5.905 nueve dias antes, asi que el stock
|
|
8
|
+
* crece con cada corrida y nadie lo limpia.
|
|
9
|
+
*
|
|
10
|
+
* Y la causa NO es que los tests no limpien. Los grandes aportantes SI
|
|
11
|
+
* tienen su afterEach con `fs.rm(tmpHome, {recursive, force, maxRetries})`,
|
|
12
|
+
* y la bateria pasa en verde -- si ese rm tirara, el hook fallaria. Lo que
|
|
13
|
+
* se midio al correr tests/vault-server.test.ts aislado (17 casos, 8
|
|
14
|
+
* directorios nuevos) es que lo que queda en pie contiene EXACTAMENTE esto:
|
|
15
|
+
*
|
|
16
|
+
* yf-vsrv-home-BqVTGW/AppData/Roaming <- y nada mas
|
|
17
|
+
*
|
|
18
|
+
* O sea: el rm borro el arbol y DESPUES alguien volvio a crear el camino,
|
|
19
|
+
* ya con el HOME del test restaurado. Es la forma de PND-237 otra vez: un
|
|
20
|
+
* escritor que sobrevive al cierre. Perseguir ese escritor en cada uno de
|
|
21
|
+
* los 230 archivos que hacen mkdtemp es la via larga y se vuelve a filtrar
|
|
22
|
+
* con el proximo test que se escriba.
|
|
23
|
+
*
|
|
24
|
+
* La via corta es cambiar DONDE caen: la bateria corre con su propio
|
|
25
|
+
* os.tmpdir() -- un unico padre yf-run-<pid>-<t> dentro del %TEMP% real --
|
|
26
|
+
* y al terminar se borra el padre entero. Un escritor rezagado que recrea
|
|
27
|
+
* su camino lo recrea ADENTRO del padre, asi que igual se va. Ver
|
|
28
|
+
* tests/global_temp_sandbox.ts, que es el que lo instala.
|
|
29
|
+
*
|
|
30
|
+
* Este modulo tiene las dos mitades y las comparten TRES consumidores (el
|
|
31
|
+
* cajon de arena de la bateria, `yf temp`, y el chequeo del doctor), para
|
|
32
|
+
* que la regla de que es "un temporal de Forge" viva en un solo lugar.
|
|
33
|
+
*
|
|
34
|
+
* ASCII puro.
|
|
35
|
+
*/
|
|
36
|
+
import { promises as fs } from 'node:fs';
|
|
37
|
+
import os from 'node:os';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
/** Prefijo del padre por corrida que instala el cajon de arena. */
|
|
40
|
+
export const SANDBOX_PREFIX = 'yf-run-';
|
|
41
|
+
/** Edad minima por defecto para considerar huerfano a un temporal: 2 h.
|
|
42
|
+
*
|
|
43
|
+
* No es cosmetico. En esta maquina casi siempre hay OTRA bateria corriendo
|
|
44
|
+
* al lado (y cuatro paneles vivos), asi que borrar por nombre sin mirar la
|
|
45
|
+
* edad mata la corrida del vecino -- el riesgo que el propio PND-241 pide
|
|
46
|
+
* evitar. Una bateria completa tarda ~10 min medidos; 2 h deja doce veces
|
|
47
|
+
* ese margen. */
|
|
48
|
+
export const DEFAULT_MIN_AGE_MS = 2 * 60 * 60 * 1000;
|
|
49
|
+
/** Edad a la que el cajon de arena considera abandonado un padre de otra
|
|
50
|
+
* corrida (la suya la borra por nombre, no por edad). */
|
|
51
|
+
export const STALE_SANDBOX_MS = 6 * 60 * 60 * 1000;
|
|
52
|
+
/**
|
|
53
|
+
* La regla de que cuenta como temporal de Forge, en un solo lugar.
|
|
54
|
+
*
|
|
55
|
+
* Deliberadamente ANCLADA al arranque: `startsWith('yf-')` y no un
|
|
56
|
+
* `includes`, para no barrer el temporal de otra herramienta que mencione
|
|
57
|
+
* yf- en el medio de su nombre.
|
|
58
|
+
*/
|
|
59
|
+
export function isForgeTempName(name) {
|
|
60
|
+
return name.startsWith('yf-');
|
|
61
|
+
}
|
|
62
|
+
/** Nombre del padre por corrida. Lleva pid + reloj + azar para que dos
|
|
63
|
+
* baterias simultaneas no compartan cajon. */
|
|
64
|
+
export function newSandboxName(pid, now, rand = Math.random()) {
|
|
65
|
+
const salt = Math.floor(rand * 0xffffff).toString(36).padStart(4, '0');
|
|
66
|
+
return SANDBOX_PREFIX + pid + '-' + now.toString(36) + '-' + salt;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cuenta los nombres `yf-*` de `tmpRoot` SIN hacer un stat por cada uno.
|
|
70
|
+
*
|
|
71
|
+
* Existe por costo: `listForgeTempDirs` hace un stat por entrada para poder
|
|
72
|
+
* mirar la edad, y en la maquina donde se abrio PND-241 eso eran 26.034
|
|
73
|
+
* stats. Un comando de diagnostico no puede pagar eso para decir "hay
|
|
74
|
+
* mucho". El que necesita la edad usa la otra; el que solo necesita el
|
|
75
|
+
* tamano del stock usa esta.
|
|
76
|
+
*/
|
|
77
|
+
export async function countForgeTempNames(tmpRoot = os.tmpdir()) {
|
|
78
|
+
try {
|
|
79
|
+
const names = await fs.readdir(tmpRoot);
|
|
80
|
+
return names.filter(isForgeTempName).length;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Lista los temporales de Forge que hay en `tmpRoot`. */
|
|
87
|
+
export async function listForgeTempDirs(tmpRoot = os.tmpdir()) {
|
|
88
|
+
let names;
|
|
89
|
+
try {
|
|
90
|
+
names = await fs.readdir(tmpRoot);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
if (!isForgeTempName(name))
|
|
98
|
+
continue;
|
|
99
|
+
const full = path.join(tmpRoot, name);
|
|
100
|
+
let mtimeMs = 0;
|
|
101
|
+
try {
|
|
102
|
+
const st = await fs.stat(full);
|
|
103
|
+
if (!st.isDirectory())
|
|
104
|
+
continue;
|
|
105
|
+
mtimeMs = st.mtimeMs;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* Desaparecio entre el readdir y el stat: lo limpio alguien mas. */
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
out.push({ name, path: full, mtimeMs, sandbox: name.startsWith(SANDBOX_PREFIX) });
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Barre los temporales de Forge huerfanos.
|
|
117
|
+
*
|
|
118
|
+
* Fail-open a proposito: un directorio que no se puede borrar (escritor
|
|
119
|
+
* vivo, permiso) se anota en `failed` y el barrido SIGUE. Un portero que
|
|
120
|
+
* aborta en el primer EBUSY deja el resto del stock en pie, que es
|
|
121
|
+
* exactamente el estado del que venimos.
|
|
122
|
+
*/
|
|
123
|
+
export async function sweepForgeTemp(opts = {}) {
|
|
124
|
+
const tmpRoot = opts.tmpRoot ?? os.tmpdir();
|
|
125
|
+
const now = opts.now ?? Date.now();
|
|
126
|
+
const minAgeMs = opts.minAgeMs ?? DEFAULT_MIN_AGE_MS;
|
|
127
|
+
const keep = new Set(opts.keep ?? []);
|
|
128
|
+
const entries = await listForgeTempDirs(tmpRoot);
|
|
129
|
+
const res = {
|
|
130
|
+
tmpRoot,
|
|
131
|
+
scanned: entries.length,
|
|
132
|
+
eligible: 0,
|
|
133
|
+
removed: 0,
|
|
134
|
+
failed: [],
|
|
135
|
+
skippedFresh: 0,
|
|
136
|
+
skippedKept: 0,
|
|
137
|
+
};
|
|
138
|
+
for (const e of entries) {
|
|
139
|
+
if (keep.has(e.name)) {
|
|
140
|
+
res.skippedKept++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (now - e.mtimeMs < minAgeMs) {
|
|
144
|
+
res.skippedFresh++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
res.eligible++;
|
|
148
|
+
if (opts.dryRun)
|
|
149
|
+
continue;
|
|
150
|
+
try {
|
|
151
|
+
await fs.rm(e.path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
152
|
+
res.removed++;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
res.failed.push({ name: e.name, error: err instanceof Error ? err.message : String(err) });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return res;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=temp_janitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp_janitor.js","sourceRoot":"","sources":["../../src/core/temp_janitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,38 +1,50 @@
|
|
|
1
|
-
# Urazuke -- the evidence method, as Forge runs it
|
|
2
|
-
|
|
3
|
-
Urazuke (github.com/pkuschnirof/urazuke) is the method behind every project
|
|
4
|
-
Forge builds or maintains: 24 ordered steps in four stretches, each with an
|
|
5
|
-
exact prompt, the inputs the owner prepares, the artifacts the agent leaves
|
|
6
|
-
in the repository, and a completion check the owner does WITHOUT reading
|
|
7
|
-
code. An existing system built without it goes through eight normalisation
|
|
8
|
-
stations (N0..N7) first and enters the loop at step 5.
|
|
9
|
-
|
|
10
|
-
## What is mechanical (not prose)
|
|
11
|
-
|
|
12
|
-
- The project's workflow is `urazuke` (no code) or `urazuke-legacy` (code
|
|
13
|
-
without the method's artifacts). Every canonical task carries its step.
|
|
14
|
-
- The CURRENT step's prompt is injected into your system prompt every turn,
|
|
15
|
-
with the state of its checks (section "URAZUKE -- peldano NN"). Act on
|
|
16
|
-
that prompt as if the owner had pasted it. Do not skip ahead.
|
|
17
|
-
- A canonical task closes ONLY when `evaluateStep()` is GREEN: the files
|
|
18
|
-
the step names exist, are not stubs, say what they must; the sizing
|
|
19
|
-
record is admissible (G9); the plan items carry a spec; the evidence
|
|
20
|
-
receipts exist. `yujin.pending.close` refuses a red step. An override
|
|
21
|
-
needs the OWNER's written `force_reason` (kept in history); never pass
|
|
22
|
-
one on your own initiative.
|
|
23
|
-
- Evidence is a RECEIPT Forge records (command, ref, exit code, counts),
|
|
24
|
-
never your summary. "Tests pass" without a receipt is a claim.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
# Urazuke -- the evidence method, as Forge runs it
|
|
2
|
+
|
|
3
|
+
Urazuke (github.com/pkuschnirof/urazuke) is the method behind every project
|
|
4
|
+
Forge builds or maintains: 24 ordered steps in four stretches, each with an
|
|
5
|
+
exact prompt, the inputs the owner prepares, the artifacts the agent leaves
|
|
6
|
+
in the repository, and a completion check the owner does WITHOUT reading
|
|
7
|
+
code. An existing system built without it goes through eight normalisation
|
|
8
|
+
stations (N0..N7) first and enters the loop at step 5.
|
|
9
|
+
|
|
10
|
+
## What is mechanical (not prose)
|
|
11
|
+
|
|
12
|
+
- The project's workflow is `urazuke` (no code) or `urazuke-legacy` (code
|
|
13
|
+
without the method's artifacts). Every canonical task carries its step.
|
|
14
|
+
- The CURRENT step's prompt is injected into your system prompt every turn,
|
|
15
|
+
with the state of its checks (section "URAZUKE -- peldano NN"). Act on
|
|
16
|
+
that prompt as if the owner had pasted it. Do not skip ahead.
|
|
17
|
+
- A canonical task closes ONLY when `evaluateStep()` is GREEN: the files
|
|
18
|
+
the step names exist, are not stubs, say what they must; the sizing
|
|
19
|
+
record is admissible (G9); the plan items carry a spec; the evidence
|
|
20
|
+
receipts exist. `yujin.pending.close` refuses a red step. An override
|
|
21
|
+
needs the OWNER's written `force_reason` (kept in history); never pass
|
|
22
|
+
one on your own initiative.
|
|
23
|
+
- Evidence is a RECEIPT Forge records (command, ref, exit code, counts),
|
|
24
|
+
never your summary. "Tests pass" without a receipt is a claim.
|
|
25
|
+
- A LONG run leaves its receipt the same way. `yujin.evidence.run` blocks
|
|
26
|
+
the turn only while the run fits the blocking window (10 min, lowered with
|
|
27
|
+
`limits.claude_cli_silence_ms`); over that, or with `background: true`, it
|
|
28
|
+
is launched detached and returns a HANDLE. That is not a degraded mode:
|
|
29
|
+
the receipt is written by whoever runs, not by whoever waits. Read it with
|
|
30
|
+
`yujin.evidence.list` when the task finishes (`yujin.task.status({id})`
|
|
31
|
+
for the progress). Do NOT relaunch a run that already has a handle.
|
|
32
|
+
- And the corollary for any invocation that dies waiting: a verb call that
|
|
33
|
+
comes back UNANSWERED (E_VERB_CALL_UNANSWERED) did not necessarily fail.
|
|
34
|
+
Before redoing the work, LOOK FOR THE TRACE -- `yujin.evidence.list` for a
|
|
35
|
+
receipt, `yujin.task.list` for a task. Two closes were reported as
|
|
36
|
+
"believed" over a receipt that existed on disk the whole time.
|
|
37
|
+
|
|
38
|
+
## How to work inside it
|
|
39
|
+
|
|
40
|
+
1. Read the URAZUKE section: red checks are what is missing. Produce the
|
|
41
|
+
artifact at the path named, with real content, or ask the owner for the
|
|
42
|
+
input that is theirs (`antes de enviar`). Never invent an owner input.
|
|
43
|
+
2. When the section says GREEN, close the task with `yujin.pending.close`
|
|
44
|
+
(full expediente) and the next step's prompt arrives on the next turn.
|
|
45
|
+
3. `yujin.urazuke.status` shows the whole ladder; `yujin.urazuke.step`
|
|
46
|
+
shows any step in full (prompts, inputs, artifacts, checklist).
|
|
47
|
+
4. The plan is the pendings: each item needs `spec` (acceptance "given /
|
|
48
|
+
when / then" + `how_verified`) before it is implemented (step 5).
|
|
49
|
+
5. No deadlines, ever: the method fixes the order and the gates, not the
|
|
50
|
+
calendar. Speed is a consequence, not the objective.
|
|
@@ -35,6 +35,8 @@ SHIP + DEPLOY
|
|
|
35
35
|
OBSERVABILITY
|
|
36
36
|
yf log per-scope log levels + redaction (SQ.K)
|
|
37
37
|
yf projects cross-device project registry
|
|
38
|
+
yf task background tasks (list / status / log / stop)
|
|
39
|
+
yf temp orphan temp dirs: status (count) / clean (delete)
|
|
38
40
|
|
|
39
41
|
QA HARNESSES
|
|
40
42
|
yf qa brain-matrix E2E test invariants against LLM brains
|
|
@@ -17,30 +17,5 @@
|
|
|
17
17
|
* Hand-rolled JSON-RPC 2.0 sobre stdio (estilo zero-dep del repo,
|
|
18
18
|
* espejo del cliente en protocol_client.ts). ASCII-only.
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
jsonrpc: '2.0';
|
|
22
|
-
id?: number | string;
|
|
23
|
-
method: string;
|
|
24
|
-
params?: {
|
|
25
|
-
name?: string;
|
|
26
|
-
arguments?: Record<string, unknown>;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
interface ToolDef {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
inputSchema: Record<string, unknown>;
|
|
33
|
-
}
|
|
34
|
-
declare const TOOLS: ToolDef[];
|
|
35
|
-
declare function send(msg: unknown): void;
|
|
36
|
-
declare function logErr(msg: string): void;
|
|
37
|
-
/** Reenvia un nac3_invoke al chat server (que corre el dispatcher
|
|
38
|
-
* real) y devuelve el texto del resultado. */
|
|
39
|
-
declare function forwardInvoke(verbId: string, args: Record<string, unknown>): Promise<{
|
|
40
|
-
ok: boolean;
|
|
41
|
-
text: string;
|
|
42
|
-
}>;
|
|
43
|
-
declare function toolResult(id: number | string | undefined, text: string, isError?: boolean): void;
|
|
44
|
-
declare function handle(req: JsonRpcReq): Promise<void>;
|
|
45
|
-
declare function main(): void;
|
|
20
|
+
export {};
|
|
46
21
|
//# sourceMappingURL=forge_panel_server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forge_panel_server.d.ts","sourceRoot":"","sources":["../../src/mcp/forge_panel_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG
|
|
1
|
+
{"version":3,"file":"forge_panel_server.d.ts","sourceRoot":"","sources":["../../src/mcp/forge_panel_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* forge_panel_server.ts -- PND-064 slice B.
|
|
4
3
|
*
|
|
@@ -18,6 +17,7 @@
|
|
|
18
17
|
* Hand-rolled JSON-RPC 2.0 sobre stdio (estilo zero-dep del repo,
|
|
19
18
|
* espejo del cliente en protocol_client.ts). ASCII-only.
|
|
20
19
|
*/
|
|
20
|
+
import { forwardInvoke } from './panel_invoke.js';
|
|
21
21
|
/* PND-064 -- una sola tool `nac3_invoke` que proxea el catalogo
|
|
22
22
|
* COMPLETO de verbos yujin (igual que el modo API). El subproceso
|
|
23
23
|
* ya tiene el manifest de verbos en su system prompt; esta tool es
|
|
@@ -55,31 +55,10 @@ function logErr(msg) {
|
|
|
55
55
|
}
|
|
56
56
|
catch { /* ignore */ }
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const token = process.env['FORGE_PANEL_TOKEN'];
|
|
63
|
-
if (!port || !token) {
|
|
64
|
-
return { ok: false, text: 'El canal de Forge no esta configurado (sin puerto/token).' };
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
const r = await fetch('http://127.0.0.1:' + port + '/api/forge/panel-invoke', {
|
|
68
|
-
method: 'POST',
|
|
69
|
-
headers: { 'content-type': 'application/json' },
|
|
70
|
-
body: JSON.stringify({ token, verb_id: verbId, args }),
|
|
71
|
-
});
|
|
72
|
-
if (!r.ok) {
|
|
73
|
-
return { ok: false, text: 'Forge rechazo la invocacion (HTTP ' + r.status + ').' };
|
|
74
|
-
}
|
|
75
|
-
const j = await r.json();
|
|
76
|
-
return { ok: j.ok !== false, text: j.text || 'Listo.' };
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
80
|
-
return { ok: false, text: 'No pude contactar a Forge: ' + m };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
58
|
+
/* El reenvio al chat server vive en panel_invoke.ts (PND-235). Estaba
|
|
59
|
+
* duplicado aca y en forge_verbs_server, y las dos copias esperaban la
|
|
60
|
+
* respuesta con `fetch`, heredando el techo de cinco minutos de undici: el
|
|
61
|
+
* mismo defecto, dos veces, y arreglado una sola. */
|
|
83
62
|
function toolResult(id, text, isError = false) {
|
|
84
63
|
send({
|
|
85
64
|
jsonrpc: '2.0',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forge_panel_server.js","sourceRoot":"","sources":["../../src/mcp/forge_panel_server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"forge_panel_server.js","sourceRoot":"","sources":["../../src/mcp/forge_panel_server.ts"],"names":[],"mappings":"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"}
|