4runr-os 2.10.55 → 2.10.57

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ddos-protection.d.ts","sourceRoot":"","sources":["../../../../../src/middleware/ddos-protection.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA2NvD,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAoDf;AAKD,wBAAgB,YAAY;;;;;;;;;EAW3B"}
1
+ {"version":3,"file":"ddos-protection.d.ts","sourceRoot":"","sources":["../../../../../src/middleware/ddos-protection.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA2OvD,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAwDf;AAKD,wBAAgB,YAAY;;;;;;;;;EAW3B"}
@@ -22,6 +22,18 @@ function generateFingerprint(request) {
22
22
  const data = `${ip}:${userAgent}:${accept}:${acceptLanguage}`;
23
23
  return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
24
24
  }
25
+ function isLoopbackIP(ip) {
26
+ const n = ip.toLowerCase();
27
+ return (n === '127.0.0.1' ||
28
+ n === '::1' ||
29
+ n === '::ffff:127.0.0.1' ||
30
+ n === 'localhost');
31
+ }
32
+ function loopbackExemptFromDdos() {
33
+ if (process.env['DDOS_LOOPBACK_EXEMPT'] === 'false')
34
+ return false;
35
+ return process.env['NODE_ENV'] !== 'production';
36
+ }
25
37
  function getClientIP(request) {
26
38
  const forwarded = request.headers['x-forwarded-for'];
27
39
  if (forwarded) {
@@ -138,6 +150,9 @@ export async function ddosProtection(request, reply) {
138
150
  return;
139
151
  }
140
152
  const ip = getClientIP(request);
153
+ if (loopbackExemptFromDdos() && isLoopbackIP(ip)) {
154
+ return;
155
+ }
141
156
  const userAgent = request.headers['user-agent'] || 'unknown';
142
157
  if (isIPBlocked(ip)) {
143
158
  const block = ipBlocks.get(ip);
@@ -1 +1 @@
1
- {"version":3,"file":"ddos-protection.js","sourceRoot":"","sources":["../../../../../src/middleware/ddos-protection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ddos-protection.js","sourceRoot":"","sources":["../../../../../src/middleware/ddos-protection.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,MAAM,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AAsBvD,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAA8B,CAAC;AAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;AAC5C,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA8C,CAAC;AAG/E,MAAM,MAAM,GAAG;IAEb,uBAAuB,EAAE,GAAG;IAC5B,uBAAuB,EAAE,EAAE;IAC3B,uBAAuB,EAAE,EAAE;IAG3B,wBAAwB,EAAE,GAAG;IAC7B,iBAAiB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACjC,wBAAwB,EAAE,EAAE;IAG5B,mBAAmB,EAAE,EAAE,GAAG,IAAI;IAC9B,kBAAkB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;CACnC,CAAC;AAKF,SAAS,mBAAmB,CAAC,OAAuB;IAClD,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/C,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAEhE,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,SAAS,IAAI,MAAM,IAAI,cAAc,EAAE,CAAC;IAC9D,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,YAAY,CAAC,EAAU;IAC9B,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3B,OAAO,CACL,CAAC,KAAK,WAAW;QACjB,CAAC,KAAK,KAAK;QACX,CAAC,KAAK,kBAAkB;QACxB,CAAC,KAAK,WAAW,CAClB,CAAC;AACJ,CAAC;AAGD,SAAS,sBAAsB;IAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAClE,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,CAAC;AAClD,CAAC;AAKD,SAAS,WAAW,CAAC,OAAuB;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;QACrC,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,OAAO,OAAO,CAAC,EAAE,IAAI,SAAS,IAAI,SAAS,CAAC;AAC9C,CAAC;AAKD,SAAS,WAAW,CAAC,EAAU;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;QAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAKD,SAAS,OAAO,CAAC,EAAU,EAAE,MAAc,EAAE,eAAuB,CAAC;IACnE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE;QACf,EAAE;QACF,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,YAAY;QACZ,MAAM;QACN,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC;AAKD,SAAS,cAAc,CAAC,WAAmB,EAAE,EAAU;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,UAAU,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC;IACrE,MAAM,SAAS,GAAG,UAAU,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC;IAGpE,IAAI,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,CAAC,YAAY,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QACjD,YAAY,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,CAAC;QAClD,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAChD,CAAC;IACD,YAAY,CAAC,KAAK,EAAE,CAAC;IAErB,IAAI,YAAY,CAAC,KAAK,GAAG,MAAM,CAAC,uBAAuB,EAAE,CAAC;QACxD,OAAO,CAAC,EAAE,EAAE,qBAAqB,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACxE,CAAC;IAGD,IAAI,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,CAAC,YAAY,IAAI,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QACjD,YAAY,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,CAAC;QACjD,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAChD,CAAC;IACD,YAAY,CAAC,KAAK,EAAE,CAAC;IAErB,IAAI,YAAY,CAAC,KAAK,GAAG,MAAM,CAAC,uBAAuB,EAAE,CAAC;QACxD,OAAO,CAAC,EAAE,EAAE,qBAAqB,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACxE,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAKD,SAAS,gBAAgB,CAAC,WAAmB,EAAE,EAAU,EAAE,SAAiB;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEtD,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,QAAQ,CAAC,QAAQ,GAAG,GAAG,CAAC;QAGxB,IAAI,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,wBAAwB,EAAE,CAAC;YACrD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACtB,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;gBACxB,QAAQ,CAAC,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC;gBACvD,OAAO,CAAC,EAAE,EAAE,qBAAqB,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,mBAAmB,CAAC,GAAG,CAAC,WAAW,EAAE;YACnC,EAAE;YACF,SAAS;YACT,WAAW;YACX,KAAK,EAAE,CAAC;YACR,SAAS,EAAE,GAAG;YACd,QAAQ,EAAE,GAAG;YACb,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAKD,SAAS,OAAO;IACd,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAGvB,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC;QACtD,IAAI,GAAG,GAAG,EAAE,CAAC,QAAQ,GAAG,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAClD,mBAAmB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAGD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,GAAG,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;YAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAGD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,gBAAgB,CAAC,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1B,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;AACH,CAAC;AAGD,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAGjD,SAAS,iBAAiB,CAAC,MAAc,EAAE,MAAc;IACvD,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACnC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,UAAU,CAAC;AACxE,CAAC;AAKD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAuB,EACvB,KAAmB;IAEnB,IAAI,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD,OAAO;IACT,CAAC;IAED,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,sBAAsB,EAAE,IAAI,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;QACjD,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC;IAG7D,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QAEvE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACrB,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE,mCAAmC;YAC5C,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,WAAW,EAAE,UAAU;YACvB,aAAa,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE;SAC1D,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAGD,MAAM,WAAW,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAGjD,MAAM,cAAc,GAAG,cAAc,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QAC5B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACrB,KAAK,EAAE,cAAc;YACrB,OAAO,EAAE,cAAc,CAAC,MAAM,IAAI,mBAAmB;SACtD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAGD,gBAAgB,CAAC,WAAW,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;IAG7C,MAAM,EAAE,GAAG,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC;QACnE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACrB,KAAK,EAAE,qBAAqB;YAC5B,OAAO,EAAE,uCAAuC;SACjD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAGA,OAAe,CAAC,eAAe,GAAG,WAAW,CAAC;IAC9C,OAAe,CAAC,QAAQ,GAAG,EAAE,CAAC;AACjC,CAAC;AAKD,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,UAAU,EAAE,QAAQ,CAAC,IAAI;QACzB,mBAAmB,EAAE,mBAAmB,CAAC,IAAI;QAC7C,sBAAsB,EAAE,gBAAgB,CAAC,IAAI;QAC7C,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACtD,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,YAAY,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE;SACrD,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -3546,9 +3546,9 @@
3546
3546
  "license": "MIT"
3547
3547
  },
3548
3548
  "node_modules/baseline-browser-mapping": {
3549
- "version": "2.10.32",
3550
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
3551
- "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
3549
+ "version": "2.10.33",
3550
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
3551
+ "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
3552
3552
  "dev": true,
3553
3553
  "license": "Apache-2.0",
3554
3554
  "bin": {
@@ -64,6 +64,22 @@ function generateFingerprint(request: FastifyRequest): string {
64
64
  return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
65
65
  }
66
66
 
67
+ function isLoopbackIP(ip: string): boolean {
68
+ const n = ip.toLowerCase();
69
+ return (
70
+ n === '127.0.0.1' ||
71
+ n === '::1' ||
72
+ n === '::ffff:127.0.0.1' ||
73
+ n === 'localhost'
74
+ );
75
+ }
76
+
77
+ /** Local dev + smoke tests: loopback is not DDoS-blocked unless explicitly disabled. */
78
+ function loopbackExemptFromDdos(): boolean {
79
+ if (process.env['DDOS_LOOPBACK_EXEMPT'] === 'false') return false;
80
+ return process.env['NODE_ENV'] !== 'production';
81
+ }
82
+
67
83
  /**
68
84
  * Get client IP address
69
85
  */
@@ -232,6 +248,10 @@ export async function ddosProtection(
232
248
  }
233
249
 
234
250
  const ip = getClientIP(request);
251
+ if (loopbackExemptFromDdos() && isLoopbackIP(ip)) {
252
+ return;
253
+ }
254
+
235
255
  const userAgent = request.headers['user-agent'] || 'unknown';
236
256
 
237
257
  // Check if IP is blocked
@@ -452,6 +452,7 @@ pub struct AppState {
452
452
 
453
453
  // Step 6: Gateway runs
454
454
  pub pending_run_list_id: Option<String>,
455
+ pub pending_run_list_silent: bool,
455
456
  pub pending_run_get_id: Option<String>,
456
457
  pub pending_run_cancel_id: Option<String>,
457
458
  pub pending_run_quick_id: Option<String>,
@@ -537,6 +538,7 @@ impl Default for AppState {
537
538
  pending_monitoring_drill_id: None,
538
539
  pending_setup_detect_id: None,
539
540
  pending_run_list_id: None,
541
+ pending_run_list_silent: false,
540
542
  pending_run_get_id: None,
541
543
  pending_run_cancel_id: None,
542
544
  pending_run_quick_id: None,
@@ -778,6 +780,46 @@ impl App {
778
780
  }
779
781
  }
780
782
 
783
+ /// Fetch run list from Gateway (`run.list`). `silent` = background poll (no loading spinner).
784
+ pub fn begin_run_list_request(&mut self, ws: &WebSocketClient, silent: bool) {
785
+ if self.state.operation_mode != OperationMode::Connected {
786
+ return;
787
+ }
788
+ if self.state.pending_run_list_id.is_some() {
789
+ return;
790
+ }
791
+ if !silent && self.state.run_manager.loading {
792
+ return;
793
+ }
794
+ let data = serde_json::json!({ "limit": 50 });
795
+ match ws.send_command("run.list", Some(data)) {
796
+ Ok(id) => {
797
+ self.state.pending_run_list_id = Some(id);
798
+ self.state.pending_run_list_silent = silent;
799
+ if !silent {
800
+ self.state.run_manager.loading = true;
801
+ }
802
+ }
803
+ Err(e) => {
804
+ self.add_log(format!("[RUN] run.list failed: {}", e));
805
+ }
806
+ }
807
+ }
808
+
809
+ /// Refresh selected run detail (`run.get`) during live polling.
810
+ pub fn begin_run_get_request(&mut self, ws: &WebSocketClient, run_id: &str) {
811
+ if self.state.operation_mode != OperationMode::Connected {
812
+ return;
813
+ }
814
+ if self.state.pending_run_get_id.is_some() {
815
+ return;
816
+ }
817
+ let data = serde_json::json!({ "runId": run_id });
818
+ if let Ok(id) = ws.send_command("run.get", Some(data)) {
819
+ self.state.pending_run_get_id = Some(id);
820
+ }
821
+ }
822
+
781
823
  /// Phase 2: refresh one Portal Monitoring section via CLI (`monitoring.refresh` → Gateway APIs).
782
824
  pub fn begin_monitoring_refresh_request(
783
825
  &mut self,
@@ -1415,13 +1457,7 @@ impl App {
1415
1457
  .push_back("[NAV] Opening Run Manager...".into());
1416
1458
  if self.state.operation_mode == OperationMode::Connected {
1417
1459
  if let Some(ws) = ws_client {
1418
- if !self.state.run_manager.loading {
1419
- let data = serde_json::json!({ "limit": 50 });
1420
- if let Ok(id) = ws.send_command("run.list", Some(data)) {
1421
- self.state.pending_run_list_id = Some(id);
1422
- self.state.run_manager.loading = true;
1423
- }
1424
- }
1460
+ self.begin_run_list_request(ws, false);
1425
1461
  }
1426
1462
  } else {
1427
1463
  self.add_log(
@@ -2212,18 +2248,12 @@ impl App {
2212
2248
  self.request_render("run_manager_sort");
2213
2249
  }
2214
2250
 
2215
- // Refresh
2251
+ // Refresh (manual)
2216
2252
  KeyCode::Char('r') | KeyCode::Char('R') => {
2217
2253
  if self.state.operation_mode == OperationMode::Connected {
2218
- if self.state.run_manager.loading {
2219
- self.add_log("[RUN] Refresh already in progress...".to_string());
2220
- } else if let Some(ws) = ws_client {
2221
- let data = serde_json::json!({ "limit": 50 });
2222
- if let Ok(id) = ws.send_command("run.list", Some(data)) {
2223
- self.state.pending_run_list_id = Some(id);
2224
- self.state.run_manager.loading = true;
2225
- self.add_log("[RUN] Refreshing run list from Gateway...".to_string());
2226
- }
2254
+ if let Some(ws) = ws_client {
2255
+ self.begin_run_list_request(ws, false);
2256
+ self.add_log("[RUN] Refreshing run list from Gateway...".to_string());
2227
2257
  } else {
2228
2258
  self.add_log("[RUN] WebSocket not connected".to_string());
2229
2259
  }
@@ -2233,6 +2263,19 @@ impl App {
2233
2263
  self.request_render("run_manager_refresh");
2234
2264
  }
2235
2265
 
2266
+ // Toggle live auto-refresh (poll every ~3s)
2267
+ KeyCode::Char('a') | KeyCode::Char('A') => {
2268
+ self.state.run_manager.auto_refresh_enabled =
2269
+ !self.state.run_manager.auto_refresh_enabled;
2270
+ let status = if self.state.run_manager.auto_refresh_enabled {
2271
+ "on"
2272
+ } else {
2273
+ "off"
2274
+ };
2275
+ self.add_log(format!("[RUN] Live auto-refresh {}", status));
2276
+ self.request_render("run_manager_live_toggle");
2277
+ }
2278
+
2236
2279
  // View details / Close detail view
2237
2280
  KeyCode::Enter => {
2238
2281
  if self.state.run_manager.is_detail_view() {
@@ -18,7 +18,7 @@ mod storage;
18
18
  mod ui;
19
19
  mod websocket;
20
20
 
21
- use app::App;
21
+ use app::{App, OperationMode};
22
22
  use io::IoHandler;
23
23
  use websocket::{WebSocketClient, WsClientMessage};
24
24
 
@@ -189,6 +189,42 @@ fn main() -> Result<()> {
189
189
  }
190
190
  }
191
191
 
192
+ // Run Manager: live poll run.list every ~3s when enabled (no manual R)
193
+ if matches!(current_screen, Screen::RunManager)
194
+ && app.state.operation_mode == OperationMode::Connected
195
+ {
196
+ let run_poll = {
197
+ let rm = &app.state.run_manager;
198
+ if !rm.auto_refresh_enabled {
199
+ None
200
+ } else {
201
+ let due = rm
202
+ .last_refresh
203
+ .map(|lr| lr.elapsed() >= rm.auto_refresh_interval)
204
+ .unwrap_or(true);
205
+ if !due {
206
+ None
207
+ } else {
208
+ let detail_run_id = if rm.is_detail_view() {
209
+ rm.selected_run().map(|r| r.id.clone())
210
+ } else {
211
+ None
212
+ };
213
+ Some(detail_run_id)
214
+ }
215
+ }
216
+ };
217
+ if let Some(detail_run_id) = run_poll {
218
+ if let Some(ws) = &ws_client {
219
+ app.state.run_manager.last_refresh = Some(Instant::now());
220
+ app.begin_run_list_request(ws, true);
221
+ if let Some(run_id) = detail_run_id {
222
+ app.begin_run_get_request(ws, &run_id);
223
+ }
224
+ }
225
+ }
226
+ }
227
+
192
228
  // ┌─────────────────────────────────────────────────────────┐
193
229
  // │ STEP 2: CHECK WEBSOCKET MESSAGES │
194
230
  // └─────────────────────────────────────────────────────────┘
@@ -423,7 +459,9 @@ fn main() -> Result<()> {
423
459
  else if Some(&resp.id)
424
460
  == app.state.pending_run_list_id.as_ref()
425
461
  {
462
+ let silent = app.state.pending_run_list_silent;
426
463
  app.state.pending_run_list_id = None;
464
+ app.state.pending_run_list_silent = false;
427
465
  app.state.run_manager.loading = false;
428
466
  if let Some(runs_arr) =
429
467
  obj.get("runs").and_then(|r| r.as_array())
@@ -437,12 +475,22 @@ fn main() -> Result<()> {
437
475
  parsed.push(info);
438
476
  }
439
477
  }
440
- app.state.run_manager.replace_runs(parsed);
441
- app.add_log(format!(
442
- "✓ [{}] Loaded {} run(s) from Gateway",
443
- short_id,
444
- app.state.run_manager.runs.len()
445
- ));
478
+ let preserve =
479
+ !app.state.run_manager.runs.is_empty();
480
+ if preserve {
481
+ app.state
482
+ .run_manager
483
+ .replace_runs_preserve(parsed);
484
+ } else {
485
+ app.state.run_manager.replace_runs(parsed);
486
+ }
487
+ if !silent {
488
+ app.add_log(format!(
489
+ "✓ [{}] Loaded {} run(s) from Gateway",
490
+ short_id,
491
+ app.state.run_manager.runs.len()
492
+ ));
493
+ }
446
494
  app.request_render("run_list_loaded");
447
495
  } else {
448
496
  app.add_log(format!(
@@ -1288,6 +1336,7 @@ fn main() -> Result<()> {
1288
1336
  ));
1289
1337
  } else if Some(&resp.id) == app.state.pending_run_list_id.as_ref() {
1290
1338
  app.state.pending_run_list_id = None;
1339
+ app.state.pending_run_list_silent = false;
1291
1340
  app.state.run_manager.loading = false;
1292
1341
  app.add_log(format!(
1293
1342
  "✗ [{}] run.list failed: {}",
@@ -40,6 +40,10 @@ pub struct RunManagerState {
40
40
 
41
41
  /// Detailed view state (None = list view, Some(index) = detail view)
42
42
  pub detail_view: Option<usize>,
43
+
44
+ /// Poll Gateway for run list while Run Manager is open (no manual R).
45
+ pub auto_refresh_enabled: bool,
46
+ pub auto_refresh_interval: std::time::Duration,
43
47
  }
44
48
 
45
49
  #[derive(Debug, Clone)]
@@ -153,6 +157,8 @@ impl Default for RunManagerState {
153
157
  loading: false,
154
158
  last_refresh: None,
155
159
  detail_view: None,
160
+ auto_refresh_enabled: true,
161
+ auto_refresh_interval: std::time::Duration::from_secs(3),
156
162
  }
157
163
  }
158
164
  }
@@ -282,6 +288,36 @@ impl RunManagerState {
282
288
  self.last_refresh = Some(std::time::Instant::now());
283
289
  }
284
290
 
291
+ /// Auto-refresh: keep selection and detail view when the selected run still exists.
292
+ pub fn replace_runs_preserve(&mut self, runs: Vec<RunInfo>) {
293
+ let selected_id = self.selected_run().map(|r| r.id.clone());
294
+ let in_detail = self.detail_view.is_some();
295
+ self.runs = runs;
296
+ self.loading = false;
297
+ self.last_refresh = Some(std::time::Instant::now());
298
+
299
+ let filtered = self.filtered_runs();
300
+ if filtered.is_empty() {
301
+ self.selected_index = 0;
302
+ self.detail_view = None;
303
+ return;
304
+ }
305
+
306
+ if let Some(id) = selected_id {
307
+ if let Some(idx) = filtered.iter().position(|r| r.id == id) {
308
+ self.selected_index = idx;
309
+ } else if self.selected_index >= filtered.len() {
310
+ self.selected_index = filtered.len() - 1;
311
+ }
312
+ } else if self.selected_index >= filtered.len() {
313
+ self.selected_index = filtered.len() - 1;
314
+ }
315
+
316
+ if in_detail && self.selected_run().is_none() {
317
+ self.detail_view = None;
318
+ }
319
+ }
320
+
285
321
  pub fn clear_runs(&mut self) {
286
322
  self.runs.clear();
287
323
  self.selected_index = 0;
@@ -425,12 +461,19 @@ fn render_header(f: &mut Frame, area: Rect, state: &RunManagerState) {
425
461
  let filtered_count = state.filtered_runs().len();
426
462
  let total_count = state.runs.len();
427
463
 
464
+ let live = if state.auto_refresh_enabled {
465
+ " · LIVE"
466
+ } else {
467
+ ""
468
+ };
469
+
428
470
  let block = Block::default()
429
471
  .title(format!(
430
- " ⚡ Run Manager - {} ({}/{} runs) ",
472
+ " ⚡ Run Manager - {} ({}/{} runs){} ",
431
473
  state.filter.as_str(),
432
474
  filtered_count,
433
- total_count
475
+ total_count,
476
+ live
434
477
  ))
435
478
  .borders(Borders::ALL)
436
479
  .border_style(Style::default().fg(BRAND_PURPLE))
@@ -776,6 +819,8 @@ fn render_actions(f: &mut Frame, area: Rect) {
776
819
  Span::styled(" Sort │ ", Style::default().fg(TEXT_DIM)),
777
820
  Span::styled("R", Style::default().fg(NEON_GREEN).bold()),
778
821
  Span::styled(" Refresh │ ", Style::default().fg(TEXT_DIM)),
822
+ Span::styled("A", Style::default().fg(CYBER_CYAN).bold()),
823
+ Span::styled(" Live │ ", Style::default().fg(TEXT_DIM)),
779
824
  Span::styled("C", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
780
825
  Span::styled(" Cancel │ ", Style::default().fg(TEXT_DIM)),
781
826
  Span::styled("D", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.10.55",
3
+ "version": "2.10.57",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.55: Gateway auto run retention (cap terminal runs); Run Manager clears on disconnect + local API key; smoke tags; 4Runr Tools bundle.",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.57: Fix mk3-tui compile (Run Manager live refresh borrow); DDoS loopback exempt; run retention + 4Runr Tools.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",
@@ -395,11 +395,12 @@ Env:
395
395
 
396
396
  await checkSentinelHealth(base, apiKey);
397
397
  await checkShieldHealth(base, apiKey);
398
+ await sleep(1500);
398
399
  await smokeSentinelCancel(base, apiKey);
399
400
  if (args.full) {
400
- await sleep(3000);
401
+ await sleep(5000);
401
402
  await smokeSentinelIdleKill(base, apiKey);
402
- await sleep(3000);
403
+ await sleep(5000);
403
404
  } else {
404
405
  console.log(colors.dim(' (skip N5 idle kill — use --full)'));
405
406
  }