@shawnstack/quickforge 1.3.12 → 1.3.14

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.
Files changed (65) hide show
  1. package/README.md +348 -348
  2. package/dist/assets/{anthropic-xAV_KBCN.js → anthropic-DGgbbhP2.js} +1 -1
  3. package/dist/assets/{azure-openai-responses-BczDocGX.js → azure-openai-responses-iVkKls8h.js} +1 -1
  4. package/dist/assets/{google-DlBM2UuM.js → google-CfWayb6J.js} +1 -1
  5. package/dist/assets/{google-gemini-cli-BNOncgB1.js → google-gemini-cli-BKFbEcDj.js} +1 -1
  6. package/dist/assets/{google-vertex-B9BjJowW.js → google-vertex-Di6pCCaT.js} +1 -1
  7. package/dist/assets/{icons-pEANlMqZ.js → icons-BHkxP7oT.js} +1 -1
  8. package/dist/assets/{index-Cq4dknqT.js → index-CgTJgJ5U.js} +570 -520
  9. package/dist/assets/index-t6ITXfOr.css +3 -0
  10. package/dist/assets/{mistral-Dldc_EeW.js → mistral-DGp-bWeK.js} +1 -1
  11. package/dist/assets/{openai-codex-responses-vFjB6yfe.js → openai-codex-responses-CFSjwMXz.js} +1 -1
  12. package/dist/assets/{openai-completions-DPFPTHxA.js → openai-completions-DlNb8Upk.js} +1 -1
  13. package/dist/assets/{openai-responses-B9SrG5r6.js → openai-responses-DulHAAeh.js} +1 -1
  14. package/dist/assets/{openai-responses-shared-DCiYCDet.js → openai-responses-shared-BWikZIo_.js} +1 -1
  15. package/dist/assets/{react-vendor-ORf0unE-.js → react-vendor-CmyL2roG.js} +1 -1
  16. package/dist/index.html +10 -4
  17. package/dist/manifest.webmanifest +30 -0
  18. package/dist/pwa-icon-192.png +0 -0
  19. package/dist/pwa-icon-512.png +0 -0
  20. package/dist/pwa-maskable-512.png +0 -0
  21. package/dist/sw.js +79 -0
  22. package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +2 -2
  23. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +201 -0
  24. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +62 -0
  25. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +156 -0
  26. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +2 -0
  27. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +16 -0
  28. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +80 -0
  29. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +8 -0
  30. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +11 -0
  31. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +10 -0
  32. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +4 -0
  33. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +5 -0
  34. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +7 -0
  35. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +7 -0
  36. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +8 -0
  37. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +69 -0
  38. package/node_modules/@aws-sdk/token-providers/package.json +1 -1
  39. package/node_modules/es-object-atoms/CHANGELOG.md +21 -14
  40. package/node_modules/es-object-atoms/package.json +6 -7
  41. package/node_modules/es-object-atoms/tsconfig.json +1 -0
  42. package/node_modules/hono/dist/adapter/deno/websocket.js +5 -1
  43. package/node_modules/hono/dist/cjs/adapter/deno/websocket.js +5 -1
  44. package/node_modules/hono/dist/cjs/index.js +3 -0
  45. package/node_modules/hono/dist/cjs/middleware/compress/index.js +31 -5
  46. package/node_modules/hono/dist/cjs/utils/compress.js +1 -1
  47. package/node_modules/hono/dist/cjs/utils/filepath.js +1 -1
  48. package/node_modules/hono/dist/cjs/utils/ipaddr.js +1 -1
  49. package/node_modules/hono/dist/cjs/utils/mime.js +15 -17
  50. package/node_modules/hono/dist/index.js +2 -0
  51. package/node_modules/hono/dist/middleware/compress/index.js +30 -5
  52. package/node_modules/hono/dist/utils/compress.js +1 -1
  53. package/node_modules/hono/dist/utils/filepath.js +1 -1
  54. package/node_modules/hono/dist/utils/ipaddr.js +1 -1
  55. package/node_modules/hono/dist/utils/mime.js +15 -17
  56. package/node_modules/hono/package.json +3 -3
  57. package/node_modules/ws/lib/receiver.js +54 -0
  58. package/node_modules/ws/lib/websocket-server.js +8 -0
  59. package/node_modules/ws/lib/websocket.js +14 -0
  60. package/node_modules/ws/package.json +1 -1
  61. package/package.json +1 -1
  62. package/server/agent-manager.mjs +45 -10
  63. package/server/auto-compaction.mjs +229 -0
  64. package/server/routes/workspace.mjs +33 -1
  65. package/dist/assets/index-Dq1Kwzwg.css +0 -3
@@ -1,5 +1,5 @@
1
1
  // src/utils/compress.ts
2
- var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
2
+ var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|msgpack|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|vnd\.msgpack|wasm|x-httpd-php|x-javascript|x-msgpack|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml|msgpack))(?:[;\s]|$)/i;
3
3
  export {
4
4
  COMPRESSIBLE_CONTENT_TYPE_REGEX
5
5
  };
@@ -20,7 +20,7 @@ var getFilePathWithoutDefaultDocument = (options) => {
20
20
  return;
21
21
  }
22
22
  filename = filename.replace(/^\.?[\/\\]/, "");
23
- filename = filename.replace(/\\/, "/");
23
+ filename = filename.replace(/\\/g, "/");
24
24
  root = root.replace(/\/$/, "");
25
25
  let path = root ? root + "/" + filename : filename;
26
26
  path = path.replace(/^\.?\//, "");
@@ -265,7 +265,7 @@ var convertIPv6BinaryToString = (ipV6) => {
265
265
  maxZeroEnd = 8;
266
266
  }
267
267
  }
268
- if (maxZeroStart !== -1) {
268
+ if (maxZeroStart !== -1 && maxZeroEnd - maxZeroStart > 1) {
269
269
  sections.splice(maxZeroStart, maxZeroEnd - maxZeroStart, ":");
270
270
  }
271
271
  return sections.join(":").replace(/:{2,}/g, "::");
@@ -5,15 +5,13 @@ var getMimeType = (filename, mimes = baseMimes) => {
5
5
  if (!match) {
6
6
  return;
7
7
  }
8
- let mimeType = mimes[match[1].toLowerCase()];
9
- if (mimeType && mimeType.startsWith("text")) {
10
- mimeType += "; charset=utf-8";
11
- }
12
- return mimeType;
8
+ return mimes[match[1].toLowerCase()];
13
9
  };
14
10
  var getExtension = (mimeType) => {
11
+ const baseType = mimeType.split(";", 1)[0].trim();
15
12
  for (const ext in baseMimes) {
16
- if (baseMimes[ext] === mimeType) {
13
+ const stored = baseMimes[ext];
14
+ if (stored === mimeType || stored.split(";", 1)[0].trim() === baseType) {
17
15
  return ext;
18
16
  }
19
17
  }
@@ -25,25 +23,25 @@ var _baseMimes = {
25
23
  av1: "video/av1",
26
24
  bin: "application/octet-stream",
27
25
  bmp: "image/bmp",
28
- css: "text/css",
29
- csv: "text/csv",
26
+ css: "text/css; charset=utf-8",
27
+ csv: "text/csv; charset=utf-8",
30
28
  eot: "application/vnd.ms-fontobject",
31
29
  epub: "application/epub+zip",
32
30
  gif: "image/gif",
33
31
  gz: "application/gzip",
34
- htm: "text/html",
35
- html: "text/html",
32
+ htm: "text/html; charset=utf-8",
33
+ html: "text/html; charset=utf-8",
36
34
  ico: "image/x-icon",
37
- ics: "text/calendar",
35
+ ics: "text/calendar; charset=utf-8",
38
36
  jpeg: "image/jpeg",
39
37
  jpg: "image/jpeg",
40
- js: "text/javascript",
38
+ js: "text/javascript; charset=utf-8",
41
39
  json: "application/json",
42
40
  jsonld: "application/ld+json",
43
41
  map: "application/json",
44
42
  mid: "audio/x-midi",
45
43
  midi: "audio/x-midi",
46
- mjs: "text/javascript",
44
+ mjs: "text/javascript; charset=utf-8",
47
45
  mp3: "audio/mpeg",
48
46
  mp4: "video/mp4",
49
47
  mpeg: "video/mpeg",
@@ -55,12 +53,12 @@ var _baseMimes = {
55
53
  pdf: "application/pdf",
56
54
  png: "image/png",
57
55
  rtf: "application/rtf",
58
- svg: "image/svg+xml",
56
+ svg: "image/svg+xml; charset=utf-8",
59
57
  tif: "image/tiff",
60
58
  tiff: "image/tiff",
61
59
  ts: "video/mp2t",
62
60
  ttf: "font/ttf",
63
- txt: "text/plain",
61
+ txt: "text/plain; charset=utf-8",
64
62
  wasm: "application/wasm",
65
63
  webm: "video/webm",
66
64
  weba: "audio/webm",
@@ -68,8 +66,8 @@ var _baseMimes = {
68
66
  webp: "image/webp",
69
67
  woff: "font/woff",
70
68
  woff2: "font/woff2",
71
- xhtml: "application/xhtml+xml",
72
- xml: "application/xml",
69
+ xhtml: "application/xhtml+xml; charset=utf-8",
70
+ xml: "application/xml; charset=utf-8",
73
71
  zip: "application/zip",
74
72
  "3gp": "video/3gpp",
75
73
  "3g2": "video/3gpp2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "4.12.21",
3
+ "version": "4.12.23",
4
4
  "description": "Web framework built on Web Standards",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -663,7 +663,7 @@
663
663
  "@types/node": "^24.3.0",
664
664
  "@types/ws": "^8.18.1",
665
665
  "@typescript/native-preview": "7.0.0-dev.20260210.1",
666
- "@vitest/coverage-v8": "^3.2.4",
666
+ "@vitest/coverage-v8": "^4.1.7",
667
667
  "arg": "^5.0.2",
668
668
  "bun-types": "^1.2.20",
669
669
  "editorconfig-checker": "6.1.1",
@@ -680,7 +680,7 @@
680
680
  "typescript": "^5.9.2",
681
681
  "undici": "^6.21.3",
682
682
  "vite-plugin-fastly-js-compute": "^0.4.2",
683
- "vitest": "^3.2.4",
683
+ "vitest": "^4.1.7",
684
684
  "wrangler": "4.12.0",
685
685
  "ws": "^8.18.0",
686
686
  "zod": "^3.23.8"
@@ -40,6 +40,10 @@ class Receiver extends Writable {
40
40
  * extensions
41
41
  * @param {Boolean} [options.isServer=false] Specifies whether to operate in
42
42
  * client or server mode
43
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
44
+ * buffered data chunks
45
+ * @param {Number} [options.maxFragments=0] The maximum number of message
46
+ * fragments
43
47
  * @param {Number} [options.maxPayload=0] The maximum allowed message length
44
48
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
45
49
  * not to skip UTF-8 validation for text and close messages
@@ -54,6 +58,8 @@ class Receiver extends Writable {
54
58
  this._binaryType = options.binaryType || BINARY_TYPES[0];
55
59
  this._extensions = options.extensions || {};
56
60
  this._isServer = !!options.isServer;
61
+ this._maxBufferedChunks = options.maxBufferedChunks | 0;
62
+ this._maxFragments = options.maxFragments | 0;
57
63
  this._maxPayload = options.maxPayload | 0;
58
64
  this._skipUTF8Validation = !!options.skipUTF8Validation;
59
65
  this[kWebSocket] = undefined;
@@ -89,6 +95,22 @@ class Receiver extends Writable {
89
95
  _write(chunk, encoding, cb) {
90
96
  if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
91
97
 
98
+ if (
99
+ this._maxBufferedChunks > 0 &&
100
+ this._buffers.length >= this._maxBufferedChunks
101
+ ) {
102
+ cb(
103
+ this.createError(
104
+ RangeError,
105
+ 'Too many buffered chunks',
106
+ false,
107
+ 1008,
108
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
109
+ )
110
+ );
111
+ return;
112
+ }
113
+
92
114
  this._bufferedBytes += chunk.length;
93
115
  this._buffers.push(chunk);
94
116
  this.startLoop(cb);
@@ -485,6 +507,22 @@ class Receiver extends Writable {
485
507
  }
486
508
 
487
509
  if (data.length) {
510
+ if (
511
+ this._maxFragments > 0 &&
512
+ this._fragments.length >= this._maxFragments
513
+ ) {
514
+ const error = this.createError(
515
+ RangeError,
516
+ 'Too many message fragments',
517
+ false,
518
+ 1008,
519
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
520
+ );
521
+
522
+ cb(error);
523
+ return;
524
+ }
525
+
488
526
  //
489
527
  // This message is not compressed so its length is the sum of the payload
490
528
  // length of all fragments.
@@ -524,6 +562,22 @@ class Receiver extends Writable {
524
562
  return;
525
563
  }
526
564
 
565
+ if (
566
+ this._maxFragments > 0 &&
567
+ this._fragments.length >= this._maxFragments
568
+ ) {
569
+ const error = this.createError(
570
+ RangeError,
571
+ 'Too many message fragments',
572
+ false,
573
+ 1008,
574
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
575
+ );
576
+
577
+ cb(error);
578
+ return;
579
+ }
580
+
527
581
  this._fragments.push(buf);
528
582
  }
529
583
 
@@ -43,6 +43,10 @@ class WebSocketServer extends EventEmitter {
43
43
  * called
44
44
  * @param {Function} [options.handleProtocols] A hook to handle protocols
45
45
  * @param {String} [options.host] The hostname where to bind the server
46
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
47
+ * buffered data chunks
48
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
49
+ * fragments
46
50
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
47
51
  * size
48
52
  * @param {Boolean} [options.noServer=false] Enable no server mode
@@ -65,6 +69,8 @@ class WebSocketServer extends EventEmitter {
65
69
  options = {
66
70
  allowSynchronousEvents: true,
67
71
  autoPong: true,
72
+ maxBufferedChunks: 1024 * 1024,
73
+ maxFragments: 128 * 1024,
68
74
  maxPayload: 100 * 1024 * 1024,
69
75
  skipUTF8Validation: false,
70
76
  perMessageDeflate: false,
@@ -424,6 +430,8 @@ class WebSocketServer extends EventEmitter {
424
430
 
425
431
  ws.setSocket(socket, head, {
426
432
  allowSynchronousEvents: this.options.allowSynchronousEvents,
433
+ maxBufferedChunks: this.options.maxBufferedChunks,
434
+ maxFragments: this.options.maxFragments,
427
435
  maxPayload: this.options.maxPayload,
428
436
  skipUTF8Validation: this.options.skipUTF8Validation
429
437
  });
@@ -201,6 +201,10 @@ class WebSocket extends EventEmitter {
201
201
  * multiple times in the same tick
202
202
  * @param {Function} [options.generateMask] The function used to generate the
203
203
  * masking key
204
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
205
+ * buffered data chunks
206
+ * @param {Number} [options.maxFragments=0] The maximum number of message
207
+ * fragments
204
208
  * @param {Number} [options.maxPayload=0] The maximum allowed message size
205
209
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
206
210
  * not to skip UTF-8 validation for text and close messages
@@ -212,6 +216,8 @@ class WebSocket extends EventEmitter {
212
216
  binaryType: this.binaryType,
213
217
  extensions: this._extensions,
214
218
  isServer: this._isServer,
219
+ maxBufferedChunks: options.maxBufferedChunks,
220
+ maxFragments: options.maxFragments,
215
221
  maxPayload: options.maxPayload,
216
222
  skipUTF8Validation: options.skipUTF8Validation
217
223
  });
@@ -640,6 +646,10 @@ module.exports = WebSocket;
640
646
  * masking key
641
647
  * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
642
648
  * handshake request
649
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
650
+ * buffered data chunks
651
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
652
+ * fragments
643
653
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
644
654
  * size
645
655
  * @param {Number} [options.maxRedirects=10] The maximum number of redirects
@@ -660,6 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
660
670
  autoPong: true,
661
671
  closeTimeout: CLOSE_TIMEOUT,
662
672
  protocolVersion: protocolVersions[1],
673
+ maxBufferedChunks: 1024 * 1024,
674
+ maxFragments: 128 * 1024,
663
675
  maxPayload: 100 * 1024 * 1024,
664
676
  skipUTF8Validation: false,
665
677
  perMessageDeflate: true,
@@ -1017,6 +1029,8 @@ function initAsClient(websocket, address, protocols, options) {
1017
1029
  websocket.setSocket(socket, head, {
1018
1030
  allowSynchronousEvents: opts.allowSynchronousEvents,
1019
1031
  generateMask: opts.generateMask,
1032
+ maxBufferedChunks: opts.maxBufferedChunks,
1033
+ maxFragments: opts.maxFragments,
1020
1034
  maxPayload: opts.maxPayload,
1021
1035
  skipUTF8Validation: opts.skipUTF8Validation
1022
1036
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ws",
3
- "version": "8.20.1",
3
+ "version": "8.21.0",
4
4
  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
5
5
  "keywords": [
6
6
  "HyBi",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shawnstack/quickforge",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "AI chat application with YOLO-mode local workspace tools. React + Vite + Tailwind CSS frontend, local Node.js storage server.",
5
5
  "keywords": [
6
6
  "ai",
@@ -15,6 +15,10 @@ import {
15
15
  parseCompactArgs,
16
16
  saveCompactBackup,
17
17
  } from './conversation-compaction.mjs'
18
+ import {
19
+ buildAutoCompactLoopMessages,
20
+ maybeAutoCompactSession,
21
+ } from './auto-compaction.mjs'
18
22
  import {
19
23
  handleInternalCommand,
20
24
  parseInternalCommandInvocation,
@@ -294,10 +298,13 @@ function addToolTimingToEvent(session, event) {
294
298
 
295
299
  function updateSessionMessages(session, messages) {
296
300
  session.agent.state.messages = messages
297
- const compacted = compactedContextMessages(messages)
298
- if (compacted.length < messages.length) {
299
- session.agent.state.messages = compacted
300
- }
301
+ }
302
+
303
+ function resetSessionCompaction(session) {
304
+ session.contextCompaction = null
305
+ session.lastAutoCompactAt = null
306
+ session.lastTransformedContextMessages = null
307
+ session.autoCompacting = false
301
308
  }
302
309
 
303
310
  function finishManualSessionRun(session, status, errorMessage) {
@@ -464,6 +471,7 @@ async function clearSession(session) {
464
471
  }
465
472
 
466
473
  updateSessionMessages(session, [])
474
+ resetSessionCompaction(session)
467
475
  session.status = 'idle'
468
476
  session.startedAt = null
469
477
  session.finishedAt = new Date().toISOString()
@@ -576,8 +584,18 @@ function compactedContextMessages(messages) {
576
584
  return index >= 0 ? messages.slice(index) : messages
577
585
  }
578
586
 
579
- function transformAgentContext(messages, commandPrompt) {
580
- return applyActiveCommandPrompt(compactedContextMessages(messages), commandPrompt)
587
+ async function transformSessionContext(session, messages, signal) {
588
+ await maybeAutoCompactSession({
589
+ session,
590
+ messages,
591
+ signal,
592
+ emitSessionEvent,
593
+ persistSession,
594
+ logger,
595
+ })
596
+ const transformedMessages = buildAutoCompactLoopMessages(session, messages)
597
+ session.lastTransformedContextMessages = transformedMessages
598
+ return applyActiveCommandPrompt(compactedContextMessages(transformedMessages), session?.activeCommandPrompt)
581
599
  }
582
600
 
583
601
  export const agentEvents = new EventEmitter()
@@ -633,6 +651,7 @@ export async function createAgent(sessionId, config = {}) {
633
651
  systemPrompt = null,
634
652
  title = 'New chat',
635
653
  createdAt = new Date().toISOString(),
654
+ contextCompaction = null,
636
655
  } = config
637
656
 
638
657
  // Resolve project context for tool calls
@@ -704,9 +723,9 @@ export async function createAgent(sessionId, config = {}) {
704
723
  sessionId,
705
724
  convertToLlm: serverConvertToLlm,
706
725
  onPayload: (payload) => {
707
- restoreReasoningContentInPayload(payload, agent.state.messages, agent.state.model)
726
+ restoreReasoningContentInPayload(payload, session?.lastTransformedContextMessages || agent.state.messages, agent.state.model)
708
727
  },
709
- transformContext: (messages) => transformAgentContext(messages, session?.activeCommandPrompt),
728
+ transformContext: (messages, signal) => transformSessionContext(session, messages, signal),
710
729
  beforeToolCall: async (context) => {
711
730
  const toolName = context.toolCall?.name
712
731
  const toolCallId = context.toolCall?.id
@@ -756,6 +775,10 @@ export async function createAgent(sessionId, config = {}) {
756
775
  titleGenerated: false,
757
776
  toolTimings: new Map(),
758
777
  getApiKey,
778
+ contextCompaction,
779
+ lastTransformedContextMessages: null,
780
+ autoCompacting: false,
781
+ lastAutoCompactAt: null,
759
782
  /** Track active SSE connections. Only one SSE stream allowed per session to prevent
760
783
  * connection-pool exhaustion when two browser tabs load the same session. */
761
784
  sseConnected: false,
@@ -816,8 +839,8 @@ export async function createAgent(sessionId, config = {}) {
816
839
  * Persist session data to storage.
817
840
  */
818
841
  async function persistSession(session) {
819
- const { sessionId, agent, scope, projectId, title, createdAt, status, startedAt, finishedAt, model, thinkingLevel, yoloMode } = session
820
- const messages = compactedContextMessages(agent.state.messages)
842
+ const { sessionId, agent, scope, projectId, title, createdAt, status, startedAt, finishedAt, model, thinkingLevel, yoloMode, contextCompaction } = session
843
+ const messages = agent.state.messages
821
844
 
822
845
  if (messages.length === 0) {
823
846
  try {
@@ -847,6 +870,7 @@ async function persistSession(session) {
847
870
  taskStatus: status,
848
871
  taskStartedAt: startedAt,
849
872
  taskFinishedAt: finishedAt,
873
+ contextCompaction: contextCompaction || undefined,
850
874
  }
851
875
 
852
876
  // Calculate usage
@@ -894,6 +918,13 @@ async function persistSession(session) {
894
918
  taskStatus: status,
895
919
  taskStartedAt: startedAt,
896
920
  taskFinishedAt: finishedAt,
921
+ contextCompaction: contextCompaction ? {
922
+ compactedAt: contextCompaction.compactedAt,
923
+ compactedUpToIndex: contextCompaction.compactedUpToIndex,
924
+ keepRecentTurns: contextCompaction.keepRecentTurns,
925
+ thresholdPercent: contextCompaction.thresholdPercent,
926
+ usageBefore: contextCompaction.usageBefore,
927
+ } : undefined,
897
928
  }
898
929
 
899
930
  // Write to storage atomically (read-modify-write within queue)
@@ -947,6 +978,7 @@ export async function rollbackSessionMessages(sessionId, rollbackMessageIndex) {
947
978
 
948
979
  const nextMessages = messages.slice(0, rollbackIndex)
949
980
  updateSessionMessages(session, nextMessages)
981
+ resetSessionCompaction(session)
950
982
  session.status = 'idle'
951
983
  session.finishedAt = new Date().toISOString()
952
984
  await persistSession(session)
@@ -971,6 +1003,7 @@ export async function replaceSessionMessages(sessionId, messages) {
971
1003
  throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before rolling back.'), { statusCode: 409 })
972
1004
  }
973
1005
  updateSessionMessages(session, Array.isArray(messages) ? messages : [])
1006
+ resetSessionCompaction(session)
974
1007
  session.status = 'idle'
975
1008
  session.finishedAt = new Date().toISOString()
976
1009
  await persistSession(session)
@@ -1160,6 +1193,7 @@ export function getSessionState(sessionId) {
1160
1193
  finishedAt: session.finishedAt,
1161
1194
  tools: session.agent.state.tools,
1162
1195
  messages: session.agent.state.messages,
1196
+ contextCompaction: session.contextCompaction,
1163
1197
  isStreaming: session.agent.state.isStreaming,
1164
1198
  errorMessage: session.agent.state.errorMessage,
1165
1199
  }
@@ -1255,6 +1289,7 @@ export async function restoreAgent(sessionId) {
1255
1289
  messages: sessionData.messages || [],
1256
1290
  title: sessionData.title || 'New chat',
1257
1291
  createdAt: sessionData.createdAt,
1292
+ contextCompaction: sessionData.contextCompaction || null,
1258
1293
  })
1259
1294
  } catch (err) {
1260
1295
  logger.error(`Failed to restore agent ${sessionId}:`, err, { sessionId })