mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 16:02:15 +00:00
Merge main and retain reviewed Electron and DOMPurify pins
This commit is contained in:
@@ -14,8 +14,8 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.14",
|
||||
"hono": "^4.12.27",
|
||||
"@hono/node-server": "^1.19.17",
|
||||
"hono": "^4.13.7",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.14",
|
||||
"hono": "^4.12.27",
|
||||
"@hono/node-server": "^1.19.17",
|
||||
"hono": "^4.13.7",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.14",
|
||||
"@hono/node-server": "^1.19.17",
|
||||
"@orca-cloud/relay-contract": "workspace:*",
|
||||
"hono": "^4.12.27",
|
||||
"hono": "^4.13.7",
|
||||
"jose": "^6.1.3",
|
||||
"pg": "^8.22.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"ws": "^8.18.3",
|
||||
"ws": "^8.21.3",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+32
-32
@@ -24,14 +24,14 @@ importers:
|
||||
apps/relay:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.19.14
|
||||
version: 1.19.14(hono@4.12.27)
|
||||
specifier: ^1.19.17
|
||||
version: 1.19.17(hono@4.13.7)
|
||||
'@orca-cloud/relay-contract':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/relay-contract
|
||||
hono:
|
||||
specifier: ^4.12.27
|
||||
version: 4.12.27
|
||||
specifier: ^4.13.7
|
||||
version: 4.13.7
|
||||
jose:
|
||||
specifier: ^6.1.3
|
||||
version: 6.2.3
|
||||
@@ -42,8 +42,8 @@ importers:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
ws:
|
||||
specifier: ^8.18.3
|
||||
version: 8.21.0
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
@@ -70,11 +70,11 @@ importers:
|
||||
apps/relay-fence-broker:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.19.14
|
||||
version: 1.19.14(hono@4.12.27)
|
||||
specifier: ^1.19.17
|
||||
version: 1.19.17(hono@4.13.7)
|
||||
hono:
|
||||
specifier: ^4.12.27
|
||||
version: 4.12.27
|
||||
specifier: ^4.13.7
|
||||
version: 4.13.7
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
@@ -95,11 +95,11 @@ importers:
|
||||
apps/relay-ops:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.19.14
|
||||
version: 1.19.14(hono@4.12.27)
|
||||
specifier: ^1.19.17
|
||||
version: 1.19.17(hono@4.13.7)
|
||||
hono:
|
||||
specifier: ^4.12.27
|
||||
version: 4.12.27
|
||||
specifier: ^4.13.7
|
||||
version: 4.13.7
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
@@ -300,8 +300,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@hono/node-server@1.19.14':
|
||||
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
|
||||
'@hono/node-server@1.19.17':
|
||||
resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
@@ -507,8 +507,8 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
hono@4.12.27:
|
||||
resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==}
|
||||
hono@4.13.7:
|
||||
resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
jose@6.2.3:
|
||||
@@ -587,8 +587,8 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
nanoid@3.3.13:
|
||||
resolution: {integrity: sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==}
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -640,8 +640,8 @@ packages:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
postcss@8.5.28:
|
||||
resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postgres-array@2.0.0:
|
||||
@@ -805,8 +805,8 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
ws@8.21.0:
|
||||
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
|
||||
ws@8.21.3:
|
||||
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -920,9 +920,9 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.28.1':
|
||||
optional: true
|
||||
|
||||
'@hono/node-server@1.19.14(hono@4.12.27)':
|
||||
'@hono/node-server@1.19.17(hono@4.13.7)':
|
||||
dependencies:
|
||||
hono: 4.12.27
|
||||
hono: 4.13.7
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
@@ -1109,7 +1109,7 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hono@4.12.27: {}
|
||||
hono@4.13.7: {}
|
||||
|
||||
jose@6.2.3: {}
|
||||
|
||||
@@ -1166,7 +1166,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
nanoid@3.3.13: {}
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
obug@2.1.3: {}
|
||||
|
||||
@@ -1211,9 +1211,9 @@ snapshots:
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
postcss@8.5.15:
|
||||
postcss@8.5.28:
|
||||
dependencies:
|
||||
nanoid: 3.3.13
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -1288,7 +1288,7 @@ snapshots:
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.15
|
||||
postcss: 8.5.28
|
||||
rolldown: 1.0.3
|
||||
tinyglobby: 0.2.17
|
||||
optionalDependencies:
|
||||
@@ -1329,7 +1329,7 @@ snapshots:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
ws@8.21.0: {}
|
||||
ws@8.21.3: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
|
||||
@@ -9,8 +9,3 @@ inline src/main/ssh/ssh-relay-deploy.ts
|
||||
inline src/main/ssh/ssh-relay-session.ts
|
||||
inline src/relay/pty-handler.ts
|
||||
inline src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts
|
||||
mobile-config app/h/*/files/*.tsx
|
||||
mobile-config app/h/*/source-control/*.tsx
|
||||
mobile-config app/index.tsx
|
||||
mobile-config scripts/mock-server.ts
|
||||
mobile-config src/transport/rpc-client.ts
|
||||
|
||||
Generated
+8
-8
@@ -2321,11 +2321,11 @@ packages:
|
||||
bindings@1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
|
||||
brace-expansion@1.1.16:
|
||||
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
|
||||
brace-expansion@1.1.18:
|
||||
resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
|
||||
|
||||
brace-expansion@5.0.8:
|
||||
resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==}
|
||||
brace-expansion@5.0.9:
|
||||
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
@@ -6854,12 +6854,12 @@ snapshots:
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
|
||||
brace-expansion@1.1.16:
|
||||
brace-expansion@1.1.18:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@5.0.8:
|
||||
brace-expansion@5.0.9:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
@@ -8764,11 +8764,11 @@ snapshots:
|
||||
|
||||
minimatch@10.2.6:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.8
|
||||
brace-expansion: 5.0.9
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.16
|
||||
brace-expansion: 1.1.18
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
|
||||
@@ -20,36 +20,6 @@
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 379, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["app/h/*/source-control/*.tsx"],
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 2152, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["app/index.tsx"],
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 1422, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/transport/rpc-client.ts"],
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 1074, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["scripts/mock-server.ts"],
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 407, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["app/h/*/files/*.tsx"],
|
||||
"rules": {
|
||||
"max-lines": ["error", { "max": 402, "skipBlankLines": true, "skipComments": true }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+9
-9
@@ -3075,12 +3075,12 @@ packages:
|
||||
'@vitest/utils@4.1.11':
|
||||
resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
|
||||
|
||||
'@xmldom/xmldom@0.8.13':
|
||||
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
|
||||
'@xmldom/xmldom@0.8.15':
|
||||
resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@xmldom/xmldom@0.9.10':
|
||||
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
||||
'@xmldom/xmldom@0.9.12':
|
||||
resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==}
|
||||
engines: {node: '>=14.6'}
|
||||
|
||||
'@xterm/addon-unicode11@0.10.0-beta.300':
|
||||
@@ -9072,13 +9072,13 @@ snapshots:
|
||||
|
||||
'@expo/plist@0.5.3':
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.13
|
||||
'@xmldom/xmldom': 0.8.15
|
||||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
'@expo/plist@0.5.4':
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.13
|
||||
'@xmldom/xmldom': 0.8.15
|
||||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
@@ -10574,9 +10574,9 @@ snapshots:
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@xmldom/xmldom@0.8.13': {}
|
||||
'@xmldom/xmldom@0.8.15': {}
|
||||
|
||||
'@xmldom/xmldom@0.9.10': {}
|
||||
'@xmldom/xmldom@0.9.12': {}
|
||||
|
||||
'@xterm/addon-unicode11@0.10.0-beta.300(@xterm/xterm@6.1.0-beta.303)':
|
||||
dependencies:
|
||||
@@ -14451,7 +14451,7 @@ snapshots:
|
||||
|
||||
plist@3.1.1:
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.9.10
|
||||
'@xmldom/xmldom': 0.9.12
|
||||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
|
||||
+2
-2
@@ -176,7 +176,7 @@
|
||||
"proper-lockfile": "4.1.2",
|
||||
"psl": "1.15.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react-i18next": "^17.0.8",
|
||||
"react-i18next": "17.0.13",
|
||||
"serve-sim": "^0.1.40",
|
||||
"sherpa-onnx": "1.12.37",
|
||||
"ssh2": "^1.17.0",
|
||||
@@ -235,7 +235,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"dompurify": "^3.4.13",
|
||||
"dompurify": "3.4.14",
|
||||
"electron": "43.6.0",
|
||||
"electron-builder": "^26.15.3",
|
||||
"electron-builder-squirrel-windows": "^26.15.3",
|
||||
|
||||
Generated
+75
-81
@@ -106,7 +106,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
monaco-editor>dompurify: 3.4.13
|
||||
monaco-editor>dompurify: 3.4.14
|
||||
|
||||
patchedDependencies:
|
||||
'@vscode/windows-process-tree@0.8.0': e66202cc623996d02040c93449eb9ae353fddadf426cb53202a59ee710ee6fe7
|
||||
@@ -174,8 +174,8 @@ importers:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
react-i18next:
|
||||
specifier: ^17.0.8
|
||||
version: 17.0.8(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
|
||||
specifier: 17.0.13
|
||||
version: 17.0.13(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
|
||||
serve-sim:
|
||||
specifier: ^0.1.40
|
||||
version: 0.1.40(typescript@7.0.2)
|
||||
@@ -346,8 +346,8 @@ importers:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
dompurify:
|
||||
specifier: ^3.4.13
|
||||
version: 3.4.13
|
||||
specifier: 3.4.14
|
||||
version: 3.4.14
|
||||
electron:
|
||||
specifier: 43.6.0
|
||||
version: 43.6.0(supports-color@7.2.0)
|
||||
@@ -3513,8 +3513,8 @@ packages:
|
||||
'@vscode/windows-process-tree@0.8.0':
|
||||
resolution: {integrity: sha512-TI+h2GRwX+igD/YYJMQQVAFcsCNSg7Te2yYxQpKMzwto5RsJ8d2KKgOeur/p/6sAOQwZvopiTDOClyTHEn9MhQ==}
|
||||
|
||||
'@xmldom/xmldom@0.8.13':
|
||||
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
|
||||
'@xmldom/xmldom@0.8.15':
|
||||
resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@xterm/addon-fit@0.12.0-beta.300':
|
||||
@@ -3677,8 +3677,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.27:
|
||||
resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==}
|
||||
baseline-browser-mapping@2.11.21:
|
||||
resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -3715,8 +3715,8 @@ packages:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
browserslist@4.28.2:
|
||||
resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
|
||||
browserslist@4.28.9:
|
||||
resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
@@ -3779,8 +3779,8 @@ packages:
|
||||
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001791:
|
||||
resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==}
|
||||
caniuse-lite@1.0.30001810:
|
||||
resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
@@ -4261,8 +4261,8 @@ packages:
|
||||
dom-accessibility-api@0.6.3:
|
||||
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
||||
|
||||
dompurify@3.4.13:
|
||||
resolution: {integrity: sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==}
|
||||
dompurify@3.4.14:
|
||||
resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==}
|
||||
|
||||
dotenv-expand@11.0.7:
|
||||
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
|
||||
@@ -4306,8 +4306,8 @@ packages:
|
||||
electron-publish@26.15.3:
|
||||
resolution: {integrity: sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==}
|
||||
|
||||
electron-to-chromium@1.5.351:
|
||||
resolution: {integrity: sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==}
|
||||
electron-to-chromium@1.5.422:
|
||||
resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==}
|
||||
|
||||
electron-updater@6.8.9:
|
||||
resolution: {integrity: sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==}
|
||||
@@ -4525,8 +4525,8 @@ packages:
|
||||
fast-string-width@3.0.2:
|
||||
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
|
||||
|
||||
fast-uri@3.1.5:
|
||||
resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==}
|
||||
fast-uri@3.1.7:
|
||||
resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==}
|
||||
|
||||
fast-wrap-ansi@0.2.2:
|
||||
resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
|
||||
@@ -4794,8 +4794,8 @@ packages:
|
||||
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
html-parse-stringify@4.0.1:
|
||||
resolution: {integrity: sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==}
|
||||
|
||||
html-to-image@1.11.13:
|
||||
resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==}
|
||||
@@ -5563,8 +5563,8 @@ packages:
|
||||
nan@2.26.2:
|
||||
resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==}
|
||||
|
||||
nanoid@3.3.17:
|
||||
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -5600,8 +5600,9 @@ packages:
|
||||
node-pty@1.1.0:
|
||||
resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==}
|
||||
|
||||
node-releases@2.0.38:
|
||||
resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==}
|
||||
node-releases@2.0.54:
|
||||
resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
nopt@9.0.0:
|
||||
resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
|
||||
@@ -5856,8 +5857,8 @@ packages:
|
||||
points-on-path@0.2.1:
|
||||
resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
postcss-selector-parser@7.1.6:
|
||||
resolution: {integrity: sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postcss@8.5.25:
|
||||
@@ -5977,8 +5978,8 @@ packages:
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
qs@6.15.2:
|
||||
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
||||
qs@6.16.0:
|
||||
resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
@@ -6029,14 +6030,14 @@ packages:
|
||||
react:
|
||||
optional: true
|
||||
|
||||
react-i18next@17.0.8:
|
||||
resolution: {integrity: sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==}
|
||||
react-i18next@17.0.13:
|
||||
resolution: {integrity: sha512-Cc1PscmblIHA1kljTqDwrcVMI21ydgmUzw0UAeQBe7pAOgfuRLfzXze4EUBQoeDiICzFIXXhHFoZxuetNg5D0Q==}
|
||||
peerDependencies:
|
||||
i18next: '>= 26.2.0'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
typescript: ^5 || ^6
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
@@ -6392,8 +6393,8 @@ packages:
|
||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel@1.1.0:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
side-channel@1.1.1:
|
||||
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
siginfo@2.0.0:
|
||||
@@ -6755,8 +6756,8 @@ packages:
|
||||
unzipper@0.12.5:
|
||||
resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==}
|
||||
|
||||
update-browserslist-db@1.2.3:
|
||||
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
|
||||
update-browserslist-db@1.3.2:
|
||||
resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
@@ -6854,10 +6855,6 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
vscode-oniguruma@2.0.1:
|
||||
resolution: {integrity: sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==}
|
||||
|
||||
@@ -7119,7 +7116,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.29.7
|
||||
'@babel/helper-validator-option': 7.29.7
|
||||
browserslist: 4.28.2
|
||||
browserslist: 4.28.9
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
@@ -7739,7 +7736,7 @@ snapshots:
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.0.8
|
||||
express: 5.2.1(supports-color@7.2.0)
|
||||
express-rate-limit: 8.5.2(express@5.2.1(supports-color@7.2.0))
|
||||
express-rate-limit: 8.5.2(express@5.2.1)
|
||||
hono: 4.13.0
|
||||
jose: 6.2.3
|
||||
json-schema-typed: 8.0.2
|
||||
@@ -7761,7 +7758,7 @@ snapshots:
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.0.8
|
||||
express: 5.2.1(supports-color@7.2.0)
|
||||
express-rate-limit: 8.5.2(express@5.2.1(supports-color@7.2.0))
|
||||
express-rate-limit: 8.5.2(express@5.2.1)
|
||||
hono: 4.13.0
|
||||
jose: 6.2.3
|
||||
json-schema-typed: 8.0.2
|
||||
@@ -9826,7 +9823,7 @@ snapshots:
|
||||
node-addon-api: 7.1.0
|
||||
optional: true
|
||||
|
||||
'@xmldom/xmldom@0.8.13': {}
|
||||
'@xmldom/xmldom@0.8.15': {}
|
||||
|
||||
'@xterm/addon-fit@0.12.0-beta.300(@xterm/xterm@6.1.0-beta.303(patch_hash=98756bcedc402bcdb7c6ab7b015d2e59cd18e97b03a2c06a27e95bb3ba429d9d))':
|
||||
dependencies:
|
||||
@@ -9882,7 +9879,7 @@ snapshots:
|
||||
ajv@8.20.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.1.5
|
||||
fast-uri: 3.1.7
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
@@ -9994,7 +9991,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.27: {}
|
||||
baseline-browser-mapping@2.11.21: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
dependencies:
|
||||
@@ -10014,7 +10011,7 @@ snapshots:
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.15.2
|
||||
qs: 6.16.0
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
@@ -10040,13 +10037,13 @@ snapshots:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
browserslist@4.28.2:
|
||||
browserslist@4.28.9:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.27
|
||||
caniuse-lite: 1.0.30001791
|
||||
electron-to-chromium: 1.5.351
|
||||
node-releases: 2.0.38
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
baseline-browser-mapping: 2.11.21
|
||||
caniuse-lite: 1.0.30001810
|
||||
electron-to-chromium: 1.5.422
|
||||
node-releases: 2.0.54
|
||||
update-browserslist-db: 1.3.2(browserslist@4.28.9)
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
@@ -10119,7 +10116,7 @@ snapshots:
|
||||
|
||||
camelcase@5.3.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001791: {}
|
||||
caniuse-lite@1.0.30001810: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
@@ -10573,7 +10570,7 @@ snapshots:
|
||||
|
||||
dom-accessibility-api@0.6.3: {}
|
||||
|
||||
dompurify@3.4.13:
|
||||
dompurify@3.4.14:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
@@ -10647,7 +10644,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron-to-chromium@1.5.351: {}
|
||||
electron-to-chromium@1.5.422: {}
|
||||
|
||||
electron-updater@6.8.9(supports-color@7.2.0):
|
||||
dependencies:
|
||||
@@ -10862,7 +10859,7 @@ snapshots:
|
||||
|
||||
exponential-backoff@3.1.3: {}
|
||||
|
||||
express-rate-limit@8.5.2(express@5.2.1(supports-color@7.2.0)):
|
||||
express-rate-limit@8.5.2(express@5.2.1):
|
||||
dependencies:
|
||||
express: 5.2.1(supports-color@7.2.0)
|
||||
ip-address: 10.4.0
|
||||
@@ -10889,7 +10886,7 @@ snapshots:
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.2
|
||||
qs: 6.16.0
|
||||
range-parser: 1.2.1
|
||||
router: 2.2.0(supports-color@7.2.0)
|
||||
send: 1.2.1(supports-color@7.2.0)
|
||||
@@ -10922,7 +10919,7 @@ snapshots:
|
||||
dependencies:
|
||||
fast-string-truncated-width: 3.0.3
|
||||
|
||||
fast-uri@3.1.5: {}
|
||||
fast-uri@3.1.7: {}
|
||||
|
||||
fast-wrap-ansi@0.2.2:
|
||||
dependencies:
|
||||
@@ -11299,9 +11296,7 @@ snapshots:
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
html-parse-stringify@4.0.1: {}
|
||||
|
||||
html-to-image@1.11.13: {}
|
||||
|
||||
@@ -11361,7 +11356,7 @@ snapshots:
|
||||
minimatch: 10.2.5
|
||||
ora: 9.4.0
|
||||
react: 19.2.8
|
||||
react-i18next: 17.0.8(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
|
||||
react-i18next: 17.0.13(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
|
||||
yaml: 2.9.0
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
@@ -11929,7 +11924,7 @@ snapshots:
|
||||
d3-sankey: 0.12.3
|
||||
dagre-d3-es: 7.0.14
|
||||
dayjs: 1.11.23
|
||||
dompurify: 3.4.13
|
||||
dompurify: 3.4.14
|
||||
es-toolkit: 1.46.1
|
||||
fastdom: 1.0.12
|
||||
katex: 0.16.47
|
||||
@@ -12214,7 +12209,7 @@ snapshots:
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
dependencies:
|
||||
dompurify: 3.4.13
|
||||
dompurify: 3.4.14
|
||||
marked: 14.0.0
|
||||
|
||||
ms@2.1.3: {}
|
||||
@@ -12250,7 +12245,7 @@ snapshots:
|
||||
nan@2.26.2:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.17: {}
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
@@ -12289,7 +12284,7 @@ snapshots:
|
||||
dependencies:
|
||||
node-addon-api: 7.1.1
|
||||
|
||||
node-releases@2.0.38: {}
|
||||
node-releases@2.0.54: {}
|
||||
|
||||
nopt@9.0.0:
|
||||
dependencies:
|
||||
@@ -12575,7 +12570,7 @@ snapshots:
|
||||
|
||||
plist@3.1.0:
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.13
|
||||
'@xmldom/xmldom': 0.8.15
|
||||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
@@ -12590,14 +12585,14 @@ snapshots:
|
||||
path-data-parser: 0.1.0
|
||||
points-on-curve: 0.2.0
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
postcss-selector-parser@7.1.6:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
postcss@8.5.25:
|
||||
dependencies:
|
||||
nanoid: 3.3.17
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -12743,9 +12738,10 @@ snapshots:
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
qs@6.15.2:
|
||||
qs@6.16.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
es-define-property: 1.0.1
|
||||
side-channel: 1.1.1
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
@@ -12840,10 +12836,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
react: 19.2.8
|
||||
|
||||
react-i18next@17.0.8(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2):
|
||||
react-i18next@17.0.13(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
html-parse-stringify: 3.0.1
|
||||
html-parse-stringify: 4.0.1
|
||||
i18next: 26.3.1(typescript@7.0.2)
|
||||
react: 19.2.8
|
||||
use-sync-external-store: 1.6.0(react@19.2.8)
|
||||
@@ -13222,7 +13218,7 @@ snapshots:
|
||||
'@dotenvx/dotenvx': 1.66.0
|
||||
'@modelcontextprotocol/sdk': 1.30.0(zod@3.25.76)
|
||||
'@types/validate-npm-package-name': 4.0.2
|
||||
browserslist: 4.28.2
|
||||
browserslist: 4.28.9
|
||||
commander: 14.0.3
|
||||
cosmiconfig: 9.0.1(typescript@7.0.2)
|
||||
dedent: 1.7.2
|
||||
@@ -13236,7 +13232,7 @@ snapshots:
|
||||
open: 11.0.0
|
||||
ora: 8.2.0
|
||||
postcss: 8.5.25
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-selector-parser: 7.1.6
|
||||
prompts: 2.4.2
|
||||
recast: 0.23.11
|
||||
stringify-object: 5.0.0
|
||||
@@ -13296,7 +13292,7 @@ snapshots:
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
|
||||
side-channel@1.1.0:
|
||||
side-channel@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
@@ -13668,9 +13664,9 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
node-int64: 0.4.0
|
||||
|
||||
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
||||
update-browserslist-db@1.3.2(browserslist@4.28.9):
|
||||
dependencies:
|
||||
browserslist: 4.28.2
|
||||
browserslist: 4.28.9
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
@@ -13747,8 +13743,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
vscode-oniguruma@2.0.1: {}
|
||||
|
||||
vscode-textmate@9.3.2: {}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ allowBuilds:
|
||||
windows-native-registry: false
|
||||
|
||||
overrides:
|
||||
monaco-editor>dompurify: 3.4.13
|
||||
monaco-editor>dompurify: 3.4.14
|
||||
|
||||
patchedDependencies:
|
||||
node-pty@1.1.0: config/patches/node-pty@1.1.0.patch
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { AgentJournalMessageItem } from '../../shared/agent-session-journal-types'
|
||||
import type { NativeChatBlock } from '../../shared/native-chat-types'
|
||||
import { buildImageDataUri } from '../../shared/image-data-uri'
|
||||
import { DEFAULT_JOURNAL_PAYLOAD_LIMITS } from '../native-chat/agent-session-journal/journal-payload-bounds'
|
||||
import { readString } from './codex-item-field-readers'
|
||||
import type { CodexThreadItem } from './codex-thread-item-identity'
|
||||
|
||||
// Leave room for operation text and the journal envelope beside inline image bytes.
|
||||
const MAX_IMAGE_REFERENCE_BYTES = DEFAULT_JOURNAL_PAYLOAD_LIMITS.inlineHeadBytes / 2
|
||||
|
||||
function imagePath(item: CodexThreadItem, key: string): string | null {
|
||||
const value = readString(item, key)
|
||||
return value?.trim() && Buffer.byteLength(value, 'utf8') <= MAX_IMAGE_REFERENCE_BYTES
|
||||
? value
|
||||
: null
|
||||
}
|
||||
|
||||
function generatedImageUrl(item: CodexThreadItem): string | null {
|
||||
const result = readString(item, 'result')
|
||||
if (!result || result.length > MAX_IMAGE_REFERENCE_BYTES) {
|
||||
return null
|
||||
}
|
||||
const match = /^data:(image\/(?:png|jpeg|webp));base64,(.*)$/s.exec(result)
|
||||
const base64 = (match?.[2] ?? result).replace(/\s/g, '')
|
||||
if (!base64 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(base64)) {
|
||||
return null
|
||||
}
|
||||
const url = buildImageDataUri(match?.[1] ?? 'image/png', base64)
|
||||
return url && url.length <= MAX_IMAGE_REFERENCE_BYTES ? url : null
|
||||
}
|
||||
|
||||
export function codexImageItemBody(item: CodexThreadItem): AgentJournalMessageItem {
|
||||
let image: Extract<NativeChatBlock, { type: 'image-ref' }> | null = null
|
||||
let text: string
|
||||
if (item.type === 'imageView') {
|
||||
const path = imagePath(item, 'path')
|
||||
text = path ? 'Viewed image' : 'Image view: preview unavailable'
|
||||
image = path ? { type: 'image-ref', path } : null
|
||||
} else {
|
||||
const status = readString(item, 'status')
|
||||
if (item.failure || status === 'failed') {
|
||||
text = 'Image generation failed'
|
||||
} else if (status !== 'completed') {
|
||||
text = status === 'inProgress' ? 'Generating image…' : 'Image generation: preview unavailable'
|
||||
} else {
|
||||
const path = imagePath(item, 'savedPath')
|
||||
const url = path ? null : generatedImageUrl(item)
|
||||
image = path
|
||||
? { type: 'image-ref', path }
|
||||
: url
|
||||
? { type: 'image-ref', url, alt: 'Generated image' }
|
||||
: null
|
||||
text = image ? 'Generated image' : 'Image generated: preview unavailable'
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [{ type: 'text', text }, ...(image ? [image] : [])]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { codexItemBody, codexStreamingJournalItem } from './codex-structured-item-translation'
|
||||
import { AgentJournalItemBodySchema } from '../../shared/agent-session-journal-schemas'
|
||||
import { projectStructuredItemsToNativeChat } from '../../shared/structured-agent-session-projection'
|
||||
|
||||
describe('plan document translation', () => {
|
||||
it('marks both complete documents and streaming snapshots', () => {
|
||||
const item = { id: 'plan-1', type: 'plan', text: '# Plan\n\nReadable prose.' }
|
||||
expect(codexItemBody(item)).toEqual({
|
||||
kind: 'status',
|
||||
text: item.text,
|
||||
presentation: 'plan-document'
|
||||
})
|
||||
expect(codexStreamingJournalItem(item, '# Plan\n\nPartial').body).toEqual({
|
||||
kind: 'status',
|
||||
text: '# Plan\n\nPartial',
|
||||
presentation: 'plan-document'
|
||||
})
|
||||
expect(codexItemBody({ id: 'plan-1', type: 'plan' })).toBeNull()
|
||||
})
|
||||
it('preserves the full existing reasoning body byte for byte', () => {
|
||||
expect(
|
||||
codexItemBody({ id: 'r', type: 'reasoning', summary: ['Thinking through the problem.'] })
|
||||
).toEqual({
|
||||
kind: 'status',
|
||||
text: 'Thinking through the problem.'
|
||||
})
|
||||
expect(codexStreamingJournalItem({ id: 'r', type: 'reasoning' }, 'Thinking…')).toEqual({
|
||||
body: { kind: 'status', text: 'Thinking…' },
|
||||
handled: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('image item translation', () => {
|
||||
it.each(['/remote/work/image.png', 'C:\\work\\image.png'])(
|
||||
'preserves the execution-host path %s and old-reader operation text',
|
||||
(path) => {
|
||||
expect(codexItemBody({ id: 'view', type: 'imageView', path })).toEqual({
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [
|
||||
{ type: 'text', text: 'Viewed image' },
|
||||
{ type: 'image-ref', path }
|
||||
]
|
||||
})
|
||||
}
|
||||
)
|
||||
it('prefers saved paths over unbounded inline image data', () => {
|
||||
const body = codexItemBody({
|
||||
id: 'gen',
|
||||
type: 'imageGeneration',
|
||||
status: 'completed',
|
||||
savedPath: '/remote/image.png',
|
||||
result: 'A'.repeat(100_000)
|
||||
})
|
||||
expect(body).toEqual({
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [
|
||||
{ type: 'text', text: 'Generated image' },
|
||||
{ type: 'image-ref', path: '/remote/image.png' }
|
||||
]
|
||||
})
|
||||
expect(AgentJournalItemBodySchema.safeParse(body).success).toBe(true)
|
||||
const [message] = projectStructuredItemsToNativeChat([
|
||||
{ itemId: 'gen', revision: 1, sequence: 1, observedAt: 1, body: body! }
|
||||
])
|
||||
expect(message?.blocks).toEqual(body?.kind === 'message' ? body.blocks : [])
|
||||
})
|
||||
it.each(['AAAA', 'data:image/png;base64,AAAA'])(
|
||||
'maps bounded image data onto a meaningful existing image-ref: %s',
|
||||
(result) => {
|
||||
expect(
|
||||
codexItemBody({ id: 'gen', type: 'imageGeneration', status: 'completed', result })
|
||||
).toMatchObject({
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [
|
||||
{ type: 'text', text: 'Generated image' },
|
||||
{ type: 'image-ref', url: 'data:image/png;base64,AAAA', alt: 'Generated image' }
|
||||
]
|
||||
})
|
||||
}
|
||||
)
|
||||
it.each(['A'.repeat(20_000), 'not valid image bytes', 'data:text/html;base64,AAAA', ''])(
|
||||
'keeps unavailable results bounded and readable',
|
||||
(result) => {
|
||||
const body = codexItemBody({
|
||||
id: 'gen',
|
||||
type: 'imageGeneration',
|
||||
status: 'completed',
|
||||
result
|
||||
})
|
||||
expect(body).toEqual({
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [{ type: 'text', text: 'Image generated: preview unavailable' }]
|
||||
})
|
||||
}
|
||||
)
|
||||
it.each([
|
||||
[{ status: 'inProgress' }, 'Generating image…'],
|
||||
[{ status: 'failed' }, 'Image generation failed'],
|
||||
[{ status: 'completed', failure: { type: 'usageLimitExceeded' } }, 'Image generation failed'],
|
||||
[{ status: 'future-state' }, 'Image generation: preview unavailable']
|
||||
])('does not invent completed output for %j', (fields, text) => {
|
||||
expect(codexItemBody({ id: 'gen', type: 'imageGeneration', ...fields })).toEqual({
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [{ type: 'text', text }]
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
DEFAULT_JOURNAL_PAYLOAD_LIMITS
|
||||
} from '../native-chat/agent-session-journal/journal-payload-bounds'
|
||||
import { unhandledProviderFrameJournalItem } from '../native-chat/agent-session-wire/unhandled-provider-frame'
|
||||
import { codexImageItemBody } from './codex-image-item-translation'
|
||||
import { commandActionFacts } from './codex-command-action-class'
|
||||
import {
|
||||
readFirstString,
|
||||
@@ -250,6 +251,23 @@ export function codexJournalItem(item: CodexThreadItem): CodexJournalItem {
|
||||
if (item.type === 'webSearch') {
|
||||
return webSearchItem(item)
|
||||
}
|
||||
if (item.type === 'imageView' || item.type === 'imageGeneration') {
|
||||
return { body: codexImageItemBody(item), handled: true }
|
||||
}
|
||||
if (item.type === 'plan') {
|
||||
const text = readTextContent(item, 'text')
|
||||
return {
|
||||
body:
|
||||
text === null
|
||||
? null
|
||||
: {
|
||||
kind: 'status',
|
||||
text: boundInlineText(text, DEFAULT_JOURNAL_PAYLOAD_LIMITS).text,
|
||||
presentation: 'plan-document'
|
||||
},
|
||||
handled: true
|
||||
}
|
||||
}
|
||||
if (item.type === 'reasoning' || item.type === 'plan') {
|
||||
const text =
|
||||
readTextContent(item, 'text') ??
|
||||
@@ -298,6 +316,16 @@ export function codexStreamingJournalItem(item: CodexThreadItem, text: string):
|
||||
handled: true
|
||||
}
|
||||
}
|
||||
if (item.type === 'plan') {
|
||||
return {
|
||||
body: {
|
||||
kind: 'status',
|
||||
text: boundInlineText(text, DEFAULT_JOURNAL_PAYLOAD_LIMITS).text,
|
||||
presentation: 'plan-document'
|
||||
},
|
||||
handled: true
|
||||
}
|
||||
}
|
||||
const bounded = boundInlineText(text, DEFAULT_JOURNAL_PAYLOAD_LIMITS)
|
||||
return { body: { kind: 'status', text: bounded.text }, handled: true }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { agentJournalItemKey } from '../../shared/agent-session-journal-item-key'
|
||||
import type { AgentJournalItemBody } from '../../shared/agent-session-journal-types'
|
||||
import { projectStructuredItemsToNativeChat } from '../../shared/structured-agent-session-projection'
|
||||
import type { StructuredAgentSessionEventSink } from '../native-chat/agent-session-wire/structured-agent-session-event-sink'
|
||||
import {
|
||||
createCodexJournalTranslator,
|
||||
MAX_CODEX_GENERIC_TURN_BUCKETS
|
||||
} from './codex-structured-journal-translation'
|
||||
|
||||
function setup() {
|
||||
const rows = new Map<string, AgentJournalItemBody>()
|
||||
let writes = 0
|
||||
const sink: StructuredAgentSessionEventSink = {
|
||||
appendItem: (identity, body) => {
|
||||
writes += 1
|
||||
rows.set(agentJournalItemKey(identity), body)
|
||||
},
|
||||
appendTombstone: (identity) => rows.delete(agentJournalItemKey(identity)),
|
||||
publish: () => {}
|
||||
}
|
||||
const translator = createCodexJournalTranslator({ sink })
|
||||
const send = (method: string, turnId = 'turn', threadId = 'thread') =>
|
||||
translator.handle({
|
||||
type: 'notification',
|
||||
sessionId: 'session',
|
||||
threadId,
|
||||
method,
|
||||
params: { turnId, item: { id: `compact-${turnId}`, type: 'contextCompaction' } }
|
||||
})
|
||||
return { rows, sink, translator, send, writes: () => writes }
|
||||
}
|
||||
|
||||
describe('compaction provider generation compatibility', () => {
|
||||
it.each([
|
||||
['item/completed'],
|
||||
['thread/compacted'],
|
||||
['item/completed', 'thread/compacted'],
|
||||
['thread/compacted', 'item/completed']
|
||||
])('projects one readable divider for %j', (...methods) => {
|
||||
const { rows, translator, send } = setup()
|
||||
expect(send('item/started')).toEqual({ accepted: true })
|
||||
expect(rows.size).toBe(0)
|
||||
for (const method of methods) {
|
||||
expect(send(method)).toEqual({ accepted: true })
|
||||
}
|
||||
const messages = projectStructuredItemsToNativeChat(
|
||||
[...rows].map(([itemId, body], index) => ({
|
||||
itemId,
|
||||
body,
|
||||
sequence: index + 1,
|
||||
revision: 1,
|
||||
observedAt: 1
|
||||
}))
|
||||
)
|
||||
expect(messages).toHaveLength(1)
|
||||
expect(messages[0]?.blocks).toEqual([
|
||||
{ type: 'text', text: 'Context compacted', presentation: 'compaction' }
|
||||
])
|
||||
translator.dispose()
|
||||
})
|
||||
|
||||
it('keeps the canonical item authoritative and scopes deduplication by thread and turn', () => {
|
||||
const { rows, translator, send, writes } = setup()
|
||||
send('thread/compacted')
|
||||
send('item/completed')
|
||||
expect(writes()).toBe(2)
|
||||
send('thread/compacted')
|
||||
send('item/completed')
|
||||
expect(writes()).toBe(2)
|
||||
send('item/completed', 'turn-2')
|
||||
send('item/completed', 'turn', 'other-thread')
|
||||
expect(rows.size).toBe(3)
|
||||
translator.dispose()
|
||||
})
|
||||
|
||||
it.each(['append', 'publish'])('does not suppress the retry after rejected %s', (stage) => {
|
||||
const { rows, sink, translator, send } = setup()
|
||||
let reject = true
|
||||
let publishes = 0
|
||||
sink.tryAppendItem = (identity, body) => {
|
||||
if (stage === 'append' && reject) {
|
||||
return { accepted: false, reason: 'backpressure' }
|
||||
}
|
||||
sink.appendItem(identity, body)
|
||||
return { accepted: true }
|
||||
}
|
||||
sink.tryPublish = () => {
|
||||
publishes += 1
|
||||
return stage === 'publish' && reject
|
||||
? { accepted: false, reason: 'backpressure' }
|
||||
: { accepted: true }
|
||||
}
|
||||
expect(send('item/completed')).toEqual({ accepted: false, reason: 'backpressure' })
|
||||
reject = false
|
||||
expect(send('item/completed')).toEqual({ accepted: true })
|
||||
expect(rows.size).toBe(1)
|
||||
expect(publishes).toBe(stage === 'publish' ? 2 : 1)
|
||||
translator.dispose()
|
||||
})
|
||||
|
||||
it('bounds retained turns and keeps the same journal identity after eviction', () => {
|
||||
const { rows, translator, send, writes } = setup()
|
||||
send('item/completed', 'oldest')
|
||||
for (let index = 0; index < MAX_CODEX_GENERIC_TURN_BUCKETS; index += 1) {
|
||||
send('item/completed', `turn-${index}`)
|
||||
}
|
||||
const before = writes()
|
||||
send('thread/compacted', 'oldest')
|
||||
expect(writes()).toBe(before + 1)
|
||||
expect(rows.size).toBe(MAX_CODEX_GENERIC_TURN_BUCKETS + 1)
|
||||
translator.dispose()
|
||||
})
|
||||
|
||||
it('restores canonical compaction history over a previously journaled legacy fallback', () => {
|
||||
const { rows, sink, translator, send } = setup()
|
||||
send('thread/compacted')
|
||||
translator.dispose()
|
||||
const restored = createCodexJournalTranslator({ sink })
|
||||
expect(
|
||||
restored.restoreThread('thread', {
|
||||
turns: [{ id: 'turn', items: [{ id: 'renumbered', type: 'contextCompaction' }] }]
|
||||
})
|
||||
).toEqual({ accepted: true })
|
||||
expect(rows.size).toBe(1)
|
||||
expect([...rows.values()][0]).toEqual({
|
||||
kind: 'status',
|
||||
text: 'Context compacted',
|
||||
presentation: 'compaction'
|
||||
})
|
||||
restored.dispose()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { isCodexCompactionComplete } from '../native-chat/agent-session-wire/structured-session-compaction'
|
||||
import type { StructuredAgentSessionEventSink } from '../native-chat/agent-session-wire/structured-agent-session-event-sink'
|
||||
import {
|
||||
CODEX_JOURNAL_ADMITTED,
|
||||
type CodexJournalTranslationAdmission
|
||||
} from './codex-structured-journal-contracts'
|
||||
import { MAX_CODEX_GENERIC_TURN_BUCKETS } from './codex-structured-journal-limits'
|
||||
import { appendCodexLifecycleItem, publishCodexLifecycle } from './codex-structured-journal-sink'
|
||||
import { readCodexTurnId } from './codex-structured-thread-facts'
|
||||
|
||||
export class CodexJournalCompactions {
|
||||
private readonly turns = new Map<string, 'item' | 'legacy'>()
|
||||
|
||||
constructor(
|
||||
private readonly sink: StructuredAgentSessionEventSink,
|
||||
private readonly activeTurn: (threadId: string) => string | null
|
||||
) {}
|
||||
|
||||
handle(event: {
|
||||
threadId: string
|
||||
method: string
|
||||
params: unknown
|
||||
}): CodexJournalTranslationAdmission | null {
|
||||
if (!isCodexCompactionComplete(event.method, event.params)) {
|
||||
return null
|
||||
}
|
||||
const turnId = readCodexTurnId(event.params) ?? this.activeTurn(event.threadId)
|
||||
if (!turnId) {
|
||||
return null
|
||||
}
|
||||
// Collapse compactions within a thread/turn; the canonical item replaces its legacy fallback.
|
||||
const key = createHash('sha256')
|
||||
.update(JSON.stringify([event.threadId, turnId]))
|
||||
.digest('hex')
|
||||
const source = event.method === 'item/completed' ? 'item' : 'legacy'
|
||||
const previous = this.turns.get(key)
|
||||
if (previous === 'item' || previous === source) {
|
||||
return CODEX_JOURNAL_ADMITTED
|
||||
}
|
||||
const admission = appendCodexLifecycleItem(
|
||||
this.sink,
|
||||
{ provider: 'orca', clientMessageId: `codex-compaction:${key}` },
|
||||
{ kind: 'status', text: 'Context compacted', presentation: 'compaction' }
|
||||
)
|
||||
if (!admission.accepted) {
|
||||
return admission
|
||||
}
|
||||
const published = publishCodexLifecycle(this.sink)
|
||||
if (!published.accepted) {
|
||||
return published
|
||||
}
|
||||
this.turns.set(key, source)
|
||||
while (this.turns.size > MAX_CODEX_GENERIC_TURN_BUCKETS) {
|
||||
const oldest = this.turns.keys().next().value
|
||||
if (oldest !== undefined) {
|
||||
this.turns.delete(oldest)
|
||||
}
|
||||
}
|
||||
return CODEX_JOURNAL_ADMITTED
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.turns.clear()
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,9 @@ export class CodexJournalItems {
|
||||
if (source === 'live' && item.type === 'userMessage') {
|
||||
return { handled: true, admission: CODEX_JOURNAL_ADMITTED }
|
||||
}
|
||||
if (item.type === 'contextCompaction' && event.method === 'item/started') {
|
||||
return { handled: true, admission: CODEX_JOURNAL_ADMITTED }
|
||||
}
|
||||
const translated = codexJournalItem(item)
|
||||
const command = readCodexJournalString(item, 'command')
|
||||
if (command) {
|
||||
|
||||
@@ -603,3 +603,68 @@ describe('codex journal translation', () => {
|
||||
expect(tap.rows).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('notice journal pipeline', () => {
|
||||
it('replaces a legacy compaction divider with its canonical item at the same journal key', () => {
|
||||
const { translator, tap } = translatorWith()
|
||||
translator.handle(notification('thread/compacted', { threadId: THREAD_ID, turnId: TURN_ID }))
|
||||
translator.handle(
|
||||
notification('item/completed', {
|
||||
turnId: TURN_ID,
|
||||
item: { id: 'compact', type: 'contextCompaction' }
|
||||
})
|
||||
)
|
||||
expect(tap.rows).toHaveLength(2)
|
||||
expect([...new Map(tap.rows.map((row) => [row.key, row.body])).values()]).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: 'status',
|
||||
text: 'Context compacted',
|
||||
presentation: 'compaction'
|
||||
})
|
||||
])
|
||||
translator.dispose()
|
||||
})
|
||||
it('preserves every notice after generic traffic reaches its cap', () => {
|
||||
const { translator, tap, window } = translatorWith()
|
||||
translator.handle(TURN_STARTED)
|
||||
for (let index = 0; index < MAX_CODEX_GENERIC_ROWS_PER_TURN; index += 1) {
|
||||
translator.handle(notification('future/notification', { value: index }))
|
||||
}
|
||||
for (const method of ['warning', 'guardianWarning', 'configWarning', 'deprecationNotice']) {
|
||||
translator.handle(notification(method, { message: method, summary: method }))
|
||||
}
|
||||
window.fire()
|
||||
expect(tap.rows.slice(-4).map((row) => row.body)).toEqual([
|
||||
expect.objectContaining({ text: 'warning', tone: 'warning' }),
|
||||
expect.objectContaining({ text: 'guardianWarning', tone: 'warning' }),
|
||||
expect.objectContaining({ text: 'configWarning', tone: 'warning' }),
|
||||
expect.objectContaining({ text: 'deprecationNotice', tone: 'notice' })
|
||||
])
|
||||
translator.dispose()
|
||||
})
|
||||
it('keeps the plan document marker during streamed updates and completion', () => {
|
||||
const { translator, tap, window } = translatorWith()
|
||||
translator.handle(
|
||||
notification('item/started', {
|
||||
turnId: TURN_ID,
|
||||
item: { id: 'plan', type: 'plan', text: '' }
|
||||
})
|
||||
)
|
||||
translator.handle(
|
||||
notification('item/plan/delta', { turnId: TURN_ID, itemId: 'plan', delta: '# Plan' })
|
||||
)
|
||||
window.fire()
|
||||
expect(tap.rows.at(-1)?.body).toMatchObject({ text: '# Plan', presentation: 'plan-document' })
|
||||
translator.handle(
|
||||
notification('item/completed', {
|
||||
turnId: TURN_ID,
|
||||
item: { id: 'plan', type: 'plan', text: '# Plan\n\nComplete' }
|
||||
})
|
||||
)
|
||||
expect(tap.rows.at(-1)?.body).toMatchObject({
|
||||
text: '# Plan\n\nComplete',
|
||||
presentation: 'plan-document'
|
||||
})
|
||||
translator.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { CodexSubagentRoster } from './codex-subagent-roster'
|
||||
import { readCodexThreadItem } from './codex-structured-item-translation'
|
||||
import { CodexJournalGenericFrames } from './codex-structured-journal-generic-frames'
|
||||
import { CodexJournalCompactions } from './codex-structured-journal-compactions'
|
||||
import { CodexJournalItems } from './codex-structured-journal-items'
|
||||
import { CodexJournalPrompts } from './codex-structured-journal-prompts'
|
||||
import {
|
||||
@@ -46,6 +47,9 @@ export function createCodexJournalTranslator(
|
||||
deps: CodexJournalTranslatorDeps
|
||||
): CodexJournalTranslator {
|
||||
const activeTurns = new CodexJournalActiveTurns()
|
||||
const compactions = new CodexJournalCompactions(deps.sink, (threadId) =>
|
||||
activeTurns.current(threadId)
|
||||
)
|
||||
const genericFrames = new CodexJournalGenericFrames(deps, (threadId) =>
|
||||
activeTurns.current(threadId)
|
||||
)
|
||||
@@ -94,6 +98,10 @@ export function createCodexJournalTranslator(
|
||||
currentTurnIds: activeTurns.byThread,
|
||||
ordinals: items.ordinals,
|
||||
handleItem: (event) => {
|
||||
const compaction = compactions.handle(event)
|
||||
if (compaction) {
|
||||
return compaction
|
||||
}
|
||||
const translated = items.handle(event, 'history')
|
||||
return translated.handled
|
||||
? translated.admission
|
||||
@@ -135,6 +143,7 @@ export function createCodexJournalTranslator(
|
||||
items.activeItems.clear()
|
||||
prompts.pending.clear()
|
||||
activeTurns.clear()
|
||||
compactions.clear()
|
||||
return CODEX_JOURNAL_ADMITTED
|
||||
}
|
||||
if (event.type === 'notification') {
|
||||
@@ -168,6 +177,10 @@ export function createCodexJournalTranslator(
|
||||
if (event.method === 'turn/started') {
|
||||
return startTurn(event)
|
||||
}
|
||||
const compaction = compactions.handle(event)
|
||||
if (compaction) {
|
||||
return publishActivity(event, compaction)
|
||||
}
|
||||
if (event.method === 'turn/completed') {
|
||||
return completeTurn(event)
|
||||
}
|
||||
@@ -223,17 +236,15 @@ export function createCodexJournalTranslator(
|
||||
genericFrames.dispose()
|
||||
subagents.dispose()
|
||||
activeTurns.clear()
|
||||
compactions.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/** Settles the item a notification the transport refused to carry left
|
||||
* mid-flight; null when the frame is not one. */
|
||||
function settleOversizedNotification(event: {
|
||||
sessionId: string
|
||||
threadId: string
|
||||
kind: string
|
||||
payload: unknown
|
||||
}): CodexJournalTranslationAdmission | null {
|
||||
function settleOversizedNotification(
|
||||
event: Extract<CodexStructuredSessionEvent, { type: 'provider-frame' }>
|
||||
): CodexJournalTranslationAdmission | null {
|
||||
return settleCodexOversizedNotificationFrame({
|
||||
...event,
|
||||
sink: deps.sink,
|
||||
@@ -242,11 +253,9 @@ export function createCodexJournalTranslator(
|
||||
})
|
||||
}
|
||||
|
||||
function startTurn(event: {
|
||||
sessionId: string
|
||||
threadId: string
|
||||
params: unknown
|
||||
}): CodexJournalTranslationAdmission {
|
||||
function startTurn(
|
||||
event: Extract<CodexStructuredSessionEvent, { type: 'notification' }>
|
||||
): CodexJournalTranslationAdmission {
|
||||
const turnId = readCodexTurnId(event.params)
|
||||
if (!turnId) {
|
||||
return CODEX_JOURNAL_ADMITTED
|
||||
|
||||
@@ -104,11 +104,12 @@ describe('provider frame classification catalog', () => {
|
||||
})
|
||||
|
||||
it('dispositions codex item-form frames, which the method catalog never matches', () => {
|
||||
// `thread/compacted` is already chrome; its item form is the same event and
|
||||
// must not leak `codex · item:contextCompaction` into the transcript.
|
||||
expect(classifyProviderFrame('codex', 'item:contextCompaction', {})).toBe('status-chrome')
|
||||
// Both provider generations reach the journal's compaction deduplication.
|
||||
expect(classifyProviderFrame('codex', 'item:contextCompaction', {})).toBe(
|
||||
'timeline-substantive'
|
||||
)
|
||||
expect(classifyProviderFrame('codex', 'notification:thread/compacted', {})).toBe(
|
||||
'status-chrome'
|
||||
'timeline-substantive'
|
||||
)
|
||||
// An item type nobody has dispositioned still falls through visibly.
|
||||
expect(classifyProviderFrame('codex', 'item:futureThing', {})).toBe('timeline-substantive')
|
||||
@@ -165,6 +166,21 @@ describe('provider frame classification catalog', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('notice disposition boundaries', () => {
|
||||
it.each(['warning', 'guardianWarning', 'deprecationNotice', 'configWarning'])(
|
||||
'retains the error-surface cap exemption for %s',
|
||||
(method) => {
|
||||
expect(classifyProviderFrame('codex', `notification:${method}`, {})).toBe('error-surface')
|
||||
}
|
||||
)
|
||||
it('does not change usage or rate-limit classifications', () => {
|
||||
expect(classifyProviderFrame('codex', 'thread/tokenUsage/updated', {})).toBe('status-chrome')
|
||||
expect(classifyProviderFrame('codex', 'account/rateLimits/updated', {})).toBe(
|
||||
'suppressed-benign'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('codex subagent item disposition', () => {
|
||||
it('keeps subagent lifecycle out of the transcript now that it renders as a roster row', () => {
|
||||
expect(
|
||||
|
||||
@@ -66,7 +66,7 @@ export const PROVIDER_FRAME_CLASSIFICATIONS = {
|
||||
'item/reasoning/summaryTextDelta': 'stream-into-item',
|
||||
'item/reasoning/summaryPartAdded': 'stream-into-item',
|
||||
'item/reasoning/textDelta': 'stream-into-item',
|
||||
'thread/compacted': 'status-chrome',
|
||||
'thread/compacted': 'timeline-substantive',
|
||||
'model/rerouted': 'status-chrome',
|
||||
'model/verification': 'status-chrome',
|
||||
'turn/moderationMetadata': 'suppressed-benign',
|
||||
@@ -196,9 +196,8 @@ function hasProviderError(payload: unknown): boolean {
|
||||
* new item type cannot leak `codex · item:<type>` into the transcript. The
|
||||
* notification catalog above is keyed by METHOD and never matches these. */
|
||||
const CODEX_ITEM_CLASSIFICATIONS: Record<string, ProviderFrameClassification> = {
|
||||
// The `thread/compacted` notification is already chrome; its item form is the
|
||||
// same event and must not read as a mysterious opcode row.
|
||||
contextCompaction: 'status-chrome',
|
||||
// The journal coalesces this canonical completion with the legacy notification.
|
||||
contextCompaction: 'timeline-substantive',
|
||||
// Subagent lifecycle renders as the spawn-group roster row, so its raw items
|
||||
// must not print a gray `codex · item:<type>` row beside it. The live
|
||||
// notification path intercepts them before this catalog is reached;
|
||||
|
||||
@@ -11,6 +11,13 @@ function record(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
|
||||
}
|
||||
|
||||
export function isCodexCompactionComplete(method: string, params: unknown): boolean {
|
||||
return (
|
||||
method === 'thread/compacted' ||
|
||||
(method === 'item/completed' && record(record(params).item).type === 'contextCompaction')
|
||||
)
|
||||
}
|
||||
|
||||
/** A receipt is not completion; keep listening through the provider's terminal frame. */
|
||||
export class StructuredSessionCompaction {
|
||||
private readonly pending = new Map<string, PendingCompaction>()
|
||||
@@ -95,10 +102,7 @@ export class StructuredSessionCompaction {
|
||||
if (method === 'turn/started' && typeof turn.id === 'string') {
|
||||
pending.turnId = turn.id
|
||||
}
|
||||
if (
|
||||
method === 'thread/compacted' ||
|
||||
(method === 'item/completed' && record(params.item).type === 'contextCompaction')
|
||||
) {
|
||||
if (isCodexCompactionComplete(method, params)) {
|
||||
pending.compacted = true
|
||||
}
|
||||
if (method === 'turn/completed' && turn.id === pending.turnId) {
|
||||
|
||||
@@ -235,3 +235,41 @@ describe('a failed provider dependency', () => {
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('typed notice metadata', () => {
|
||||
it('publishes readable compaction statuses for both provider forms', () => {
|
||||
expect(
|
||||
unhandledProviderFrameJournalItem('codex', 'notification:thread/compacted', {})
|
||||
).toMatchObject({
|
||||
classification: 'timeline-substantive',
|
||||
body: { kind: 'status', text: 'Context compacted', presentation: 'compaction' }
|
||||
})
|
||||
expect(unhandledProviderFrameJournalItem('codex', 'item:contextCompaction', {})).toMatchObject({
|
||||
body: { kind: 'status', text: 'Context compacted', presentation: 'compaction' }
|
||||
})
|
||||
})
|
||||
it.each([
|
||||
['warning', { message: 'Check this' }, 'warning', 'Check this'],
|
||||
['guardianWarning', { message: 'Review required' }, 'warning', 'Review required'],
|
||||
[
|
||||
'configWarning',
|
||||
{ summary: 'Invalid option', details: 'Remove the option' },
|
||||
'warning',
|
||||
'Invalid option\n\nRemove the option'
|
||||
],
|
||||
[
|
||||
'deprecationNotice',
|
||||
{ summary: 'Old option', details: 'Use its replacement' },
|
||||
'notice',
|
||||
'Old option\n\nUse its replacement'
|
||||
],
|
||||
['error', { error: { message: 'Connection failed' } }, 'error', 'Connection failed']
|
||||
])('assigns the tone and readable text for %s', (method, payload, tone, text) => {
|
||||
expect(
|
||||
unhandledProviderFrameJournalItem('codex', `notification:${method}`, payload)
|
||||
).toMatchObject({
|
||||
classification: 'error-surface',
|
||||
body: { kind: 'status', text, tone }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -90,12 +90,38 @@ export function unhandledProviderFrameJournalItem(
|
||||
// Why: the opcode alone ("codex · notification:warning") tells the user nothing
|
||||
// and reads as protocol noise. Lead with the provider's own sentence when it has
|
||||
// one; the raw frame stays behind the row's disclosure either way.
|
||||
const message = readableProviderFrameText(payload)
|
||||
const method = kind.startsWith('notification:') ? kind.slice('notification:'.length) : kind
|
||||
const compaction =
|
||||
provider === 'codex' && (method === 'thread/compacted' || method === 'item:contextCompaction')
|
||||
const noticeTone =
|
||||
provider === 'codex'
|
||||
? method === 'deprecationNotice'
|
||||
? 'notice'
|
||||
: ['warning', 'guardianWarning', 'configWarning'].includes(method)
|
||||
? 'warning'
|
||||
: undefined
|
||||
: undefined
|
||||
const tone = noticeTone ?? (classification === 'error-surface' ? 'error' : undefined)
|
||||
let message = readableProviderFrameText(payload)
|
||||
if (
|
||||
provider === 'codex' &&
|
||||
(method === 'configWarning' || method === 'deprecationNotice') &&
|
||||
typeof payload === 'object' &&
|
||||
payload !== null
|
||||
) {
|
||||
const record = payload as Record<string, unknown>
|
||||
message =
|
||||
[record.summary, record.details]
|
||||
.filter((part): part is string => typeof part === 'string' && part.trim().length > 0)
|
||||
.join('\n\n') || message
|
||||
}
|
||||
const display = message ? boundInlineText(message, limits) : null
|
||||
return {
|
||||
body: {
|
||||
kind: 'status',
|
||||
text: display?.text ?? `${provider} · ${kind}`,
|
||||
text: compaction ? 'Context compacted' : (display?.text ?? `${provider} · ${kind}`),
|
||||
...(compaction ? { presentation: 'compaction' } : {}),
|
||||
...(tone ? { tone } : {}),
|
||||
providerFrame: { provider, kind, payload: bounded }
|
||||
},
|
||||
classification: classification === 'error-surface' ? 'error-surface' : 'timeline-substantive'
|
||||
|
||||
@@ -30,6 +30,7 @@ export function migrate(this: OrchestrationDb): void {
|
||||
migrateV37.call(this, current)
|
||||
migrateV38.call(this, current)
|
||||
migrateV39.call(this, current)
|
||||
this.createMailboxDeliveryIndexesIfPossible()
|
||||
this.db.pragma(`user_version = ${SCHEMA_VERSION}`)
|
||||
this.db.exec('COMMIT')
|
||||
} catch (err) {
|
||||
|
||||
@@ -27,6 +27,13 @@ export function createMailboxDeliveryIndexesIfPossible(this: OrchestrationDb): v
|
||||
ON messages(to_handle, sequence)
|
||||
WHERE read = 0 AND pointer_enter_pending > 0;
|
||||
`)
|
||||
if (this.hasColumn('messages', 'pointer_pty_id')) {
|
||||
// Working-title frames release one PTY's reservations, including already-read rows.
|
||||
this.db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_pending_pointer_pty
|
||||
ON messages(pointer_pty_id) WHERE pointer_enter_pending > 0;
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import Database from '../../sqlite/sync-database'
|
||||
import { OrchestrationDb } from './db'
|
||||
|
||||
function assertTargetedRelease(db: OrchestrationDb): void {
|
||||
const prepare = vi.spyOn(db.db, 'prepare')
|
||||
db.releasePendingMailboxPointerForPty('absent-pty')
|
||||
const sql = prepare.mock.calls.find(([query]) => query.startsWith('UPDATE messages'))?.[0]
|
||||
prepare.mockRestore()
|
||||
expect(sql).toBeDefined()
|
||||
const plan = db.db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(1, 'absent-pty') as {
|
||||
detail: string
|
||||
}[]
|
||||
expect(plan.some(({ detail }) => detail.includes('SCAN messages'))).toBe(false)
|
||||
expect(plan.some(({ detail }) => detail.includes('idx_messages_pending_pointer_pty'))).toBe(true)
|
||||
}
|
||||
|
||||
describe('PTY mailbox reservation cleanup', () => {
|
||||
it('uses a PTY lookup and preserves reservation settlement semantics', () => {
|
||||
const db = new OrchestrationDb(':memory:')
|
||||
try {
|
||||
const seed = db.db.prepare(`INSERT INTO messages
|
||||
(id, from_handle, to_handle, subject, read, pointer_enter_pending,
|
||||
pointer_pty_id, pointer_process_incarnation, delivered_at)
|
||||
VALUES (?, 'sender', 'recipient', 'test', ?, ?, ?, 'incarnation', ?)`)
|
||||
db.db.exec('BEGIN')
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
seed.run(`history-${i}`, 1, 0, null, null)
|
||||
}
|
||||
seed.run('reserved', 0, 1, 'target', '2026-01-01 00:00:00')
|
||||
seed.run('written', 0, 2, 'target', null)
|
||||
seed.run('entered', 0, 3, 'target', null)
|
||||
seed.run('read', 1, 2, 'target', '2026-01-01 00:00:00')
|
||||
seed.run('other', 0, 1, 'other-pty', null)
|
||||
db.db.exec('COMMIT')
|
||||
assertTargetedRelease(db)
|
||||
db.releasePendingMailboxPointerForPty('target')
|
||||
const read = (id: string) => db.db.prepare('SELECT * FROM messages WHERE id = ?').get(id)
|
||||
for (const id of ['reserved', 'written', 'entered', 'read']) {
|
||||
expect(read(id)).toMatchObject({
|
||||
pointer_enter_pending: 0,
|
||||
pointer_pty_id: null,
|
||||
pointer_process_incarnation: null
|
||||
})
|
||||
}
|
||||
expect(read('reserved')).toMatchObject({ delivered_at: null, read: 0 })
|
||||
expect(read('written')).toMatchObject({ delivered_at: expect.any(String), read: 0 })
|
||||
expect(read('entered')).toMatchObject({ delivered_at: expect.any(String), read: 0 })
|
||||
expect(read('read')).toMatchObject({ delivered_at: '2026-01-01 00:00:00', read: 1 })
|
||||
expect(read('other')).toMatchObject({ pointer_enter_pending: 1, pointer_pty_id: 'other-pty' })
|
||||
expect(read('history-0')).toMatchObject({ read: 1, delivered_at: null })
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['current', 'before-pointer-columns'])(
|
||||
'installs the lookup on first open of an existing %s database',
|
||||
(version) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'orca-pointer-release-'))
|
||||
const path = join(dir, 'orchestration.db')
|
||||
try {
|
||||
new OrchestrationDb(path).close()
|
||||
const raw = new Database(path)
|
||||
try {
|
||||
raw.exec('DROP INDEX idx_messages_pending_pointer_pty')
|
||||
if (version === 'before-pointer-columns') {
|
||||
raw.exec(`DROP INDEX idx_messages_pending_pointer_enter;
|
||||
ALTER TABLE messages DROP COLUMN pointer_enter_pending;
|
||||
ALTER TABLE messages DROP COLUMN pointer_pty_id;
|
||||
ALTER TABLE messages DROP COLUMN pointer_process_incarnation;`)
|
||||
raw.pragma('user_version = 32')
|
||||
}
|
||||
} finally {
|
||||
raw.close()
|
||||
}
|
||||
const reopened = new OrchestrationDb(path)
|
||||
try {
|
||||
assertTargetedRelease(reopened)
|
||||
} finally {
|
||||
reopened.close()
|
||||
}
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -431,7 +431,9 @@ describe('OrchestrationDb version-skew migration', () => {
|
||||
|
||||
const raw = new Database(dbPath)
|
||||
raw.exec(
|
||||
'DROP INDEX IF EXISTS idx_messages_pending_pointer_enter; ALTER TABLE messages DROP COLUMN pointer_enter_pending;'
|
||||
`DROP INDEX IF EXISTS idx_messages_pending_pointer_enter;
|
||||
DROP INDEX IF EXISTS idx_messages_pending_pointer_pty;
|
||||
ALTER TABLE messages DROP COLUMN pointer_enter_pending;`
|
||||
)
|
||||
raw.pragma('user_version = 33')
|
||||
expect(resolveOrchestrationMigrationStartVersion(raw, 33, SCHEMA_VERSION)).toBe(6)
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { isSubagentGroupBlock, type NativeChatMessage } from '../../../../shared/native-chat-types'
|
||||
import { splitNativeChatBlocks } from './native-chat-tool-fold'
|
||||
import { NativeChatToolRun } from './NativeChatToolRun'
|
||||
import { NativeChatNoticeRow } from './NativeChatNoticeRow'
|
||||
import { NativeChatMessageTimestamp } from './NativeChatMessageTimestamp'
|
||||
import { nativeChatProseToMarkdown } from './native-chat-prose'
|
||||
import {
|
||||
@@ -94,6 +95,24 @@ export const MessageRow = memo(function MessageRow({
|
||||
return null
|
||||
}
|
||||
|
||||
const notice = isSystem
|
||||
? message.blocks.find(
|
||||
(block) =>
|
||||
block.type === 'text' && (block.presentation !== undefined || block.tone !== undefined)
|
||||
)
|
||||
: undefined
|
||||
if (notice?.type === 'text') {
|
||||
return (
|
||||
<div ref={rowRef}>
|
||||
<NativeChatNoticeRow
|
||||
block={notice}
|
||||
onLinkClick={onLinkClick}
|
||||
allowFileUriLinks={allowFileUriLinks}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (providerFrame) {
|
||||
return (
|
||||
<div ref={rowRef}>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// @vitest-environment happy-dom
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AgentJournalItemBodySchema } from '../../../../shared/agent-session-journal-schemas'
|
||||
import { projectStructuredItemsToNativeChat } from '../../../../shared/structured-agent-session-projection'
|
||||
import type { AgentJournalStatusItem } from '../../../../shared/agent-session-journal-types'
|
||||
import { MessageRow } from './NativeChatMessageRow'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
function renderStatus(body: AgentJournalStatusItem) {
|
||||
const [message] = projectStructuredItemsToNativeChat([
|
||||
{ itemId: 'notice', sequence: 1, revision: 1, observedAt: 1, body }
|
||||
])
|
||||
return render(
|
||||
<MessageRow message={message!} expandSignal={false} onScrollMessageToTop={vi.fn()} />
|
||||
)
|
||||
}
|
||||
|
||||
describe('notice rows', () => {
|
||||
it('renders compaction as a centered separator', () => {
|
||||
renderStatus({ kind: 'status', text: 'Context compacted', presentation: 'compaction' })
|
||||
expect(screen.getByRole('separator', { name: 'Context compacted' })).toHaveClass(
|
||||
'text-muted-foreground'
|
||||
)
|
||||
expect(
|
||||
screen.getByText('Context compacted').parentElement?.querySelectorAll('.bg-border')
|
||||
).toHaveLength(2)
|
||||
})
|
||||
it.each([
|
||||
['warning', 'text-[color:var(--warning,#f59e0b)]'],
|
||||
['error', 'text-destructive'],
|
||||
['notice', 'text-muted-foreground']
|
||||
])('renders %s using its existing color treatment', (tone, className) => {
|
||||
renderStatus({ kind: 'status', text: 'Readable notice', tone })
|
||||
expect(screen.getByText('Readable notice').parentElement?.parentElement).toHaveClass(className)
|
||||
})
|
||||
it('renders a plan as readable markdown in the card primitive', () => {
|
||||
renderStatus({
|
||||
kind: 'status',
|
||||
text: '# Steps\n\nA **readable** document.',
|
||||
presentation: 'plan-document'
|
||||
})
|
||||
expect(screen.getByText('Plan').closest('[data-slot="card"]')).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: 'Steps' })).toBeInTheDocument()
|
||||
expect(screen.getByText('readable').tagName).toBe('STRONG')
|
||||
expect(screen.getByText('readable').closest('[data-slot="card-content"]')).toHaveClass(
|
||||
'text-sm',
|
||||
'text-foreground'
|
||||
)
|
||||
})
|
||||
it('shows provider notice text once while retaining its diagnostic disclosure', () => {
|
||||
renderStatus({
|
||||
kind: 'status',
|
||||
text: 'Check the configuration',
|
||||
tone: 'warning',
|
||||
providerFrame: {
|
||||
provider: 'codex',
|
||||
kind: 'notification:warning',
|
||||
payload: {
|
||||
head: '{"message":"Check the configuration"}',
|
||||
byteLength: 37,
|
||||
digest: 'digest',
|
||||
truncated: false
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(screen.getAllByText('Check the configuration')).toHaveLength(1)
|
||||
const disclosure = screen.getByText('Details').closest('details')
|
||||
expect(disclosure?.querySelector('summary')).not.toHaveTextContent('Check the configuration')
|
||||
expect(disclosure?.querySelector('pre')).toHaveTextContent('Check the configuration')
|
||||
})
|
||||
it('renders future presentation and tone values as untinted text', () => {
|
||||
renderStatus({
|
||||
kind: 'status',
|
||||
text: 'Future readable text',
|
||||
tone: 'future-tone',
|
||||
presentation: 'future-presentation'
|
||||
})
|
||||
expect(screen.getByText('Future readable text').parentElement?.parentElement).toHaveClass(
|
||||
'text-foreground'
|
||||
)
|
||||
expect(screen.getByText('Future readable text').parentElement?.querySelector('svg')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('old-reader compatibility', () => {
|
||||
// Derive the prior status shape without its new optional hints.
|
||||
const statusSchema = AgentJournalItemBodySchema.options.find(
|
||||
(schema): schema is (typeof AgentJournalItemBodySchema.options)[5] =>
|
||||
schema.shape.kind.value === 'status'
|
||||
)!
|
||||
const oldStatusSchema = statusSchema.omit({ tone: true, presentation: true })
|
||||
it.each([
|
||||
{ presentation: 'compaction' },
|
||||
{ presentation: 'plan-document' },
|
||||
{ tone: 'warning' },
|
||||
{ tone: 'error' },
|
||||
{ tone: 'notice' },
|
||||
{ tone: 'future-tone', presentation: 'future-presentation' }
|
||||
])('accepts new metadata and still renders text with an old reader: %j', (metadata) => {
|
||||
const body = { kind: 'status', text: 'Text survives version skew', ...metadata }
|
||||
expect(AgentJournalItemBodySchema.safeParse(body).success).toBe(true)
|
||||
const oldBody = oldStatusSchema.parse(body) as AgentJournalStatusItem
|
||||
expect(oldBody).toEqual({ kind: 'status', text: body.text })
|
||||
renderStatus(oldBody)
|
||||
expect(screen.getByText(body.text)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { AlertCircle, AlertTriangle, Info } from 'lucide-react'
|
||||
import CommentMarkdown, {
|
||||
type CommentMarkdownLinkClickHandler
|
||||
} from '@/components/sidebar/CommentMarkdown'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { NativeChatTextBlock } from '../../../../shared/native-chat-types'
|
||||
import { ProviderFrameRow } from './NativeChatTranscriptChrome'
|
||||
|
||||
export function NativeChatNoticeRow({
|
||||
block,
|
||||
onLinkClick,
|
||||
allowFileUriLinks = false
|
||||
}: {
|
||||
block: NativeChatTextBlock
|
||||
onLinkClick?: CommentMarkdownLinkClickHandler
|
||||
allowFileUriLinks?: boolean
|
||||
}): React.JSX.Element {
|
||||
if (block.presentation === 'compaction') {
|
||||
const label = translate('components.native-chat.notices.compaction', 'Context compacted')
|
||||
return (
|
||||
<div
|
||||
role="separator"
|
||||
aria-label={label}
|
||||
className="flex items-center gap-3 py-2 text-xs text-muted-foreground"
|
||||
>
|
||||
<span className="h-px flex-1 bg-border" />
|
||||
<span>{label}</span>
|
||||
<span className="h-px flex-1 bg-border" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (block.presentation === 'plan-document') {
|
||||
return (
|
||||
<Card className="gap-3 py-3 shadow-xs">
|
||||
<CardHeader className="px-4">
|
||||
<CardTitle className="text-sm">
|
||||
{translate('components.native-chat.notices.plan', 'Plan')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 text-sm leading-relaxed text-foreground">
|
||||
<CommentMarkdown
|
||||
content={block.text}
|
||||
variant="document"
|
||||
className="text-sm"
|
||||
onLinkClick={onLinkClick}
|
||||
allowFileUriLinks={allowFileUriLinks}
|
||||
linkifyFilePaths={onLinkClick !== undefined}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
const tone = block.tone
|
||||
const Icon =
|
||||
tone === 'warning'
|
||||
? AlertTriangle
|
||||
: tone === 'error'
|
||||
? AlertCircle
|
||||
: tone === 'notice'
|
||||
? Info
|
||||
: null
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'space-y-2 text-sm text-foreground',
|
||||
Icon && 'rounded-md border border-border bg-muted/20 p-3',
|
||||
tone === 'warning' && 'text-[color:var(--warning,#f59e0b)]',
|
||||
tone === 'error' && 'text-destructive',
|
||||
tone === 'notice' && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
{Icon ? <Icon aria-hidden="true" className="mt-0.5 size-4 shrink-0" /> : null}
|
||||
<p className="min-w-0 whitespace-pre-wrap break-words">{block.text}</p>
|
||||
</div>
|
||||
{block.providerFrame ? (
|
||||
<ProviderFrameRow
|
||||
block={block}
|
||||
summary={translate('components.native-chat.notices.details', 'Details')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -274,7 +274,13 @@ export function NativeChatAgentControls({
|
||||
)
|
||||
}
|
||||
|
||||
export function ProviderFrameRow({ block }: { block: NativeChatBlock }): React.JSX.Element | null {
|
||||
export function ProviderFrameRow({
|
||||
block,
|
||||
summary
|
||||
}: {
|
||||
block: NativeChatBlock
|
||||
summary?: string
|
||||
}): React.JSX.Element | null {
|
||||
if (block.type !== 'text' || !block.providerFrame) {
|
||||
return null
|
||||
}
|
||||
@@ -284,7 +290,7 @@ export function ProviderFrameRow({ block }: { block: NativeChatBlock }): React.J
|
||||
<summary className="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
<span className="transition-transform group-open:rotate-90">›</span>
|
||||
<span className="font-medium text-foreground">{frame.provider}</span>
|
||||
<span className="truncate">{nativeChatProviderFrameSummary(block)}</span>
|
||||
<span className="truncate">{summary ?? nativeChatProviderFrameSummary(block)}</span>
|
||||
{frame.payload.truncated ? (
|
||||
<span>
|
||||
·{' '}
|
||||
|
||||
@@ -236,7 +236,8 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
useSidebarRevealRequests({
|
||||
groupBy,
|
||||
renderedSidebarRowKeys: rowModel.renderedSidebarRowKeys,
|
||||
renderedWorktreeIdentities: selection.renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces: visibleScope.visibleFolderWorkspacesForRows,
|
||||
currentSidebarWorktreeId,
|
||||
currentSidebarExecutionHostId: activeWorkspaceExecutionHostId,
|
||||
worktreeMap,
|
||||
|
||||
+80
-36
@@ -82,7 +82,8 @@ beforeEach(() => {
|
||||
args = {
|
||||
groupBy: 'repo',
|
||||
renderedSidebarRowKeys: new Set(),
|
||||
renderedWorktreeIdentities: [],
|
||||
visibleWorktrees: [],
|
||||
visibleFolderWorkspaces: [],
|
||||
currentSidebarWorktreeId: worktree.id,
|
||||
currentSidebarExecutionHostId: 'ssh:dev',
|
||||
worktreeMap: new Map([[worktree.id, worktree]]),
|
||||
@@ -133,7 +134,7 @@ describe('revealing a filtered workspace', () => {
|
||||
args = {
|
||||
...args,
|
||||
hasFilters: visible,
|
||||
renderedWorktreeIdentities: visible ? ['ssh:dev|wt-1'] : []
|
||||
visibleWorktrees: visible ? args.worktrees : []
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
@@ -143,6 +144,41 @@ describe('revealing a filtered workspace', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['ssh:dev', null] as const)(
|
||||
'reveals a collapsed workspace that passes filters with active host %s',
|
||||
async (executionHostId) => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarExecutionHostId: executionHostId,
|
||||
visibleWorktrees: args.worktrees
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
)
|
||||
|
||||
it('does not let a visible same-id workspace on another host bypass confirmation', async () => {
|
||||
args = { ...args, visibleWorktrees: [{ ...args.worktrees[0], hostId: 'local' }] }
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
expect(document.querySelector('[role="dialog"]')).not.toBeNull()
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
await click('Keep filters')
|
||||
})
|
||||
|
||||
it('preserves filters when the target becomes included while confirmation is open', async () => {
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
args = { ...args, visibleWorktrees: args.worktrees }
|
||||
await render()
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not apply a stale confirmation after switching workspaces', async () => {
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceReveal())
|
||||
@@ -153,39 +189,47 @@ describe('revealing a filtered workspace', () => {
|
||||
expect(state.revealWorktreeInSidebar).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('confirms filtered folder workspaces and preserves the rename request', async () => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarWorktreeId: folderWorkspaceKey('folder-1'),
|
||||
currentSidebarExecutionHostId: null,
|
||||
folderWorkspaces: [
|
||||
{
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'project-1',
|
||||
name: 'Notes',
|
||||
folderPath: '/notes',
|
||||
linkedTask: null,
|
||||
comment: '',
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
lastActivityAt: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
]
|
||||
it.each([true, false])(
|
||||
'reveals folder workspaces and preserves rename (filtered: %s)',
|
||||
async (filtered) => {
|
||||
args = {
|
||||
...args,
|
||||
currentSidebarWorktreeId: folderWorkspaceKey('folder-1'),
|
||||
currentSidebarExecutionHostId: null,
|
||||
folderWorkspaces: [
|
||||
{
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'project-1',
|
||||
name: 'Notes',
|
||||
folderPath: '/notes',
|
||||
linkedTask: null,
|
||||
comment: '',
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
lastActivityAt: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
args.visibleFolderWorkspaces = filtered ? [] : args.folderWorkspaces
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceRevealAndRename())
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
if (filtered) {
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
} else {
|
||||
expect(document.querySelector('[role="dialog"]')).toBeNull()
|
||||
}
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledWith(folderWorkspaceKey('folder-1'), {
|
||||
behavior: 'smooth',
|
||||
highlight: true,
|
||||
beginRename: true,
|
||||
executionHostId: undefined
|
||||
})
|
||||
}
|
||||
await render()
|
||||
await act(async () => requestScrollToCurrentWorkspaceRevealAndRename())
|
||||
expect(args.clearFilters).not.toHaveBeenCalled()
|
||||
await click('Clear filters and reveal')
|
||||
expect(args.clearFilters).toHaveBeenCalledTimes(1)
|
||||
expect(state.revealWorktreeInSidebar).toHaveBeenCalledWith(folderWorkspaceKey('folder-1'), {
|
||||
behavior: 'smooth',
|
||||
highlight: true,
|
||||
beginRename: true,
|
||||
executionHostId: undefined
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
+33
-10
@@ -10,16 +10,30 @@ import {
|
||||
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
|
||||
import type { Worktree } from '../../../../../../shared/worktree/types'
|
||||
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
|
||||
import { composeWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
|
||||
import { getWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
|
||||
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
|
||||
import type { WorktreeGroupBy } from '../grouping/row-types'
|
||||
import { getKnownSidebarWorktreeById } from './folder-reveal'
|
||||
|
||||
function workspacePassesFilters(
|
||||
worktree: Worktree,
|
||||
worktrees: readonly Worktree[],
|
||||
folderWorkspaces: readonly FolderWorkspace[]
|
||||
): boolean {
|
||||
const identity = getWorktreeHostIdentity(worktree)
|
||||
return (
|
||||
worktrees.some((candidate) => getWorktreeHostIdentity(candidate) === identity) ||
|
||||
folderWorkspaces.some((workspace) => folderWorkspaceKey(workspace.id) === worktree.id)
|
||||
)
|
||||
}
|
||||
|
||||
// Turns a "show me the current workspace" request into whatever the sidebar must change
|
||||
// first — grouping mode, active filters — before the viewport can scroll to it.
|
||||
export function useSidebarRevealRequests(args: {
|
||||
groupBy: WorktreeGroupBy
|
||||
renderedSidebarRowKeys: ReadonlySet<string>
|
||||
renderedWorktreeIdentities: readonly string[]
|
||||
visibleWorktrees: readonly Worktree[]
|
||||
visibleFolderWorkspaces: readonly FolderWorkspace[]
|
||||
currentSidebarWorktreeId: string | null
|
||||
currentSidebarExecutionHostId: ExecutionHostId | null
|
||||
worktreeMap: Map<string, Worktree>
|
||||
@@ -31,7 +45,8 @@ export function useSidebarRevealRequests(args: {
|
||||
const {
|
||||
groupBy,
|
||||
renderedSidebarRowKeys,
|
||||
renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces,
|
||||
currentSidebarWorktreeId,
|
||||
currentSidebarExecutionHostId,
|
||||
worktreeMap,
|
||||
@@ -106,11 +121,11 @@ export function useSidebarRevealRequests(args: {
|
||||
if (!activeWorktree || activeWorktree.isArchived) {
|
||||
return
|
||||
}
|
||||
const currentIdentity = composeWorktreeHostIdentity(
|
||||
currentSidebarExecutionHostId ?? undefined,
|
||||
currentSidebarWorktreeId
|
||||
)
|
||||
if (hasFilters && !renderedWorktreeIdentities.includes(currentIdentity)) {
|
||||
// Collapsed groups hide rows without excluding their workspaces from the filter results.
|
||||
if (
|
||||
hasFilters &&
|
||||
!workspacePassesFilters(activeWorktree, visibleWorktrees, visibleFolderWorkspaces)
|
||||
) {
|
||||
if (confirmationPending.current) {
|
||||
return
|
||||
}
|
||||
@@ -141,7 +156,14 @@ export function useSidebarRevealRequests(args: {
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (latest.hasFilters && !latest.renderedWorktreeIdentities.includes(currentIdentity)) {
|
||||
if (
|
||||
latest.hasFilters &&
|
||||
!workspacePassesFilters(
|
||||
activeWorktree,
|
||||
latest.visibleWorktrees,
|
||||
latest.visibleFolderWorkspaces
|
||||
)
|
||||
) {
|
||||
latest.clearFilters()
|
||||
}
|
||||
}
|
||||
@@ -159,7 +181,8 @@ export function useSidebarRevealRequests(args: {
|
||||
currentSidebarExecutionHostId,
|
||||
folderWorkspaces,
|
||||
revealSidebarRow,
|
||||
renderedWorktreeIdentities,
|
||||
visibleWorktrees,
|
||||
visibleFolderWorkspaces,
|
||||
revealWorktreeInSidebar,
|
||||
worktreeMap,
|
||||
worktrees
|
||||
|
||||
@@ -16968,6 +16968,11 @@
|
||||
"empty": "No users found"
|
||||
},
|
||||
"native-chat": {
|
||||
"notices": {
|
||||
"compaction": "Context compacted",
|
||||
"details": "Details",
|
||||
"plan": "Plan"
|
||||
},
|
||||
"composer": {
|
||||
"imageUnsupported": "Image paste is not supported for this agent.",
|
||||
"send": "Send",
|
||||
|
||||
@@ -190,6 +190,34 @@ describe('forward tolerance', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('optional notice metadata', () => {
|
||||
it.each([
|
||||
{},
|
||||
{ presentation: 'compaction' },
|
||||
{ presentation: 'plan-document' },
|
||||
{ tone: 'warning' },
|
||||
{ tone: 'error' },
|
||||
{ tone: 'notice' },
|
||||
{ presentation: 'future-presentation', tone: 'future-tone' }
|
||||
])('admits existing status and text kinds with %j', (metadata) => {
|
||||
expect(
|
||||
isAdmissibleAgentJournalItemBody({ kind: 'status', text: 'Readable fallback', ...metadata })
|
||||
).toBe(true)
|
||||
expect(
|
||||
isAdmissibleAgentJournalItemBody({
|
||||
kind: 'message',
|
||||
role: 'system',
|
||||
blocks: [{ type: 'text', text: 'Readable fallback', ...metadata }]
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
it.each([{ tone: false }, { presentation: {} }])('rejects malformed metadata: %j', (metadata) => {
|
||||
expect(isAdmissibleAgentJournalItemBody({ kind: 'status', text: 'Text', ...metadata })).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('optional tool annotations', () => {
|
||||
const body = { kind: 'tool-call', name: 'shell', input: null, state: 'completed' }
|
||||
it('admits old rows and rows with optional annotations without a new kind', () => {
|
||||
|
||||
@@ -68,6 +68,8 @@ const Block = z.union([
|
||||
z.object({
|
||||
type: z.literal('text'),
|
||||
text: z.string(),
|
||||
presentation: z.string().optional(),
|
||||
tone: z.string().optional(),
|
||||
providerFrame: ProviderFrame.optional()
|
||||
}),
|
||||
// `input: undefined` loses its key under JSON.stringify, so a persisted
|
||||
@@ -160,6 +162,8 @@ export const AgentJournalItemBodySchema = z.discriminatedUnion('kind', [
|
||||
z.object({
|
||||
kind: z.literal('status'),
|
||||
text: z.string(),
|
||||
presentation: z.string().optional(),
|
||||
tone: z.string().optional(),
|
||||
turnLifecycle: z.object({ turnId: z.string(), state: z.string().min(1) }).optional(),
|
||||
providerFrame: ProviderFrame.optional()
|
||||
})
|
||||
|
||||
@@ -148,6 +148,9 @@ export type AgentJournalQuestionItem = {
|
||||
export type AgentJournalStatusItem = {
|
||||
kind: 'status'
|
||||
text: string
|
||||
/** Optional display hints; unknown values retain the ordinary text fallback. */
|
||||
presentation?: string
|
||||
tone?: string
|
||||
/** Durable root-turn lifecycle used by clients to expose cancellation only
|
||||
* while the provider can still accept it. */
|
||||
turnLifecycle?: { turnId: string; state: 'running' | 'completed' }
|
||||
|
||||
@@ -32,6 +32,9 @@ export type NativeChatRole = (typeof NATIVE_CHAT_ROLES)[number]
|
||||
export type NativeChatTextBlock = {
|
||||
type: 'text'
|
||||
text: string
|
||||
/** Optional journal display hints; readers narrow only the values they know. */
|
||||
presentation?: string
|
||||
tone?: string
|
||||
/** Optional structured detail for an otherwise ordinary fallback line. */
|
||||
providerFrame?: {
|
||||
provider: string
|
||||
|
||||
@@ -284,6 +284,29 @@ describe('structured agent session status projection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('notice projection for desktop and mobile consumers', () => {
|
||||
it.each([
|
||||
{ presentation: 'compaction' },
|
||||
{ presentation: 'plan-document' },
|
||||
{ tone: 'warning' },
|
||||
{ tone: 'error' },
|
||||
{ tone: 'notice' },
|
||||
{ presentation: 'future-presentation', tone: 'future-tone' }
|
||||
])('preserves readable text alongside optional metadata: %j', (metadata) => {
|
||||
const projected = projectStructuredItemToNativeChat(
|
||||
item('notice', 1, {
|
||||
kind: 'status',
|
||||
text: 'A readable document or notice',
|
||||
...metadata
|
||||
})
|
||||
)
|
||||
expect(projected).toMatchObject({
|
||||
role: 'system',
|
||||
blocks: [{ type: 'text', text: 'A readable document or notice', ...metadata }]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves optional tool annotations for desktop and mobile projection', () => {
|
||||
const metadata = {
|
||||
exitCode: 127,
|
||||
|
||||
@@ -113,6 +113,8 @@ function itemBlocks(item: AgentJournalRenderItem): {
|
||||
{
|
||||
type: 'text',
|
||||
text: body.text,
|
||||
...(body.presentation !== undefined ? { presentation: body.presentation } : {}),
|
||||
...(body.tone !== undefined ? { tone: body.tone } : {}),
|
||||
...(body.providerFrame ? { providerFrame: body.providerFrame } : {})
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user