@thacio/auditaria 0.28.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/docs/CONTRIBUTING.md +7 -6
- package/bundle/docs/changelogs/index.md +20 -0
- package/bundle/docs/changelogs/latest.md +294 -426
- package/bundle/docs/changelogs/preview.md +343 -283
- package/bundle/docs/cli/cli-reference.md +23 -23
- package/bundle/docs/cli/commands.md +2 -0
- package/bundle/docs/cli/enterprise.md +18 -15
- package/bundle/docs/cli/keyboard-shortcuts.md +17 -8
- package/bundle/docs/cli/plan-mode.md +92 -12
- package/bundle/docs/cli/sandbox.md +3 -2
- package/bundle/docs/cli/settings.md +28 -19
- package/bundle/docs/cli/telemetry.md +18 -4
- package/bundle/docs/core/policy-engine.md +13 -3
- package/bundle/docs/extensions/reference.md +0 -3
- package/bundle/docs/get-started/configuration-v1.md +5 -3
- package/bundle/docs/get-started/configuration.md +85 -41
- package/bundle/docs/tools/ask-user.md +95 -0
- package/bundle/docs/tools/index.md +3 -0
- package/bundle/docs/tools/mcp-server.md +1 -12
- package/bundle/docs/tools/planning.md +55 -0
- package/bundle/docs/tools/shell.md +7 -6
- package/bundle/gemini.js +30500 -18105
- package/bundle/mcp-bridge.js +2 -2
- package/bundle/policies/plan.toml +3 -3
- package/bundle/policies/yolo.toml +13 -2
- package/bundle/{sandbox-macos-restrictive-closed.sb → sandbox-macos-strict-open.sb} +42 -4
- package/bundle/sandbox-macos-strict-proxied.sb +133 -0
- package/bundle/web-client/client.js +96 -3
- package/bundle/web-client/components/DiffContextMenu.js +252 -0
- package/bundle/web-client/components/DiffModal.js +85 -38
- package/bundle/web-client/components/EditorPanel.js +12 -2
- package/bundle/web-client/managers/EditorManager.js +32 -0
- package/bundle/web-client/managers/InputHistoryManager.js +139 -0
- package/bundle/web-client/managers/WebSocketManager.js +19 -4
- package/bundle/web-client/styles/editor-panel.css +32 -24
- package/bundle/web-client/styles/overhaul.css +30 -0
- package/package.json +4 -4
- package/bundle/sandbox-macos-permissive-closed.sb +0 -32
package/bundle/mcp-bridge.js
CHANGED
|
@@ -17723,10 +17723,10 @@ function isTerminal(status) {
|
|
|
17723
17723
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
17724
17724
|
}
|
|
17725
17725
|
|
|
17726
|
-
//
|
|
17726
|
+
// node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
17727
17727
|
var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
17728
17728
|
|
|
17729
|
-
//
|
|
17729
|
+
// node_modules/zod-to-json-schema/dist/esm/parsers/string.js
|
|
17730
17730
|
var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
|
|
17731
17731
|
|
|
17732
17732
|
// packages/core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
decision = "deny"
|
|
32
32
|
priority = 60
|
|
33
33
|
modes = ["plan"]
|
|
34
|
-
deny_message = "You are in Plan Mode -
|
|
34
|
+
deny_message = "You are in Plan Mode with access to read-only tools. Execution of scripts (including those from skills) is blocked."
|
|
35
35
|
|
|
36
36
|
# Explicitly Allow Read-Only Tools in Plan mode.
|
|
37
37
|
|
|
38
38
|
[[rule]]
|
|
39
|
-
toolName = ["glob", "grep_search", "list_directory", "read_file", "google_web_search"]
|
|
39
|
+
toolName = ["glob", "grep_search", "list_directory", "read_file", "google_web_search", "activate_skill"]
|
|
40
40
|
decision = "allow"
|
|
41
41
|
priority = 70
|
|
42
42
|
modes = ["plan"]
|
|
@@ -53,4 +53,4 @@ toolName = ["write_file", "replace"]
|
|
|
53
53
|
decision = "allow"
|
|
54
54
|
priority = 70
|
|
55
55
|
modes = ["plan"]
|
|
56
|
-
argsPattern = "\"file_path\":\"[^\"]+/\\.gemini/tmp/[a-zA-Z0-9_-]+/plans/[a-zA-Z0-9_-]+\\.md\""
|
|
56
|
+
argsPattern = "\"file_path\":\"[^\"]+/\\.gemini/tmp/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/plans/[a-zA-Z0-9_-]+\\.md\""
|
|
@@ -23,10 +23,21 @@
|
|
|
23
23
|
# 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
|
|
24
24
|
# 15: Auto-edit tool override (becomes 1.015 in default tier)
|
|
25
25
|
# 50: Read-only tools (becomes 1.050 in default tier)
|
|
26
|
-
#
|
|
26
|
+
# 998: YOLO mode allow-all (becomes 1.998 in default tier)
|
|
27
|
+
# 999: Ask-user tool (becomes 1.999 in default tier)
|
|
27
28
|
|
|
29
|
+
# Ask-user tool always requires user interaction, even in YOLO mode.
|
|
30
|
+
# This ensures the model can gather user preferences/decisions when needed.
|
|
31
|
+
# Note: In non-interactive mode, this decision is converted to DENY by the policy engine.
|
|
28
32
|
[[rule]]
|
|
29
|
-
|
|
33
|
+
toolName = "ask_user"
|
|
34
|
+
decision = "ask_user"
|
|
30
35
|
priority = 999
|
|
31
36
|
modes = ["yolo"]
|
|
37
|
+
|
|
38
|
+
# Allow everything else in YOLO mode
|
|
39
|
+
[[rule]]
|
|
40
|
+
decision = "allow"
|
|
41
|
+
priority = 998
|
|
42
|
+
modes = ["yolo"]
|
|
32
43
|
allow_redirection = true
|
|
@@ -3,8 +3,43 @@
|
|
|
3
3
|
;; deny everything by default
|
|
4
4
|
(deny default)
|
|
5
5
|
|
|
6
|
-
;; allow reading
|
|
7
|
-
(allow file-read*
|
|
6
|
+
;; allow reading ONLY from working directory, system paths, and essential user paths
|
|
7
|
+
(allow file-read*
|
|
8
|
+
(literal "/")
|
|
9
|
+
(subpath (param "TARGET_DIR"))
|
|
10
|
+
(subpath (param "TMP_DIR"))
|
|
11
|
+
(subpath (param "CACHE_DIR"))
|
|
12
|
+
;; Only allow reading essential dotfiles/directories under HOME, not the entire HOME
|
|
13
|
+
(subpath (string-append (param "HOME_DIR") "/.gemini"))
|
|
14
|
+
(subpath (string-append (param "HOME_DIR") "/.npm"))
|
|
15
|
+
(subpath (string-append (param "HOME_DIR") "/.cache"))
|
|
16
|
+
(literal (string-append (param "HOME_DIR") "/.gitconfig"))
|
|
17
|
+
(subpath (string-append (param "HOME_DIR") "/.nvm"))
|
|
18
|
+
(subpath (string-append (param "HOME_DIR") "/.fnm"))
|
|
19
|
+
(subpath (string-append (param "HOME_DIR") "/.node"))
|
|
20
|
+
(subpath (string-append (param "HOME_DIR") "/.config"))
|
|
21
|
+
;; Allow reads from included directories
|
|
22
|
+
(subpath (param "INCLUDE_DIR_0"))
|
|
23
|
+
(subpath (param "INCLUDE_DIR_1"))
|
|
24
|
+
(subpath (param "INCLUDE_DIR_2"))
|
|
25
|
+
(subpath (param "INCLUDE_DIR_3"))
|
|
26
|
+
(subpath (param "INCLUDE_DIR_4"))
|
|
27
|
+
;; System paths required for Node.js, shell, and common tools
|
|
28
|
+
(subpath "/usr")
|
|
29
|
+
(subpath "/bin")
|
|
30
|
+
(subpath "/sbin")
|
|
31
|
+
(subpath "/Library")
|
|
32
|
+
(subpath "/System")
|
|
33
|
+
(subpath "/private")
|
|
34
|
+
(subpath "/dev")
|
|
35
|
+
(subpath "/etc")
|
|
36
|
+
(subpath "/opt")
|
|
37
|
+
(subpath "/Applications")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
;; allow path traversal everywhere (metadata only: stat/lstat, NOT readdir or file content)
|
|
41
|
+
;; this is needed for Node.js module resolution to traverse intermediate directories
|
|
42
|
+
(allow file-read-metadata)
|
|
8
43
|
|
|
9
44
|
;; allow exec/fork (children inherit policy)
|
|
10
45
|
(allow process-exec)
|
|
@@ -70,7 +105,7 @@
|
|
|
70
105
|
(subpath (string-append (param "HOME_DIR") "/.gemini"))
|
|
71
106
|
(subpath (string-append (param "HOME_DIR") "/.npm"))
|
|
72
107
|
(subpath (string-append (param "HOME_DIR") "/.cache"))
|
|
73
|
-
(
|
|
108
|
+
(literal (string-append (param "HOME_DIR") "/.gitconfig"))
|
|
74
109
|
;; Allow writes to included directories from --include-directories
|
|
75
110
|
(subpath (param "INCLUDE_DIR_0"))
|
|
76
111
|
(subpath (param "INCLUDE_DIR_1"))
|
|
@@ -90,4 +125,7 @@
|
|
|
90
125
|
(allow file-ioctl (regex #"^/dev/tty.*"))
|
|
91
126
|
|
|
92
127
|
;; allow inbound network traffic on debugger port
|
|
93
|
-
(allow network-inbound (local ip "localhost:9229"))
|
|
128
|
+
(allow network-inbound (local ip "localhost:9229"))
|
|
129
|
+
|
|
130
|
+
;; allow all outbound network traffic
|
|
131
|
+
(allow network-outbound)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
(version 1)
|
|
2
|
+
|
|
3
|
+
;; deny everything by default
|
|
4
|
+
(deny default)
|
|
5
|
+
|
|
6
|
+
;; allow reading ONLY from working directory, system paths, and essential user paths
|
|
7
|
+
(allow file-read*
|
|
8
|
+
(literal "/")
|
|
9
|
+
(subpath (param "TARGET_DIR"))
|
|
10
|
+
(subpath (param "TMP_DIR"))
|
|
11
|
+
(subpath (param "CACHE_DIR"))
|
|
12
|
+
;; Only allow reading essential dotfiles/directories under HOME, not the entire HOME
|
|
13
|
+
(subpath (string-append (param "HOME_DIR") "/.gemini"))
|
|
14
|
+
(subpath (string-append (param "HOME_DIR") "/.npm"))
|
|
15
|
+
(subpath (string-append (param "HOME_DIR") "/.cache"))
|
|
16
|
+
(literal (string-append (param "HOME_DIR") "/.gitconfig"))
|
|
17
|
+
(subpath (string-append (param "HOME_DIR") "/.nvm"))
|
|
18
|
+
(subpath (string-append (param "HOME_DIR") "/.fnm"))
|
|
19
|
+
(subpath (string-append (param "HOME_DIR") "/.node"))
|
|
20
|
+
(subpath (string-append (param "HOME_DIR") "/.config"))
|
|
21
|
+
;; Allow reads from included directories
|
|
22
|
+
(subpath (param "INCLUDE_DIR_0"))
|
|
23
|
+
(subpath (param "INCLUDE_DIR_1"))
|
|
24
|
+
(subpath (param "INCLUDE_DIR_2"))
|
|
25
|
+
(subpath (param "INCLUDE_DIR_3"))
|
|
26
|
+
(subpath (param "INCLUDE_DIR_4"))
|
|
27
|
+
;; System paths required for Node.js, shell, and common tools
|
|
28
|
+
(subpath "/usr")
|
|
29
|
+
(subpath "/bin")
|
|
30
|
+
(subpath "/sbin")
|
|
31
|
+
(subpath "/Library")
|
|
32
|
+
(subpath "/System")
|
|
33
|
+
(subpath "/private")
|
|
34
|
+
(subpath "/dev")
|
|
35
|
+
(subpath "/etc")
|
|
36
|
+
(subpath "/opt")
|
|
37
|
+
(subpath "/Applications")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
;; allow path traversal everywhere (metadata only: stat/lstat, NOT readdir or file content)
|
|
41
|
+
;; this is needed for Node.js module resolution to traverse intermediate directories
|
|
42
|
+
(allow file-read-metadata)
|
|
43
|
+
|
|
44
|
+
;; allow exec/fork (children inherit policy)
|
|
45
|
+
(allow process-exec)
|
|
46
|
+
(allow process-fork)
|
|
47
|
+
|
|
48
|
+
;; allow signals to self, e.g. SIGPIPE on write to closed pipe
|
|
49
|
+
(allow signal (target self))
|
|
50
|
+
|
|
51
|
+
;; allow read access to specific information about system
|
|
52
|
+
;; from https://source.chromium.org/chromium/chromium/src/+/main:sandbox/policy/mac/common.sb;l=273-319;drc=7b3962fe2e5fc9e2ee58000dc8fbf3429d84d3bd
|
|
53
|
+
(allow sysctl-read
|
|
54
|
+
(sysctl-name "hw.activecpu")
|
|
55
|
+
(sysctl-name "hw.busfrequency_compat")
|
|
56
|
+
(sysctl-name "hw.byteorder")
|
|
57
|
+
(sysctl-name "hw.cacheconfig")
|
|
58
|
+
(sysctl-name "hw.cachelinesize_compat")
|
|
59
|
+
(sysctl-name "hw.cpufamily")
|
|
60
|
+
(sysctl-name "hw.cpufrequency_compat")
|
|
61
|
+
(sysctl-name "hw.cputype")
|
|
62
|
+
(sysctl-name "hw.l1dcachesize_compat")
|
|
63
|
+
(sysctl-name "hw.l1icachesize_compat")
|
|
64
|
+
(sysctl-name "hw.l2cachesize_compat")
|
|
65
|
+
(sysctl-name "hw.l3cachesize_compat")
|
|
66
|
+
(sysctl-name "hw.logicalcpu_max")
|
|
67
|
+
(sysctl-name "hw.machine")
|
|
68
|
+
(sysctl-name "hw.ncpu")
|
|
69
|
+
(sysctl-name "hw.nperflevels")
|
|
70
|
+
(sysctl-name "hw.optional.arm.FEAT_BF16")
|
|
71
|
+
(sysctl-name "hw.optional.arm.FEAT_DotProd")
|
|
72
|
+
(sysctl-name "hw.optional.arm.FEAT_FCMA")
|
|
73
|
+
(sysctl-name "hw.optional.arm.FEAT_FHM")
|
|
74
|
+
(sysctl-name "hw.optional.arm.FEAT_FP16")
|
|
75
|
+
(sysctl-name "hw.optional.arm.FEAT_I8MM")
|
|
76
|
+
(sysctl-name "hw.optional.arm.FEAT_JSCVT")
|
|
77
|
+
(sysctl-name "hw.optional.arm.FEAT_LSE")
|
|
78
|
+
(sysctl-name "hw.optional.arm.FEAT_RDM")
|
|
79
|
+
(sysctl-name "hw.optional.arm.FEAT_SHA512")
|
|
80
|
+
(sysctl-name "hw.optional.armv8_2_sha512")
|
|
81
|
+
(sysctl-name "hw.packages")
|
|
82
|
+
(sysctl-name "hw.pagesize_compat")
|
|
83
|
+
(sysctl-name "hw.physicalcpu_max")
|
|
84
|
+
(sysctl-name "hw.tbfrequency_compat")
|
|
85
|
+
(sysctl-name "hw.vectorunit")
|
|
86
|
+
(sysctl-name "kern.hostname")
|
|
87
|
+
(sysctl-name "kern.maxfilesperproc")
|
|
88
|
+
(sysctl-name "kern.osproductversion")
|
|
89
|
+
(sysctl-name "kern.osrelease")
|
|
90
|
+
(sysctl-name "kern.ostype")
|
|
91
|
+
(sysctl-name "kern.osvariant_status")
|
|
92
|
+
(sysctl-name "kern.osversion")
|
|
93
|
+
(sysctl-name "kern.secure_kernel")
|
|
94
|
+
(sysctl-name "kern.usrstack64")
|
|
95
|
+
(sysctl-name "kern.version")
|
|
96
|
+
(sysctl-name "sysctl.proc_cputype")
|
|
97
|
+
(sysctl-name-prefix "hw.perflevel")
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
;; allow writes to specific paths
|
|
101
|
+
(allow file-write*
|
|
102
|
+
(subpath (param "TARGET_DIR"))
|
|
103
|
+
(subpath (param "TMP_DIR"))
|
|
104
|
+
(subpath (param "CACHE_DIR"))
|
|
105
|
+
(subpath (string-append (param "HOME_DIR") "/.gemini"))
|
|
106
|
+
(subpath (string-append (param "HOME_DIR") "/.npm"))
|
|
107
|
+
(subpath (string-append (param "HOME_DIR") "/.cache"))
|
|
108
|
+
(literal (string-append (param "HOME_DIR") "/.gitconfig"))
|
|
109
|
+
;; Allow writes to included directories from --include-directories
|
|
110
|
+
(subpath (param "INCLUDE_DIR_0"))
|
|
111
|
+
(subpath (param "INCLUDE_DIR_1"))
|
|
112
|
+
(subpath (param "INCLUDE_DIR_2"))
|
|
113
|
+
(subpath (param "INCLUDE_DIR_3"))
|
|
114
|
+
(subpath (param "INCLUDE_DIR_4"))
|
|
115
|
+
(literal "/dev/stdout")
|
|
116
|
+
(literal "/dev/stderr")
|
|
117
|
+
(literal "/dev/null")
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
;; allow communication with sysmond for process listing (e.g. for pgrep)
|
|
121
|
+
(allow mach-lookup (global-name "com.apple.sysmond"))
|
|
122
|
+
|
|
123
|
+
;; enable terminal access required by ink
|
|
124
|
+
;; fixes setRawMode EPERM failure (at node:tty:81:24)
|
|
125
|
+
(allow file-ioctl (regex #"^/dev/tty.*"))
|
|
126
|
+
|
|
127
|
+
;; allow inbound network traffic on debugger port
|
|
128
|
+
(allow network-inbound (local ip "localhost:9229"))
|
|
129
|
+
|
|
130
|
+
;; allow outbound network traffic through proxy on localhost:8877
|
|
131
|
+
;; set `GEMINI_SANDBOX_PROXY_COMMAND=<command>` to run proxy alongside sandbox
|
|
132
|
+
;; proxy must listen on :::8877 (see docs/examples/proxy-script.md)
|
|
133
|
+
(allow network-outbound (remote tcp "localhost:8877"))
|
|
@@ -17,6 +17,7 @@ import { attachmentCacheManager } from './managers/AttachmentCacheManager.js';
|
|
|
17
17
|
import { ttsManager } from './providers/tts/TTSManager.js';
|
|
18
18
|
import { ConfirmationQueue } from './confirmation-queue.js';
|
|
19
19
|
import { SlashAutocompleteManager } from './managers/SlashAutocompleteManager.js';
|
|
20
|
+
import { InputHistoryManager } from './managers/InputHistoryManager.js';
|
|
20
21
|
import { themeManager } from './utils/theme-manager.js';
|
|
21
22
|
import { layoutManager } from './utils/layout-manager.js';
|
|
22
23
|
import { showErrorToast, showInfoToast } from './components/Toast.js';
|
|
@@ -60,6 +61,9 @@ class AuditariaWebClient {
|
|
|
60
61
|
// Initialize slash command autocomplete (after UI init)
|
|
61
62
|
this.slashAutocomplete = null; // Will be initialized after UI elements are ready
|
|
62
63
|
|
|
64
|
+
// Initialize input history (ArrowUp/Down navigation)
|
|
65
|
+
this.inputHistory = new InputHistoryManager();
|
|
66
|
+
|
|
63
67
|
// State properties
|
|
64
68
|
this.hasFooterData = false;
|
|
65
69
|
this.latestFooterData = null;
|
|
@@ -188,6 +192,10 @@ class AuditariaWebClient {
|
|
|
188
192
|
|
|
189
193
|
this.wsManager.addEventListener('history_item', (e) => {
|
|
190
194
|
this.messageManager.addHistoryItem(e.detail);
|
|
195
|
+
// Track user messages for input history (captures CLI-side inputs too)
|
|
196
|
+
if (e.detail.type === 'user' && e.detail.text) {
|
|
197
|
+
this.inputHistory.addInput(e.detail.text);
|
|
198
|
+
}
|
|
191
199
|
});
|
|
192
200
|
|
|
193
201
|
// WEB_INTERFACE: Unified response state replaces fragmented pending_item
|
|
@@ -236,6 +244,20 @@ class AuditariaWebClient {
|
|
|
236
244
|
|
|
237
245
|
this.wsManager.addEventListener('history_sync', (e) => {
|
|
238
246
|
this.messageManager.loadHistoryItems(e.detail.history);
|
|
247
|
+
// Fallback: populate input history from conversation history
|
|
248
|
+
// (overridden by input_history_sync if the CLI sends it)
|
|
249
|
+
if (this.inputHistory.length === 0) {
|
|
250
|
+
const userMessages = (e.detail.history || [])
|
|
251
|
+
.filter(item => item.type === 'user' && item.text)
|
|
252
|
+
.map(item => item.text.trim())
|
|
253
|
+
.filter(Boolean);
|
|
254
|
+
this.inputHistory.loadHistory(userMessages);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Input history sync from CLI (includes past sessions — shared with CLI's ArrowUp/Down)
|
|
259
|
+
this.wsManager.addEventListener('input_history_sync', (e) => {
|
|
260
|
+
this.inputHistory.loadHistory(e.detail.history || []);
|
|
239
261
|
});
|
|
240
262
|
|
|
241
263
|
this.wsManager.addEventListener('loading_state', (e) => {
|
|
@@ -309,6 +331,32 @@ class AuditariaWebClient {
|
|
|
309
331
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
310
332
|
event.preventDefault();
|
|
311
333
|
this.sendMessage();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ArrowUp/Down — input history navigation
|
|
338
|
+
if (event.key === 'ArrowUp' && this._cursorOnFirstLine()) {
|
|
339
|
+
const text = this.inputHistory.navigateUp(this.messageInput.value);
|
|
340
|
+
if (text !== null) {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
this.messageInput.value = text;
|
|
343
|
+
this.autoResizeTextarea();
|
|
344
|
+
// Place cursor at end
|
|
345
|
+
this.messageInput.setSelectionRange(text.length, text.length);
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (event.key === 'ArrowDown' && this._cursorOnLastLine()) {
|
|
351
|
+
const text = this.inputHistory.navigateDown();
|
|
352
|
+
if (text !== null) {
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
this.messageInput.value = text;
|
|
355
|
+
this.autoResizeTextarea();
|
|
356
|
+
// Place cursor at end
|
|
357
|
+
this.messageInput.setSelectionRange(text.length, text.length);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
312
360
|
}
|
|
313
361
|
});
|
|
314
362
|
|
|
@@ -405,6 +453,8 @@ class AuditariaWebClient {
|
|
|
405
453
|
|
|
406
454
|
// Send message with attachments (but not for slash commands)
|
|
407
455
|
if (this.wsManager.sendUserMessage(message, isSlashCommand ? [] : this.attachments)) {
|
|
456
|
+
// Track input for ArrowUp/Down history
|
|
457
|
+
this.inputHistory.addInput(message);
|
|
408
458
|
this.messageInput.value = '';
|
|
409
459
|
// Only clear attachments if we're not sending a slash command
|
|
410
460
|
if (!isSlashCommand) {
|
|
@@ -1106,21 +1156,39 @@ class AuditariaWebClient {
|
|
|
1106
1156
|
const section = document.createElement('div');
|
|
1107
1157
|
section.className = 'web-footer-model-menu-section';
|
|
1108
1158
|
const isCodexGroup = group.id === 'codex';
|
|
1159
|
+
const isAvailable = group.available !== false; // AUDITARIA_PROVIDER_AVAILABILITY: Default to true for backwards compatibility
|
|
1109
1160
|
|
|
1110
1161
|
const title = document.createElement('div');
|
|
1111
1162
|
title.className = 'web-footer-model-menu-title';
|
|
1112
1163
|
title.textContent = group.label;
|
|
1113
1164
|
section.append(title);
|
|
1114
1165
|
|
|
1166
|
+
// AUDITARIA_PROVIDER_AVAILABILITY: Show install message for unavailable providers
|
|
1167
|
+
if (!isAvailable && group.installMessage) {
|
|
1168
|
+
const installMsg = document.createElement('div');
|
|
1169
|
+
installMsg.className = 'web-footer-model-menu-install-message';
|
|
1170
|
+
installMsg.textContent = group.installMessage;
|
|
1171
|
+
section.append(installMsg);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1115
1174
|
for (const option of group.options || []) {
|
|
1116
1175
|
const item = document.createElement('div');
|
|
1117
1176
|
item.className = 'web-footer-model-menu-item';
|
|
1118
|
-
|
|
1119
|
-
|
|
1177
|
+
|
|
1178
|
+
// AUDITARIA_PROVIDER_AVAILABILITY: Disable unavailable providers
|
|
1179
|
+
if (!isAvailable) {
|
|
1180
|
+
item.classList.add('is-disabled');
|
|
1181
|
+
item.setAttribute('aria-disabled', 'true');
|
|
1182
|
+
item.title = `${option.label} (not available - install required)`;
|
|
1183
|
+
} else {
|
|
1184
|
+
item.setAttribute('role', 'button');
|
|
1185
|
+
item.setAttribute('tabindex', '0');
|
|
1186
|
+
item.title = option.description || option.label;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1120
1189
|
if (option.selection === this.modelMenuData.activeSelection) {
|
|
1121
1190
|
item.classList.add('is-active');
|
|
1122
1191
|
}
|
|
1123
|
-
item.title = option.description || option.label;
|
|
1124
1192
|
if (isCodexGroup) {
|
|
1125
1193
|
item.classList.add('web-footer-model-menu-item-codex');
|
|
1126
1194
|
}
|
|
@@ -1231,6 +1299,11 @@ class AuditariaWebClient {
|
|
|
1231
1299
|
}
|
|
1232
1300
|
|
|
1233
1301
|
item.addEventListener('click', () => {
|
|
1302
|
+
// AUDITARIA_PROVIDER_AVAILABILITY: Prevent selection of unavailable providers
|
|
1303
|
+
if (!isAvailable) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1234
1307
|
if (isCodexGroup) {
|
|
1235
1308
|
const state = this.getCodexEffortStateForSelection(option.selection);
|
|
1236
1309
|
this.wsManager.sendModelSelection(
|
|
@@ -1492,6 +1565,26 @@ class AuditariaWebClient {
|
|
|
1492
1565
|
this.messageInput.style.height = 'auto';
|
|
1493
1566
|
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
|
|
1494
1567
|
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* Returns true when the caret is on the first line of the textarea
|
|
1571
|
+
* (or the textarea is empty), so ArrowUp should navigate history.
|
|
1572
|
+
*/
|
|
1573
|
+
_cursorOnFirstLine() {
|
|
1574
|
+
const { value, selectionStart } = this.messageInput;
|
|
1575
|
+
// On first line if no newline before the cursor
|
|
1576
|
+
return value.substring(0, selectionStart).indexOf('\n') === -1;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Returns true when the caret is on the last line of the textarea
|
|
1581
|
+
* (or the textarea is empty), so ArrowDown should navigate history.
|
|
1582
|
+
*/
|
|
1583
|
+
_cursorOnLastLine() {
|
|
1584
|
+
const { value, selectionStart } = this.messageInput;
|
|
1585
|
+
// On last line if no newline after the cursor
|
|
1586
|
+
return value.substring(selectionStart).indexOf('\n') === -1;
|
|
1587
|
+
}
|
|
1495
1588
|
|
|
1496
1589
|
handleConfirmationResponse(callId, outcome, payload) {
|
|
1497
1590
|
this.wsManager.sendConfirmationResponse(callId, outcome, payload);
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Thacio
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ContextMenu } from './ContextMenu.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* DiffContextMenu — right-click context menu for diff editors.
|
|
11
|
+
*
|
|
12
|
+
* Attaches to a Monaco diff editor's modified side.
|
|
13
|
+
* When the cursor is on a changed hunk:
|
|
14
|
+
* - Shows "Revert This Change", "Copy Original", "Revert All Changes"
|
|
15
|
+
* When the cursor is NOT on a hunk:
|
|
16
|
+
* - Falls through to Monaco's native context menu
|
|
17
|
+
*
|
|
18
|
+
* All reverts use executeEdits() so Ctrl+Z works.
|
|
19
|
+
*/
|
|
20
|
+
export class DiffContextMenu {
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} diffEditor - Monaco IDiffEditor instance
|
|
23
|
+
* @param {object} monaco - Monaco namespace (for Range, etc.)
|
|
24
|
+
*/
|
|
25
|
+
constructor(diffEditor, monaco) {
|
|
26
|
+
this.diffEditor = diffEditor;
|
|
27
|
+
this.monaco = monaco;
|
|
28
|
+
this.contextMenu = new ContextMenu();
|
|
29
|
+
this.disposables = [];
|
|
30
|
+
|
|
31
|
+
this.attach();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attach the context menu listener to the modified (right) editor
|
|
36
|
+
*/
|
|
37
|
+
attach() {
|
|
38
|
+
const modifiedEditor = this.diffEditor.getModifiedEditor();
|
|
39
|
+
|
|
40
|
+
const disposable = modifiedEditor.onContextMenu((e) => {
|
|
41
|
+
const position = e.target?.position;
|
|
42
|
+
if (!position) return;
|
|
43
|
+
|
|
44
|
+
const hunk = this.findHunkAtLine(position.lineNumber);
|
|
45
|
+
if (!hunk) {
|
|
46
|
+
// Not on a hunk — let Monaco's native context menu handle it
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// On a changed hunk — suppress Monaco's menu and show ours
|
|
51
|
+
e.event.preventDefault();
|
|
52
|
+
e.event.stopPropagation();
|
|
53
|
+
|
|
54
|
+
const items = this.buildMenuItems(hunk);
|
|
55
|
+
this.contextMenu.show(
|
|
56
|
+
e.event.posx ?? e.event.clientX ?? e.event.browserEvent?.clientX ?? 0,
|
|
57
|
+
e.event.posy ?? e.event.clientY ?? e.event.browserEvent?.clientY ?? 0,
|
|
58
|
+
items
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.disposables.push(disposable);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Find the diff hunk containing the given line number (in the modified editor)
|
|
67
|
+
* @param {number} lineNumber
|
|
68
|
+
* @returns {object|null} The matching ILineChange or null
|
|
69
|
+
*/
|
|
70
|
+
findHunkAtLine(lineNumber) {
|
|
71
|
+
const changes = this.diffEditor.getLineChanges();
|
|
72
|
+
if (!changes) return null;
|
|
73
|
+
|
|
74
|
+
for (const change of changes) {
|
|
75
|
+
// Modification or insertion: modifiedEndLineNumber > 0
|
|
76
|
+
if (change.modifiedEndLineNumber > 0) {
|
|
77
|
+
if (lineNumber >= change.modifiedStartLineNumber &&
|
|
78
|
+
lineNumber <= change.modifiedEndLineNumber) {
|
|
79
|
+
return change;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Deletion: modifiedEndLineNumber === 0, lines only exist in original
|
|
83
|
+
// The deletion marker sits at modifiedStartLineNumber (the line after which content was deleted)
|
|
84
|
+
else if (change.modifiedEndLineNumber === 0) {
|
|
85
|
+
if (lineNumber === change.modifiedStartLineNumber ||
|
|
86
|
+
lineNumber === change.modifiedStartLineNumber + 1) {
|
|
87
|
+
return change;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build context menu items for a hunk
|
|
97
|
+
* @param {object} hunk - ILineChange
|
|
98
|
+
* @returns {Array} Menu items
|
|
99
|
+
*/
|
|
100
|
+
buildMenuItems(hunk) {
|
|
101
|
+
const items = [
|
|
102
|
+
{
|
|
103
|
+
label: 'Revert This Change',
|
|
104
|
+
icon: 'codicon codicon-discard',
|
|
105
|
+
action: () => this.revertHunk(hunk)
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: 'Copy Original',
|
|
109
|
+
icon: 'codicon codicon-copy',
|
|
110
|
+
action: () => this.copyOriginal(hunk)
|
|
111
|
+
},
|
|
112
|
+
{ separator: true },
|
|
113
|
+
{
|
|
114
|
+
label: 'Revert All Changes',
|
|
115
|
+
icon: 'codicon codicon-clear-all',
|
|
116
|
+
action: () => this.revertAll()
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return items;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Revert a single hunk: replace modified lines with original lines.
|
|
125
|
+
* Uses executeEdits for undo stack integration (Ctrl+Z works).
|
|
126
|
+
* @param {object} hunk - ILineChange
|
|
127
|
+
*/
|
|
128
|
+
revertHunk(hunk) {
|
|
129
|
+
const { Range } = this.monaco;
|
|
130
|
+
const modifiedEditor = this.diffEditor.getModifiedEditor();
|
|
131
|
+
const modifiedModel = modifiedEditor.getModel();
|
|
132
|
+
const originalModel = this.diffEditor.getOriginalEditor().getModel();
|
|
133
|
+
|
|
134
|
+
if (!modifiedModel || !originalModel) return;
|
|
135
|
+
|
|
136
|
+
let editRange;
|
|
137
|
+
let originalText;
|
|
138
|
+
|
|
139
|
+
if (hunk.originalEndLineNumber === 0) {
|
|
140
|
+
// Pure insertion — content only in modified, nothing in original.
|
|
141
|
+
// Delete the inserted lines from modified.
|
|
142
|
+
const lastCol = modifiedModel.getLineMaxColumn(hunk.modifiedEndLineNumber);
|
|
143
|
+
|
|
144
|
+
if (hunk.modifiedStartLineNumber === 1) {
|
|
145
|
+
// Insertion at very beginning — delete lines and the trailing newline
|
|
146
|
+
const nextLineExists = hunk.modifiedEndLineNumber < modifiedModel.getLineCount();
|
|
147
|
+
editRange = new Range(
|
|
148
|
+
1, 1,
|
|
149
|
+
nextLineExists ? hunk.modifiedEndLineNumber + 1 : hunk.modifiedEndLineNumber,
|
|
150
|
+
nextLineExists ? 1 : lastCol
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
// Delete from end of previous line (to eat the newline) through end of last inserted line
|
|
154
|
+
const prevLineLastCol = modifiedModel.getLineMaxColumn(hunk.modifiedStartLineNumber - 1);
|
|
155
|
+
editRange = new Range(
|
|
156
|
+
hunk.modifiedStartLineNumber - 1, prevLineLastCol,
|
|
157
|
+
hunk.modifiedEndLineNumber, lastCol
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
originalText = '';
|
|
162
|
+
} else if (hunk.modifiedEndLineNumber === 0) {
|
|
163
|
+
// Pure deletion — content only in original, nothing in modified.
|
|
164
|
+
// Insert the original lines into modified.
|
|
165
|
+
const origLastCol = originalModel.getLineMaxColumn(hunk.originalEndLineNumber);
|
|
166
|
+
originalText = originalModel.getValueInRange(
|
|
167
|
+
new Range(hunk.originalStartLineNumber, 1, hunk.originalEndLineNumber, origLastCol)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Insert after modifiedStartLineNumber (the deletion marker line)
|
|
171
|
+
const insertLine = hunk.modifiedStartLineNumber;
|
|
172
|
+
const insertCol = modifiedModel.getLineMaxColumn(insertLine);
|
|
173
|
+
editRange = new Range(insertLine, insertCol, insertLine, insertCol);
|
|
174
|
+
originalText = '\n' + originalText;
|
|
175
|
+
} else {
|
|
176
|
+
// Modification — replace modified lines with original lines
|
|
177
|
+
const origLastCol = originalModel.getLineMaxColumn(hunk.originalEndLineNumber);
|
|
178
|
+
originalText = originalModel.getValueInRange(
|
|
179
|
+
new Range(hunk.originalStartLineNumber, 1, hunk.originalEndLineNumber, origLastCol)
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const modLastCol = modifiedModel.getLineMaxColumn(hunk.modifiedEndLineNumber);
|
|
183
|
+
editRange = new Range(
|
|
184
|
+
hunk.modifiedStartLineNumber, 1,
|
|
185
|
+
hunk.modifiedEndLineNumber, modLastCol
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
modifiedEditor.executeEdits('revert-change', [{
|
|
190
|
+
range: editRange,
|
|
191
|
+
text: originalText
|
|
192
|
+
}]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Copy the original (left-side) content of a hunk to clipboard
|
|
197
|
+
* @param {object} hunk - ILineChange
|
|
198
|
+
*/
|
|
199
|
+
copyOriginal(hunk) {
|
|
200
|
+
const { Range } = this.monaco;
|
|
201
|
+
const originalModel = this.diffEditor.getOriginalEditor().getModel();
|
|
202
|
+
if (!originalModel) return;
|
|
203
|
+
|
|
204
|
+
if (hunk.originalEndLineNumber === 0) {
|
|
205
|
+
// Pure insertion — no original content to copy
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const origLastCol = originalModel.getLineMaxColumn(hunk.originalEndLineNumber);
|
|
210
|
+
const text = originalModel.getValueInRange(
|
|
211
|
+
new Range(hunk.originalStartLineNumber, 1, hunk.originalEndLineNumber, origLastCol)
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
215
|
+
console.warn('Failed to copy to clipboard:', err);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Revert all changes: replace entire modified content with original content
|
|
221
|
+
*/
|
|
222
|
+
revertAll() {
|
|
223
|
+
const modifiedEditor = this.diffEditor.getModifiedEditor();
|
|
224
|
+
const modifiedModel = modifiedEditor.getModel();
|
|
225
|
+
const originalModel = this.diffEditor.getOriginalEditor().getModel();
|
|
226
|
+
|
|
227
|
+
if (!modifiedModel || !originalModel) return;
|
|
228
|
+
|
|
229
|
+
const originalText = originalModel.getValue();
|
|
230
|
+
const fullRange = modifiedModel.getFullModelRange();
|
|
231
|
+
|
|
232
|
+
modifiedEditor.executeEdits('revert-all', [{
|
|
233
|
+
range: fullRange,
|
|
234
|
+
text: originalText
|
|
235
|
+
}]);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Dispose all listeners and the context menu
|
|
240
|
+
*/
|
|
241
|
+
dispose() {
|
|
242
|
+
for (const d of this.disposables) {
|
|
243
|
+
d.dispose();
|
|
244
|
+
}
|
|
245
|
+
this.disposables = [];
|
|
246
|
+
|
|
247
|
+
if (this.contextMenu) {
|
|
248
|
+
this.contextMenu.destroy();
|
|
249
|
+
this.contextMenu = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|