@youtyan/code-viewer 0.11.2 → 0.12.0
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/README.md +14 -1
- package/dist/code-viewer.js +1640 -750
- package/package.json +1 -1
- package/web/app.js +1399 -539
- package/web/style.css +172 -8
package/web/app.js
CHANGED
|
@@ -5,6 +5,311 @@
|
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// web-src/core/terminal-paste.ts
|
|
9
|
+
var PASTE_IMAGE_TYPES = {
|
|
10
|
+
"image/png": "png",
|
|
11
|
+
"image/jpeg": "jpg",
|
|
12
|
+
"image/gif": "gif",
|
|
13
|
+
"image/webp": "webp"
|
|
14
|
+
};
|
|
15
|
+
var MAX_PASTE_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
var MAX_PASTE_BODY_BYTES = Math.ceil(MAX_PASTE_IMAGE_BYTES * 1.4);
|
|
17
|
+
var SHIFT_ENTER_SEQUENCE = `${String.fromCharCode(27)}[200~${String.fromCharCode(10)}${String.fromCharCode(27)}[201~`;
|
|
18
|
+
function isShiftEnter(event) {
|
|
19
|
+
return event.key === "Enter" && event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// web-src/core/terminal-images.ts
|
|
23
|
+
var TERMINAL_IMAGE_EXTENSIONS = [
|
|
24
|
+
...new Set(Object.values(PASTE_IMAGE_TYPES)),
|
|
25
|
+
"jpeg"
|
|
26
|
+
];
|
|
27
|
+
var MAX_TERMINAL_IMAGE_PATHS = 64;
|
|
28
|
+
var MAX_TERMINAL_IMAGE_QUERY = 16;
|
|
29
|
+
var MAX_IMAGE_PATH_LENGTH = 1024;
|
|
30
|
+
var ESC = String.fromCharCode(27);
|
|
31
|
+
var BEL = String.fromCharCode(7);
|
|
32
|
+
var ANSI_RE = new RegExp(
|
|
33
|
+
`${ESC}\\[[0-9;?]*[ -/]*[@-~]|${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|${ESC}[@-Z\\\\-_]`,
|
|
34
|
+
"g"
|
|
35
|
+
);
|
|
36
|
+
function stripAnsi(text3) {
|
|
37
|
+
return text3.replace(ANSI_RE, "");
|
|
38
|
+
}
|
|
39
|
+
var PATH_CHAR = "[\\p{L}\\p{N}._~+@%/-]";
|
|
40
|
+
var NAME_CHAR = "[\\p{L}\\p{N}_~+@%-]";
|
|
41
|
+
var IMAGE_PATH_RE = new RegExp(
|
|
42
|
+
`${PATH_CHAR}*${NAME_CHAR}\\.(?:${TERMINAL_IMAGE_EXTENSIONS.join("|")})(?![\\p{L}\\p{N}])`,
|
|
43
|
+
"giu"
|
|
44
|
+
);
|
|
45
|
+
function looksLikeUrl(value) {
|
|
46
|
+
return value.startsWith("//") || value.includes("://");
|
|
47
|
+
}
|
|
48
|
+
function findImagePaths(text3, limit = MAX_TERMINAL_IMAGE_PATHS) {
|
|
49
|
+
const found = [];
|
|
50
|
+
const seen = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const match2 of text3.matchAll(IMAGE_PATH_RE)) {
|
|
52
|
+
const path = match2[0];
|
|
53
|
+
if (path.length > MAX_IMAGE_PATH_LENGTH) continue;
|
|
54
|
+
if (looksLikeUrl(path)) continue;
|
|
55
|
+
if (seen.has(path)) continue;
|
|
56
|
+
seen.add(path);
|
|
57
|
+
found.push(path);
|
|
58
|
+
if (found.length >= limit) break;
|
|
59
|
+
}
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
function joinWrappedLines(text3, width) {
|
|
63
|
+
if (!Number.isFinite(width) || width <= 0) return text3;
|
|
64
|
+
const lines = text3.split("\n");
|
|
65
|
+
const joined = [];
|
|
66
|
+
let current = null;
|
|
67
|
+
let previousLength = 0;
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (current === null) {
|
|
70
|
+
current = line;
|
|
71
|
+
} else if (previousLength >= width) {
|
|
72
|
+
current += line;
|
|
73
|
+
} else {
|
|
74
|
+
joined.push(current);
|
|
75
|
+
current = line;
|
|
76
|
+
}
|
|
77
|
+
previousLength = line.length;
|
|
78
|
+
}
|
|
79
|
+
if (current !== null) joined.push(current);
|
|
80
|
+
return joined.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function joinBrokenPathLines(text3) {
|
|
83
|
+
const lines = text3.split("\n");
|
|
84
|
+
const candidates = [];
|
|
85
|
+
for (let i2 = 0; i2 + 1 < lines.length; i2 += 1) {
|
|
86
|
+
const head = lastPathFragment(lines[i2] ?? "");
|
|
87
|
+
const tail = firstWord(lines[i2 + 1] ?? "");
|
|
88
|
+
if (!head || !tail) continue;
|
|
89
|
+
candidates.push(head.text + tail);
|
|
90
|
+
}
|
|
91
|
+
return candidates.join("\n");
|
|
92
|
+
}
|
|
93
|
+
function lastPathFragment(line) {
|
|
94
|
+
let found = null;
|
|
95
|
+
const words = /\S+/g;
|
|
96
|
+
for (const match2 of line.matchAll(words)) {
|
|
97
|
+
if (match2[0].includes("/")) {
|
|
98
|
+
found = { text: match2[0], index: match2.index ?? 0 };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return found;
|
|
102
|
+
}
|
|
103
|
+
function firstWord(line) {
|
|
104
|
+
return /\S+/.exec(line)?.[0] ?? "";
|
|
105
|
+
}
|
|
106
|
+
function findImagePathsInText(plain, width = 0, limit = MAX_TERMINAL_IMAGE_PATHS) {
|
|
107
|
+
const merged = [];
|
|
108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
109
|
+
const sources = [
|
|
110
|
+
plain,
|
|
111
|
+
width > 0 ? joinWrappedLines(plain, width) : "",
|
|
112
|
+
joinBrokenPathLines(plain)
|
|
113
|
+
];
|
|
114
|
+
for (const source of sources) {
|
|
115
|
+
if (!source) continue;
|
|
116
|
+
for (const path of findImagePaths(source, limit)) {
|
|
117
|
+
if (seen.has(path)) continue;
|
|
118
|
+
seen.add(path);
|
|
119
|
+
merged.push(path);
|
|
120
|
+
if (merged.length >= limit) return merged;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return merged;
|
|
124
|
+
}
|
|
125
|
+
function findPathAnchors(lines, candidates) {
|
|
126
|
+
const anchors = [];
|
|
127
|
+
for (const candidate of candidates) {
|
|
128
|
+
if (!candidate) continue;
|
|
129
|
+
const anchor = firstAnchor(lines, candidate);
|
|
130
|
+
if (anchor) anchors.push(anchor);
|
|
131
|
+
}
|
|
132
|
+
return anchors;
|
|
133
|
+
}
|
|
134
|
+
function firstAnchor(lines, candidate) {
|
|
135
|
+
for (let row = 0; row < lines.length; row += 1) {
|
|
136
|
+
const line = lines[row] ?? "";
|
|
137
|
+
const direct = line.indexOf(candidate);
|
|
138
|
+
if (direct >= 0) return { candidate, row, col: direct, span: 1 };
|
|
139
|
+
const head = lastPathFragment(line);
|
|
140
|
+
if (!head) continue;
|
|
141
|
+
const tail = firstWord(lines[row + 1] ?? "");
|
|
142
|
+
if (tail && head.text + tail === candidate) {
|
|
143
|
+
return { candidate, row, col: head.index, span: 2 };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// web-src/core/agent-screen.ts
|
|
150
|
+
var AGENT_SCREEN_RULE_SET_VERSION = 1;
|
|
151
|
+
var ESC2 = String.fromCharCode(27);
|
|
152
|
+
var BEL2 = String.fromCharCode(7);
|
|
153
|
+
var OSC_TITLE_RE = new RegExp(
|
|
154
|
+
`${ESC2}\\](?:0|2);([^${BEL2}${ESC2}]*)(?:${BEL2}|${ESC2}\\\\)`,
|
|
155
|
+
"g"
|
|
156
|
+
);
|
|
157
|
+
var BLOCKING_HINTS = [
|
|
158
|
+
{ contains: ["enter to confirm"] },
|
|
159
|
+
{ contains: ["enter to select"] },
|
|
160
|
+
{ contains: ["enter to submit"] },
|
|
161
|
+
{ contains: ["allow command?"] },
|
|
162
|
+
{ contains: ["[y/n]"] },
|
|
163
|
+
{ contains: ["yes (y)"] },
|
|
164
|
+
{ contains: ["do you want to proceed?"] }
|
|
165
|
+
];
|
|
166
|
+
var DEFAULT_AGENT_SCREEN_RULES = {
|
|
167
|
+
version: AGENT_SCREEN_RULE_SET_VERSION,
|
|
168
|
+
rules: [
|
|
169
|
+
{
|
|
170
|
+
id: "title_requires_input",
|
|
171
|
+
state: "waiting",
|
|
172
|
+
priority: 1100,
|
|
173
|
+
region: "osc_title",
|
|
174
|
+
contains: ["action required"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "title_spinner",
|
|
178
|
+
state: "working",
|
|
179
|
+
priority: 1050,
|
|
180
|
+
region: "osc_title",
|
|
181
|
+
regex: ["^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]"]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "transcript_view",
|
|
185
|
+
state: "skip",
|
|
186
|
+
priority: 1e3,
|
|
187
|
+
region: "bottom_non_empty",
|
|
188
|
+
lines: 8,
|
|
189
|
+
any: [
|
|
190
|
+
{ contains: ["showing detailed transcript"] },
|
|
191
|
+
{ contains: ["pgup/pgdn", "home/end to jump", "q to quit"] }
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "interactive_form",
|
|
196
|
+
state: "waiting",
|
|
197
|
+
priority: 980,
|
|
198
|
+
region: "bottom_non_empty",
|
|
199
|
+
lines: 14,
|
|
200
|
+
contains: ["esc to cancel"],
|
|
201
|
+
any: [
|
|
202
|
+
{ contains: ["enter to confirm"] },
|
|
203
|
+
{ contains: ["enter to select"] },
|
|
204
|
+
{ contains: ["enter to submit"] }
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "live_reasoning",
|
|
209
|
+
state: "working",
|
|
210
|
+
priority: 970,
|
|
211
|
+
region: "bottom_non_empty",
|
|
212
|
+
lines: 8,
|
|
213
|
+
contains: ["tokens", "thinking"],
|
|
214
|
+
any: [{ lineRegex: ["^\\s*[✢✻✽✶✳]"] }, { lineRegex: ["(?i)^\\s*·"] }]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "prompt_box",
|
|
218
|
+
state: "idle",
|
|
219
|
+
priority: 950,
|
|
220
|
+
region: "bottom_non_empty",
|
|
221
|
+
lines: 8,
|
|
222
|
+
lineRegex: ["^\\s*❯"],
|
|
223
|
+
not: [
|
|
224
|
+
...BLOCKING_HINTS,
|
|
225
|
+
{ contains: ["esc to cancel"] },
|
|
226
|
+
{ contains: ["arrow keys"] },
|
|
227
|
+
{ contains: ["↑/↓ to navigate"] }
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "strong_input_request",
|
|
232
|
+
state: "waiting",
|
|
233
|
+
priority: 900,
|
|
234
|
+
region: "bottom_non_empty",
|
|
235
|
+
lines: 12,
|
|
236
|
+
any: [
|
|
237
|
+
{ contains: ["press enter to confirm or esc to cancel"] },
|
|
238
|
+
{ contains: ["enter to submit answer"] },
|
|
239
|
+
{ contains: ["enter to submit all"] },
|
|
240
|
+
{ contains: ["allow command?"] }
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "permission_request",
|
|
245
|
+
state: "waiting",
|
|
246
|
+
priority: 850,
|
|
247
|
+
region: "bottom_non_empty",
|
|
248
|
+
lines: 14,
|
|
249
|
+
contains: ["do you want to proceed?"],
|
|
250
|
+
any: [
|
|
251
|
+
{ lineRegex: ["(?i)^\\P{L}*yes\\b"] },
|
|
252
|
+
{ lineRegex: ["(?i)^\\P{L}*no\\b"] }
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "weak_input_request",
|
|
257
|
+
state: "waiting",
|
|
258
|
+
priority: 600,
|
|
259
|
+
region: "bottom_non_empty",
|
|
260
|
+
lines: 8,
|
|
261
|
+
any: [
|
|
262
|
+
{ contains: ["[y/n]"] },
|
|
263
|
+
{ contains: ["yes (y)"] },
|
|
264
|
+
{
|
|
265
|
+
any: [
|
|
266
|
+
{ contains: ["do you want to"] },
|
|
267
|
+
{ contains: ["would you like to"] }
|
|
268
|
+
],
|
|
269
|
+
all: [{ any: [{ contains: ["yes"] }, { contains: ["❯"] }] }]
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "live_working_status",
|
|
275
|
+
state: "working",
|
|
276
|
+
priority: 500,
|
|
277
|
+
region: "bottom_non_empty",
|
|
278
|
+
lines: 3,
|
|
279
|
+
lineRegex: ["^[•◦]\\s+Working"],
|
|
280
|
+
not: [{ contains: ["conversation interrupted"] }]
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "last_prompt",
|
|
284
|
+
state: "idle",
|
|
285
|
+
priority: 400,
|
|
286
|
+
region: "last_non_empty",
|
|
287
|
+
lineRegex: ["^\\s*[›❯]"],
|
|
288
|
+
not: BLOCKING_HINTS
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
var MATCHER_KEYS = /* @__PURE__ */ new Set([
|
|
293
|
+
"contains",
|
|
294
|
+
"regex",
|
|
295
|
+
"lineRegex",
|
|
296
|
+
"all",
|
|
297
|
+
"any",
|
|
298
|
+
"not"
|
|
299
|
+
]);
|
|
300
|
+
var RULE_KEYS = /* @__PURE__ */ new Set([
|
|
301
|
+
...MATCHER_KEYS,
|
|
302
|
+
"id",
|
|
303
|
+
"state",
|
|
304
|
+
"priority",
|
|
305
|
+
"region",
|
|
306
|
+
"lines"
|
|
307
|
+
]);
|
|
308
|
+
function formatAgentScreenRuleSet(rules) {
|
|
309
|
+
return `${JSON.stringify(rules, null, 2)}
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
|
|
8
313
|
// web-src/core/diff-file-kinds.ts
|
|
9
314
|
var HEAVY_SIZE_CLASSES = /* @__PURE__ */ new Set(["medium", "large", "huge"]);
|
|
10
315
|
function classifyDiffFileKind(file) {
|
|
@@ -277,6 +582,191 @@ ${lines.join("\n")}
|
|
|
277
582
|
};
|
|
278
583
|
}
|
|
279
584
|
|
|
585
|
+
// web-src/core/error-detail.ts
|
|
586
|
+
function errorWithCause(message, cause) {
|
|
587
|
+
return Object.assign(new Error(message), { cause });
|
|
588
|
+
}
|
|
589
|
+
function errorWithCauses(message, errors2) {
|
|
590
|
+
return Object.assign(new Error(message), { errors: [...errors2] });
|
|
591
|
+
}
|
|
592
|
+
var OMIT_VALUE = /* @__PURE__ */ Symbol("omit-sensitive-error-field");
|
|
593
|
+
function isSensitiveFieldName(key) {
|
|
594
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
595
|
+
return normalized === "auth" || normalized.endsWith("auth") || normalized.startsWith("auth") && !normalized.startsWith("author") || normalized.includes("authorization") || normalized.includes("cookie") || normalized.includes("token") || normalized.includes("password") || normalized.includes("passwd") || normalized.includes("secret") || normalized.includes("credential") || normalized.includes("apikey") || normalized.includes("privatekey");
|
|
596
|
+
}
|
|
597
|
+
function errorName(error2) {
|
|
598
|
+
try {
|
|
599
|
+
return typeof error2.name === "string" ? error2.name : "Error";
|
|
600
|
+
} catch {
|
|
601
|
+
return "Error";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function errorMessage(error2) {
|
|
605
|
+
try {
|
|
606
|
+
return typeof error2.message === "string" ? error2.message : "unable to read error message";
|
|
607
|
+
} catch {
|
|
608
|
+
return "unable to read error message";
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function sanitizeObjectFields(value, ancestors, excludedKeys = /* @__PURE__ */ new Set()) {
|
|
612
|
+
let keys;
|
|
613
|
+
try {
|
|
614
|
+
keys = Object.getOwnPropertyNames(value);
|
|
615
|
+
} catch {
|
|
616
|
+
return { value: "[Unserializable object]", removedSensitive: false };
|
|
617
|
+
}
|
|
618
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
619
|
+
let removedSensitive = false;
|
|
620
|
+
for (const key of keys) {
|
|
621
|
+
if (excludedKeys.has(key)) continue;
|
|
622
|
+
if (isSensitiveFieldName(key)) {
|
|
623
|
+
removedSensitive = true;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
let descriptor;
|
|
627
|
+
try {
|
|
628
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
629
|
+
} catch {
|
|
630
|
+
output[key] = "[Unserializable field]";
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!descriptor) continue;
|
|
634
|
+
if (!("value" in descriptor)) {
|
|
635
|
+
output[key] = "[Accessor]";
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const sanitized = sanitizeValue(descriptor.value, ancestors);
|
|
639
|
+
if (sanitized === OMIT_VALUE) {
|
|
640
|
+
removedSensitive = true;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
output[key] = sanitized.value;
|
|
644
|
+
removedSensitive ||= sanitized.removedSensitive;
|
|
645
|
+
}
|
|
646
|
+
if (Object.keys(output).length === 0 && removedSensitive) return OMIT_VALUE;
|
|
647
|
+
return { value: output, removedSensitive };
|
|
648
|
+
}
|
|
649
|
+
function sanitizeError(error2, ancestors) {
|
|
650
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
651
|
+
output.name = errorName(error2);
|
|
652
|
+
output.message = errorMessage(error2);
|
|
653
|
+
const fields = sanitizeObjectFields(
|
|
654
|
+
error2,
|
|
655
|
+
ancestors,
|
|
656
|
+
/* @__PURE__ */ new Set(["name", "message", "stack"])
|
|
657
|
+
);
|
|
658
|
+
if (fields !== OMIT_VALUE) Object.assign(output, fields.value);
|
|
659
|
+
return {
|
|
660
|
+
value: output,
|
|
661
|
+
removedSensitive: fields === OMIT_VALUE ? true : fields.removedSensitive
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function sanitizeValue(value, ancestors) {
|
|
665
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
666
|
+
return { value, removedSensitive: false };
|
|
667
|
+
if (typeof value === "number") {
|
|
668
|
+
return {
|
|
669
|
+
value: Number.isFinite(value) ? value : String(value),
|
|
670
|
+
removedSensitive: false
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
if (typeof value === "bigint") {
|
|
674
|
+
return { value: `${value}n`, removedSensitive: false };
|
|
675
|
+
}
|
|
676
|
+
if (typeof value === "undefined") {
|
|
677
|
+
return { value: "[undefined]", removedSensitive: false };
|
|
678
|
+
}
|
|
679
|
+
if (typeof value === "symbol") {
|
|
680
|
+
return { value: "[symbol]", removedSensitive: false };
|
|
681
|
+
}
|
|
682
|
+
if (typeof value === "function") {
|
|
683
|
+
return { value: "[function]", removedSensitive: false };
|
|
684
|
+
}
|
|
685
|
+
const objectValue = value;
|
|
686
|
+
if (ancestors.has(objectValue)) {
|
|
687
|
+
return { value: "[Circular]", removedSensitive: false };
|
|
688
|
+
}
|
|
689
|
+
ancestors.add(objectValue);
|
|
690
|
+
try {
|
|
691
|
+
if (Array.isArray(objectValue)) {
|
|
692
|
+
const output = [];
|
|
693
|
+
let removedSensitive = false;
|
|
694
|
+
for (const item of objectValue) {
|
|
695
|
+
const sanitized = sanitizeValue(item, ancestors);
|
|
696
|
+
if (sanitized === OMIT_VALUE) {
|
|
697
|
+
removedSensitive = true;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
output.push(sanitized.value);
|
|
701
|
+
removedSensitive ||= sanitized.removedSensitive;
|
|
702
|
+
}
|
|
703
|
+
if (output.length === 0 && removedSensitive) return OMIT_VALUE;
|
|
704
|
+
return { value: output, removedSensitive };
|
|
705
|
+
}
|
|
706
|
+
if (objectValue instanceof Error)
|
|
707
|
+
return sanitizeError(objectValue, ancestors);
|
|
708
|
+
return sanitizeObjectFields(objectValue, ancestors);
|
|
709
|
+
} catch {
|
|
710
|
+
return { value: "[Unserializable object]", removedSensitive: false };
|
|
711
|
+
} finally {
|
|
712
|
+
ancestors.delete(objectValue);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function formatNonError(value) {
|
|
716
|
+
if (typeof value === "string") return value;
|
|
717
|
+
try {
|
|
718
|
+
const sanitized = sanitizeValue(value, /* @__PURE__ */ new Set());
|
|
719
|
+
const serializable = sanitized === OMIT_VALUE ? {} : sanitized.value;
|
|
720
|
+
return JSON.stringify(serializable);
|
|
721
|
+
} catch {
|
|
722
|
+
return "[Unserializable value]";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function formatErrorFields(error2) {
|
|
726
|
+
const fields = sanitizeObjectFields(
|
|
727
|
+
error2,
|
|
728
|
+
/* @__PURE__ */ new Set([error2]),
|
|
729
|
+
/* @__PURE__ */ new Set(["name", "message", "stack", "cause"])
|
|
730
|
+
);
|
|
731
|
+
if (fields === OMIT_VALUE) return "";
|
|
732
|
+
const output = fields.value;
|
|
733
|
+
return Object.keys(output).length > 0 ? `
|
|
734
|
+
Details: ${JSON.stringify(output)}` : "";
|
|
735
|
+
}
|
|
736
|
+
function formatErrorDetail(error2) {
|
|
737
|
+
const parts = [];
|
|
738
|
+
const seen = /* @__PURE__ */ new Set();
|
|
739
|
+
let current = error2;
|
|
740
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
741
|
+
seen.add(current);
|
|
742
|
+
parts.push(
|
|
743
|
+
`${errorName(current)}: ${errorMessage(current)}${formatErrorFields(current)}`
|
|
744
|
+
);
|
|
745
|
+
try {
|
|
746
|
+
current = current.cause;
|
|
747
|
+
} catch {
|
|
748
|
+
current = "[Unserializable error cause]";
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (current !== void 0) {
|
|
752
|
+
parts.push(
|
|
753
|
+
seen.has(current) ? "Error cause cycle detected" : formatNonError(current)
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return parts.join("\nCaused by: ") || formatNonError(error2);
|
|
757
|
+
}
|
|
758
|
+
async function responseErrorMessage(response, operation) {
|
|
759
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
760
|
+
const prefix = `${operation} (HTTP ${response.status}${statusText})`;
|
|
761
|
+
let body;
|
|
762
|
+
try {
|
|
763
|
+
body = await response.text();
|
|
764
|
+
} catch (error2) {
|
|
765
|
+
throw errorWithCause(`${prefix}: failed to read response body`, error2);
|
|
766
|
+
}
|
|
767
|
+
return body ? `${prefix}: ${body}` : prefix;
|
|
768
|
+
}
|
|
769
|
+
|
|
280
770
|
// web-src/core/expand-logic.ts
|
|
281
771
|
function initExpandState(prevHunkEndNew, hunkNewStart) {
|
|
282
772
|
return {
|
|
@@ -8439,16 +8929,18 @@ ${lines.join("\n")}
|
|
|
8439
8929
|
function loadShikiHighlighter(options) {
|
|
8440
8930
|
const key = JSON.stringify({
|
|
8441
8931
|
themes: [...options.themes].sort(),
|
|
8442
|
-
langs: [...options.langs].sort()
|
|
8932
|
+
langs: [...options.langs].sort(),
|
|
8933
|
+
failureMode: options.failureMode ?? "fallback"
|
|
8443
8934
|
});
|
|
8444
8935
|
const cached = cache.get(key);
|
|
8445
8936
|
if (cached) return cached;
|
|
8446
|
-
const
|
|
8937
|
+
const load = loadShikiModule().then(
|
|
8447
8938
|
(mod) => mod.createHighlighter({
|
|
8448
8939
|
themes: options.themes,
|
|
8449
8940
|
langs: options.langs
|
|
8450
8941
|
})
|
|
8451
8942
|
);
|
|
8943
|
+
const promise = options.failureMode === "throw" ? load : load.catch(() => null);
|
|
8452
8944
|
cache.set(key, promise);
|
|
8453
8945
|
return promise;
|
|
8454
8946
|
}
|
|
@@ -11323,188 +11815,6 @@ ${frontmatter.yaml}
|
|
|
11323
11815
|
return inferred;
|
|
11324
11816
|
}
|
|
11325
11817
|
|
|
11326
|
-
// web-src/core/error-detail.ts
|
|
11327
|
-
function errorWithCause(message, cause) {
|
|
11328
|
-
return Object.assign(new Error(message), { cause });
|
|
11329
|
-
}
|
|
11330
|
-
var OMIT_VALUE = /* @__PURE__ */ Symbol("omit-sensitive-error-field");
|
|
11331
|
-
function isSensitiveFieldName(key) {
|
|
11332
|
-
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
11333
|
-
return normalized === "auth" || normalized.endsWith("auth") || normalized.startsWith("auth") && !normalized.startsWith("author") || normalized.includes("authorization") || normalized.includes("cookie") || normalized.includes("token") || normalized.includes("password") || normalized.includes("passwd") || normalized.includes("secret") || normalized.includes("credential") || normalized.includes("apikey") || normalized.includes("privatekey");
|
|
11334
|
-
}
|
|
11335
|
-
function errorName(error2) {
|
|
11336
|
-
try {
|
|
11337
|
-
return typeof error2.name === "string" ? error2.name : "Error";
|
|
11338
|
-
} catch {
|
|
11339
|
-
return "Error";
|
|
11340
|
-
}
|
|
11341
|
-
}
|
|
11342
|
-
function errorMessage(error2) {
|
|
11343
|
-
try {
|
|
11344
|
-
return typeof error2.message === "string" ? error2.message : "unable to read error message";
|
|
11345
|
-
} catch {
|
|
11346
|
-
return "unable to read error message";
|
|
11347
|
-
}
|
|
11348
|
-
}
|
|
11349
|
-
function sanitizeObjectFields(value, ancestors, excludedKeys = /* @__PURE__ */ new Set()) {
|
|
11350
|
-
let keys;
|
|
11351
|
-
try {
|
|
11352
|
-
keys = Object.getOwnPropertyNames(value);
|
|
11353
|
-
} catch {
|
|
11354
|
-
return { value: "[Unserializable object]", removedSensitive: false };
|
|
11355
|
-
}
|
|
11356
|
-
const output = /* @__PURE__ */ Object.create(null);
|
|
11357
|
-
let removedSensitive = false;
|
|
11358
|
-
for (const key of keys) {
|
|
11359
|
-
if (excludedKeys.has(key)) continue;
|
|
11360
|
-
if (isSensitiveFieldName(key)) {
|
|
11361
|
-
removedSensitive = true;
|
|
11362
|
-
continue;
|
|
11363
|
-
}
|
|
11364
|
-
let descriptor;
|
|
11365
|
-
try {
|
|
11366
|
-
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
11367
|
-
} catch {
|
|
11368
|
-
output[key] = "[Unserializable field]";
|
|
11369
|
-
continue;
|
|
11370
|
-
}
|
|
11371
|
-
if (!descriptor) continue;
|
|
11372
|
-
if (!("value" in descriptor)) {
|
|
11373
|
-
output[key] = "[Accessor]";
|
|
11374
|
-
continue;
|
|
11375
|
-
}
|
|
11376
|
-
const sanitized = sanitizeValue(descriptor.value, ancestors);
|
|
11377
|
-
if (sanitized === OMIT_VALUE) {
|
|
11378
|
-
removedSensitive = true;
|
|
11379
|
-
continue;
|
|
11380
|
-
}
|
|
11381
|
-
output[key] = sanitized.value;
|
|
11382
|
-
removedSensitive ||= sanitized.removedSensitive;
|
|
11383
|
-
}
|
|
11384
|
-
if (Object.keys(output).length === 0 && removedSensitive) return OMIT_VALUE;
|
|
11385
|
-
return { value: output, removedSensitive };
|
|
11386
|
-
}
|
|
11387
|
-
function sanitizeError(error2, ancestors) {
|
|
11388
|
-
const output = /* @__PURE__ */ Object.create(null);
|
|
11389
|
-
output.name = errorName(error2);
|
|
11390
|
-
output.message = errorMessage(error2);
|
|
11391
|
-
const fields = sanitizeObjectFields(
|
|
11392
|
-
error2,
|
|
11393
|
-
ancestors,
|
|
11394
|
-
/* @__PURE__ */ new Set(["name", "message", "stack"])
|
|
11395
|
-
);
|
|
11396
|
-
if (fields !== OMIT_VALUE) Object.assign(output, fields.value);
|
|
11397
|
-
return {
|
|
11398
|
-
value: output,
|
|
11399
|
-
removedSensitive: fields === OMIT_VALUE ? true : fields.removedSensitive
|
|
11400
|
-
};
|
|
11401
|
-
}
|
|
11402
|
-
function sanitizeValue(value, ancestors) {
|
|
11403
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
11404
|
-
return { value, removedSensitive: false };
|
|
11405
|
-
if (typeof value === "number") {
|
|
11406
|
-
return {
|
|
11407
|
-
value: Number.isFinite(value) ? value : String(value),
|
|
11408
|
-
removedSensitive: false
|
|
11409
|
-
};
|
|
11410
|
-
}
|
|
11411
|
-
if (typeof value === "bigint") {
|
|
11412
|
-
return { value: `${value}n`, removedSensitive: false };
|
|
11413
|
-
}
|
|
11414
|
-
if (typeof value === "undefined") {
|
|
11415
|
-
return { value: "[undefined]", removedSensitive: false };
|
|
11416
|
-
}
|
|
11417
|
-
if (typeof value === "symbol") {
|
|
11418
|
-
return { value: "[symbol]", removedSensitive: false };
|
|
11419
|
-
}
|
|
11420
|
-
if (typeof value === "function") {
|
|
11421
|
-
return { value: "[function]", removedSensitive: false };
|
|
11422
|
-
}
|
|
11423
|
-
const objectValue = value;
|
|
11424
|
-
if (ancestors.has(objectValue)) {
|
|
11425
|
-
return { value: "[Circular]", removedSensitive: false };
|
|
11426
|
-
}
|
|
11427
|
-
ancestors.add(objectValue);
|
|
11428
|
-
try {
|
|
11429
|
-
if (Array.isArray(objectValue)) {
|
|
11430
|
-
const output = [];
|
|
11431
|
-
let removedSensitive = false;
|
|
11432
|
-
for (const item of objectValue) {
|
|
11433
|
-
const sanitized = sanitizeValue(item, ancestors);
|
|
11434
|
-
if (sanitized === OMIT_VALUE) {
|
|
11435
|
-
removedSensitive = true;
|
|
11436
|
-
continue;
|
|
11437
|
-
}
|
|
11438
|
-
output.push(sanitized.value);
|
|
11439
|
-
removedSensitive ||= sanitized.removedSensitive;
|
|
11440
|
-
}
|
|
11441
|
-
if (output.length === 0 && removedSensitive) return OMIT_VALUE;
|
|
11442
|
-
return { value: output, removedSensitive };
|
|
11443
|
-
}
|
|
11444
|
-
if (objectValue instanceof Error)
|
|
11445
|
-
return sanitizeError(objectValue, ancestors);
|
|
11446
|
-
return sanitizeObjectFields(objectValue, ancestors);
|
|
11447
|
-
} catch {
|
|
11448
|
-
return { value: "[Unserializable object]", removedSensitive: false };
|
|
11449
|
-
} finally {
|
|
11450
|
-
ancestors.delete(objectValue);
|
|
11451
|
-
}
|
|
11452
|
-
}
|
|
11453
|
-
function formatNonError(value) {
|
|
11454
|
-
if (typeof value === "string") return value;
|
|
11455
|
-
try {
|
|
11456
|
-
const sanitized = sanitizeValue(value, /* @__PURE__ */ new Set());
|
|
11457
|
-
const serializable = sanitized === OMIT_VALUE ? {} : sanitized.value;
|
|
11458
|
-
return JSON.stringify(serializable);
|
|
11459
|
-
} catch {
|
|
11460
|
-
return "[Unserializable value]";
|
|
11461
|
-
}
|
|
11462
|
-
}
|
|
11463
|
-
function formatErrorFields(error2) {
|
|
11464
|
-
const fields = sanitizeObjectFields(
|
|
11465
|
-
error2,
|
|
11466
|
-
/* @__PURE__ */ new Set([error2]),
|
|
11467
|
-
/* @__PURE__ */ new Set(["name", "message", "stack", "cause"])
|
|
11468
|
-
);
|
|
11469
|
-
if (fields === OMIT_VALUE) return "";
|
|
11470
|
-
const output = fields.value;
|
|
11471
|
-
return Object.keys(output).length > 0 ? `
|
|
11472
|
-
Details: ${JSON.stringify(output)}` : "";
|
|
11473
|
-
}
|
|
11474
|
-
function formatErrorDetail(error2) {
|
|
11475
|
-
const parts = [];
|
|
11476
|
-
const seen = /* @__PURE__ */ new Set();
|
|
11477
|
-
let current = error2;
|
|
11478
|
-
while (current instanceof Error && !seen.has(current)) {
|
|
11479
|
-
seen.add(current);
|
|
11480
|
-
parts.push(
|
|
11481
|
-
`${errorName(current)}: ${errorMessage(current)}${formatErrorFields(current)}`
|
|
11482
|
-
);
|
|
11483
|
-
try {
|
|
11484
|
-
current = current.cause;
|
|
11485
|
-
} catch {
|
|
11486
|
-
current = "[Unserializable error cause]";
|
|
11487
|
-
}
|
|
11488
|
-
}
|
|
11489
|
-
if (current !== void 0) {
|
|
11490
|
-
parts.push(
|
|
11491
|
-
seen.has(current) ? "Error cause cycle detected" : formatNonError(current)
|
|
11492
|
-
);
|
|
11493
|
-
}
|
|
11494
|
-
return parts.join("\nCaused by: ") || formatNonError(error2);
|
|
11495
|
-
}
|
|
11496
|
-
async function responseErrorMessage(response, operation) {
|
|
11497
|
-
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
11498
|
-
const prefix = `${operation} (HTTP ${response.status}${statusText})`;
|
|
11499
|
-
let body;
|
|
11500
|
-
try {
|
|
11501
|
-
body = await response.text();
|
|
11502
|
-
} catch (error2) {
|
|
11503
|
-
throw errorWithCause(`${prefix}: failed to read response body`, error2);
|
|
11504
|
-
}
|
|
11505
|
-
return body ? `${prefix}: ${body}` : prefix;
|
|
11506
|
-
}
|
|
11507
|
-
|
|
11508
11818
|
// web-src/core/id.ts
|
|
11509
11819
|
function bytesToHex(bytes) {
|
|
11510
11820
|
return Array.from(bytes, (b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
@@ -23947,11 +24257,31 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
23947
24257
|
}
|
|
23948
24258
|
async function ensureDbUiState() {
|
|
23949
24259
|
if (dbUiLoadPromise) return dbUiLoadPromise;
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
24260
|
+
const operation = (async () => {
|
|
24261
|
+
const response = await deps.trackLoad(fetch("/_db/ui"));
|
|
24262
|
+
if (!response.ok) {
|
|
24263
|
+
throw new Error(
|
|
24264
|
+
await responseErrorMessage(response, "load database UI settings")
|
|
24265
|
+
);
|
|
24266
|
+
}
|
|
24267
|
+
let state;
|
|
24268
|
+
try {
|
|
24269
|
+
state = await response.json();
|
|
24270
|
+
} catch (error2) {
|
|
24271
|
+
throw errorWithCause(
|
|
24272
|
+
"load database UI settings: response is not valid JSON",
|
|
24273
|
+
error2
|
|
24274
|
+
);
|
|
24275
|
+
}
|
|
24276
|
+
applyDbUiState(state);
|
|
24277
|
+
})();
|
|
24278
|
+
dbUiLoadPromise = operation;
|
|
24279
|
+
try {
|
|
24280
|
+
await operation;
|
|
24281
|
+
} catch (error2) {
|
|
24282
|
+
if (dbUiLoadPromise === operation) dbUiLoadPromise = null;
|
|
24283
|
+
throw error2;
|
|
24284
|
+
}
|
|
23955
24285
|
}
|
|
23956
24286
|
function getColumnWidths(dbId, table2) {
|
|
23957
24287
|
return { ...dbUiState.columnWidths[dbId]?.[table2] || {} };
|
|
@@ -24019,14 +24349,29 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24019
24349
|
const v = dbUiState.prefs?.[key];
|
|
24020
24350
|
return v === void 0 ? fallback : v;
|
|
24021
24351
|
}
|
|
24022
|
-
function
|
|
24023
|
-
const
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24352
|
+
async function saveDbUiPrefs(patch) {
|
|
24353
|
+
const response = await deps.trackLoad(
|
|
24354
|
+
fetch("/_db/ui", {
|
|
24355
|
+
method: "PATCH",
|
|
24356
|
+
headers: actionHeaders(),
|
|
24357
|
+
body: JSON.stringify({ prefs: patch })
|
|
24358
|
+
})
|
|
24359
|
+
);
|
|
24360
|
+
if (!response.ok) {
|
|
24361
|
+
throw new Error(
|
|
24362
|
+
await responseErrorMessage(response, "save database UI settings")
|
|
24363
|
+
);
|
|
24364
|
+
}
|
|
24365
|
+
let saved;
|
|
24366
|
+
try {
|
|
24367
|
+
saved = await response.json();
|
|
24368
|
+
} catch (error2) {
|
|
24369
|
+
throw errorWithCause(
|
|
24370
|
+
"save database UI settings: response is not valid JSON",
|
|
24371
|
+
error2
|
|
24372
|
+
);
|
|
24373
|
+
}
|
|
24374
|
+
applyDbUiState({ ...dbUiState, prefs: saved.prefs });
|
|
24030
24375
|
}
|
|
24031
24376
|
function onDbUiPrefChange(listener) {
|
|
24032
24377
|
dbUiPrefListeners.add(listener);
|
|
@@ -24455,7 +24800,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24455
24800
|
getSnapshotSelectedTables,
|
|
24456
24801
|
setSnapshotSelectedTables,
|
|
24457
24802
|
getDbUiPref,
|
|
24458
|
-
setDbUiPref,
|
|
24459
24803
|
onDbUiPrefChange,
|
|
24460
24804
|
loadSqlHistory,
|
|
24461
24805
|
refreshDatastores,
|
|
@@ -24872,7 +25216,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24872
25216
|
localize,
|
|
24873
25217
|
refresh: refreshDatastores,
|
|
24874
25218
|
getDbUiPref,
|
|
24875
|
-
|
|
25219
|
+
loadDbUiPrefs: ensureDbUiState,
|
|
25220
|
+
saveDbUiPrefs,
|
|
24876
25221
|
onDbUiPrefChange
|
|
24877
25222
|
};
|
|
24878
25223
|
}
|
|
@@ -28421,7 +28766,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
28421
28766
|
settings: {
|
|
28422
28767
|
nav: "Settings",
|
|
28423
28768
|
title: "Settings",
|
|
28424
|
-
intro: "Viewer preferences for this project and this browser.
|
|
28769
|
+
intro: "Viewer preferences for this project and this browser. Edits remain a draft until you select Save changes.",
|
|
28425
28770
|
groups: []
|
|
28426
28771
|
},
|
|
28427
28772
|
overview: {
|
|
@@ -28542,6 +28887,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
28542
28887
|
kind: "paragraph",
|
|
28543
28888
|
text: "The left side starts with a Your turn section for terminals that are waiting for input or finished but unread. Below it, you can scope the session list to this repository or all tmux sessions, filter by state, and search by task, place, or id. Its two-tier tree puts shells opened by this panel at the top: a shell running tmux carries a terminal icon and its session name, and that session's windows and panes hang underneath it. The bottom tier is the tmux sessions no shell has opened yet. Each pane is labelled with the title tmux shows for it — a coding agent running in a pane usually puts what it is doing there, so the tree alone tells you which pane is busy."
|
|
28544
28889
|
},
|
|
28890
|
+
{
|
|
28891
|
+
kind: "paragraph",
|
|
28892
|
+
text: "Status first uses lifecycle reports when they are available. Otherwise it evaluates every enabled screen rule against the live terminal title and recent visible lines, then uses the highest-priority match. A terminal is tracked only after a report or a visible rule identifies it; screen motion then provides the working/idle fallback. Working matches expire when the title and screen stop changing, so stale status text does not stay active. Settings & Help → Settings contains the full JSON rule set, including regions, priorities, contains checks, regular expressions, and nested all/any/not conditions. Regular expressions use a bounded safe subset: groups, alternation, and backreferences are rejected, and AND/OR belongs in all/any. Saving validates the whole set and shows every error without replacing the active rules. Restoring the built-in rules removes the saved override so updated defaults can arrive with later releases."
|
|
28893
|
+
},
|
|
28545
28894
|
{
|
|
28546
28895
|
kind: "paragraph",
|
|
28547
28896
|
text: "Click a pane and the panel takes you to it. If a shell already has that session open, it switches to that shell and makes the pane current; otherwise a shell is opened and attached for you. A session moves up to the top tier the moment a shell opens it and drops back down when that shell closes, so you end up with one shell per tmux session rather than one per pane. Powerline separators and file icons render when a Nerd Font is installed on the machine running the browser; no font ships with the package. The tree needs tmux on PATH — without it the panel says so, and shells still work. Opening shells needs the optional @lydell/node-pty package."
|
|
@@ -28596,6 +28945,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
28596
28945
|
"settings.json",
|
|
28597
28946
|
"Settings — diff layout, theme, language, sidebar/history widths, font sizes, syntax highlight, ignore-whitespace, hide-tests, scope overrides (omitted dirs / excluded names), upload toggle, annotation panel open/width/follow/mute/rate, and the last viewed diff range."
|
|
28598
28947
|
],
|
|
28948
|
+
[
|
|
28949
|
+
"agent-screen-rules.json",
|
|
28950
|
+
"Terminal status screen rules saved from Settings. Removing the override restores the built-in rules."
|
|
28951
|
+
],
|
|
28599
28952
|
[
|
|
28600
28953
|
"view-state.json",
|
|
28601
28954
|
"Sidebar tree state — collapsed directories, lazy-expanded directories (folders opened on demand in large repos), and the viewed-file list used to dim already-read entries."
|
|
@@ -29155,7 +29508,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
29155
29508
|
settings: {
|
|
29156
29509
|
nav: "設定",
|
|
29157
29510
|
title: "設定",
|
|
29158
|
-
intro: "
|
|
29511
|
+
intro: "このプロジェクトとこのブラウザのビューア設定です。「変更を保存」を押すまで編集内容は下書きのままです。",
|
|
29159
29512
|
groups: []
|
|
29160
29513
|
},
|
|
29161
29514
|
overview: {
|
|
@@ -29276,6 +29629,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
29276
29629
|
kind: "paragraph",
|
|
29277
29630
|
text: "左側の先頭には、入力待ちと、終わったのにまだ見ていないターミナルを集める「あなたの番」があります。その下の一覧は、このリポジトリだけ、またはすべての tmux セッションに範囲を切り替え、状態で絞り込み、作業内容・場所・宛先で検索できます。2 段のツリーの上段はこのパネルが開いたシェルで、中で tmux が動いていれば端末の印とセッション名が付き、そのセッションのウィンドウとペインがその下にぶら下がります。下段は、まだどのシェルも開いていない tmux セッションです。ペインには tmux 側のタイトルが付きます。ペインで動いているコーディングエージェントは作業内容をタイトルに出すので、ツリーを見るだけでどのペインが動いているか分かります。"
|
|
29278
29631
|
},
|
|
29632
|
+
{
|
|
29633
|
+
kind: "paragraph",
|
|
29634
|
+
text: "状態変更の申告がある場合はそれを先に使います。申告が無い場合は、現在のターミナルタイトルと画面下端の表示に対して全ルールを評価し、優先度が最大の一致から「作業中」「入力待ち」「待機中」「直前の状態を維持」を決めます。申告か見えているルールで対象を識別した後だけ、画面の変化量を作業中・待機中の補助判定に使います。作業中ルールの文字が残っていても、タイトルと画面が変化しなくなれば待機中へ移ります。設定・ヘルプ → 設定では、見る範囲、優先度、contains、正規表現、入れ子の all/any/not を含むJSONルール集を編集できます。正規表現は処理時間を抑えた範囲だけを許可し、グループ・選択・後方参照は使えません。AND/OR は all/any で表します。保存時は全ルールを検証し、エラーはすべて表示して適用中のルールを置き換えません。組み込みルールへ戻すと保存済みの上書きを削除するため、以後の更新で新しい既定ルールを受け取れます。"
|
|
29635
|
+
},
|
|
29279
29636
|
{
|
|
29280
29637
|
kind: "paragraph",
|
|
29281
29638
|
text: "ペインを押すと、そこまで連れて行きます。そのセッションを既に開いているシェルがあれば、そのシェルに切り替えてペインをカレントにします。無ければ、こちらでシェルを開いて attach します。シェルで開いた瞬間にセッションは下段から上段へ移り、そのシェルを閉じると下段へ戻ります。つまりペインごとではなく、tmux のセッション 1 つにつきシェル 1 本になります。powerline のセパレータやファイルアイコンは、ブラウザを動かしている環境に Nerd Font が入っていれば表示されます(フォントはパッケージに同梱していません)。ツリーには tmux が PATH にあることが必要で、無い場合はその旨を表示します(シェルはそのまま使えます)。シェルを開くには任意依存の @lydell/node-pty が必要です。"
|
|
@@ -29330,6 +29687,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
29330
29687
|
"settings.json",
|
|
29331
29688
|
"設定 — diff レイアウト、テーマ、言語、サイドバー/履歴幅、フォントサイズ、シンタックスハイライト、whitespace 無視、テスト非表示、scope 上書き(除外ディレクトリ / 除外名)、アップロード許可、注釈パネルの開閉/幅/follow/ミュート/再生速度、最後に表示した diff 範囲を保存します。"
|
|
29332
29689
|
],
|
|
29690
|
+
[
|
|
29691
|
+
"agent-screen-rules.json",
|
|
29692
|
+
"設定画面で保存したターミナル状態の画面判定ルール。上書きを削除すると組み込みルールへ戻ります。"
|
|
29693
|
+
],
|
|
29333
29694
|
[
|
|
29334
29695
|
"view-state.json",
|
|
29335
29696
|
"サイドバーツリーの状態 — 折りたたみ済みディレクトリ、遅延展開済みディレクトリ(大規模リポジトリで必要に応じて開かれたフォルダ)、既読扱いするための表示済みファイル一覧。"
|
|
@@ -39320,7 +39681,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
39320
39681
|
stateWaiting: "waiting",
|
|
39321
39682
|
stateDone: "unread",
|
|
39322
39683
|
stateIdle: "idle",
|
|
39323
|
-
guessed: "
|
|
39684
|
+
guessed: "detected from visible terminal UI or screen activity",
|
|
39324
39685
|
filterPlaceholder: "task, place, or id",
|
|
39325
39686
|
filterAll: "all",
|
|
39326
39687
|
noMatches: "Nothing matches this filter.",
|
|
@@ -39391,11 +39752,11 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
39391
39752
|
yourTurnHint: "入力待ちと、終わったのにまだ見ていないもの",
|
|
39392
39753
|
yourTurnEmpty: "いま手を入れるものはありません。",
|
|
39393
39754
|
sessions: "ターミナル",
|
|
39394
|
-
stateWorking: "
|
|
39395
|
-
stateWaiting: "
|
|
39755
|
+
stateWorking: "作業中",
|
|
39756
|
+
stateWaiting: "入力待ち",
|
|
39396
39757
|
stateDone: "未読",
|
|
39397
|
-
stateIdle: "
|
|
39398
|
-
guessed: "
|
|
39758
|
+
stateIdle: "待機中",
|
|
39759
|
+
guessed: "画面表示または画面の動きからの判定",
|
|
39399
39760
|
filterPlaceholder: "作業内容・場所・宛先",
|
|
39400
39761
|
filterAll: "すべて",
|
|
39401
39762
|
noMatches: "この条件に合うものはありません。",
|
|
@@ -39660,6 +40021,10 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
39660
40021
|
const attentionEmpty = document.createElement("p");
|
|
39661
40022
|
attentionEmpty.className = "terminal-empty";
|
|
39662
40023
|
attentionSection.append(attentionHead, attentionCards, attentionEmpty);
|
|
40024
|
+
const observationErrors = document.createElement("pre");
|
|
40025
|
+
observationErrors.className = "terminal-observation-errors";
|
|
40026
|
+
observationErrors.setAttribute("role", "alert");
|
|
40027
|
+
observationErrors.hidden = true;
|
|
39663
40028
|
const filters = document.createElement("div");
|
|
39664
40029
|
filters.className = "terminal-filters";
|
|
39665
40030
|
const stateSelect = document.createElement("select");
|
|
@@ -39678,14 +40043,15 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
39678
40043
|
tree.role = "tree";
|
|
39679
40044
|
const treeEmpty = document.createElement("p");
|
|
39680
40045
|
treeEmpty.className = "terminal-empty";
|
|
39681
|
-
el.append(scopeBar, attentionSection, filters, tree);
|
|
40046
|
+
el.append(scopeBar, observationErrors, attentionSection, filters, tree);
|
|
39682
40047
|
let data = {
|
|
39683
40048
|
panes: null,
|
|
39684
40049
|
shells: [],
|
|
39685
40050
|
clients: [],
|
|
39686
40051
|
shellAvailable: true,
|
|
39687
40052
|
shellUnavailableReason: "",
|
|
39688
|
-
states: []
|
|
40053
|
+
states: [],
|
|
40054
|
+
stateErrors: []
|
|
39689
40055
|
};
|
|
39690
40056
|
let selected = null;
|
|
39691
40057
|
let stateFilter = null;
|
|
@@ -39825,7 +40191,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
39825
40191
|
button.title = [
|
|
39826
40192
|
row.task,
|
|
39827
40193
|
row.kind === "tmux" ? `${row.locator} · ${row.place}` : row.place,
|
|
39828
|
-
row.source
|
|
40194
|
+
row.source && row.source !== "hook" ? text3.guessed : "",
|
|
39829
40195
|
row.lastPrompt ? `${text3.lastPrompt}: ${row.lastPrompt}` : ""
|
|
39830
40196
|
].filter(Boolean).join("\n");
|
|
39831
40197
|
button.addEventListener("click", () => activate(row));
|
|
@@ -40028,6 +40394,13 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
40028
40394
|
data.clients
|
|
40029
40395
|
);
|
|
40030
40396
|
const scoped = filterByScope(all, scope);
|
|
40397
|
+
observationErrors.textContent = data.stateErrors.map((error2) => {
|
|
40398
|
+
const target = error2.target ? ` ${error2.target}` : "";
|
|
40399
|
+
return `[${error2.operation}${target}]
|
|
40400
|
+
${error2.detail}${error2.stack ? `
|
|
40401
|
+
${error2.stack}` : ""}`;
|
|
40402
|
+
}).join("\n\n");
|
|
40403
|
+
observationErrors.hidden = data.stateErrors.length === 0;
|
|
40031
40404
|
renderScope(all, scoped);
|
|
40032
40405
|
const attention = attentionRows(scoped);
|
|
40033
40406
|
attentionTitle.textContent = text3.yourTurn;
|
|
@@ -40057,147 +40430,6 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
40057
40430
|
};
|
|
40058
40431
|
}
|
|
40059
40432
|
|
|
40060
|
-
// web-src/core/terminal-paste.ts
|
|
40061
|
-
var PASTE_IMAGE_TYPES = {
|
|
40062
|
-
"image/png": "png",
|
|
40063
|
-
"image/jpeg": "jpg",
|
|
40064
|
-
"image/gif": "gif",
|
|
40065
|
-
"image/webp": "webp"
|
|
40066
|
-
};
|
|
40067
|
-
var MAX_PASTE_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
40068
|
-
var MAX_PASTE_BODY_BYTES = Math.ceil(MAX_PASTE_IMAGE_BYTES * 1.4);
|
|
40069
|
-
var SHIFT_ENTER_SEQUENCE = `${String.fromCharCode(27)}[200~${String.fromCharCode(10)}${String.fromCharCode(27)}[201~`;
|
|
40070
|
-
function isShiftEnter(event) {
|
|
40071
|
-
return event.key === "Enter" && event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
40072
|
-
}
|
|
40073
|
-
|
|
40074
|
-
// web-src/core/terminal-images.ts
|
|
40075
|
-
var TERMINAL_IMAGE_EXTENSIONS = [
|
|
40076
|
-
...new Set(Object.values(PASTE_IMAGE_TYPES)),
|
|
40077
|
-
"jpeg"
|
|
40078
|
-
];
|
|
40079
|
-
var MAX_TERMINAL_IMAGE_PATHS = 64;
|
|
40080
|
-
var MAX_TERMINAL_IMAGE_QUERY = 16;
|
|
40081
|
-
var MAX_IMAGE_PATH_LENGTH = 1024;
|
|
40082
|
-
var ESC = String.fromCharCode(27);
|
|
40083
|
-
var BEL = String.fromCharCode(7);
|
|
40084
|
-
var ANSI_RE = new RegExp(
|
|
40085
|
-
`${ESC}\\[[0-9;?]*[ -/]*[@-~]|${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|${ESC}[@-Z\\\\-_]`,
|
|
40086
|
-
"g"
|
|
40087
|
-
);
|
|
40088
|
-
function stripAnsi(text3) {
|
|
40089
|
-
return text3.replace(ANSI_RE, "");
|
|
40090
|
-
}
|
|
40091
|
-
var PATH_CHAR = "[\\p{L}\\p{N}._~+@%/-]";
|
|
40092
|
-
var NAME_CHAR = "[\\p{L}\\p{N}_~+@%-]";
|
|
40093
|
-
var IMAGE_PATH_RE = new RegExp(
|
|
40094
|
-
`${PATH_CHAR}*${NAME_CHAR}\\.(?:${TERMINAL_IMAGE_EXTENSIONS.join("|")})(?![\\p{L}\\p{N}])`,
|
|
40095
|
-
"giu"
|
|
40096
|
-
);
|
|
40097
|
-
function looksLikeUrl(value) {
|
|
40098
|
-
return value.startsWith("//") || value.includes("://");
|
|
40099
|
-
}
|
|
40100
|
-
function findImagePaths(text3, limit = MAX_TERMINAL_IMAGE_PATHS) {
|
|
40101
|
-
const found = [];
|
|
40102
|
-
const seen = /* @__PURE__ */ new Set();
|
|
40103
|
-
for (const match2 of text3.matchAll(IMAGE_PATH_RE)) {
|
|
40104
|
-
const path = match2[0];
|
|
40105
|
-
if (path.length > MAX_IMAGE_PATH_LENGTH) continue;
|
|
40106
|
-
if (looksLikeUrl(path)) continue;
|
|
40107
|
-
if (seen.has(path)) continue;
|
|
40108
|
-
seen.add(path);
|
|
40109
|
-
found.push(path);
|
|
40110
|
-
if (found.length >= limit) break;
|
|
40111
|
-
}
|
|
40112
|
-
return found;
|
|
40113
|
-
}
|
|
40114
|
-
function joinWrappedLines(text3, width) {
|
|
40115
|
-
if (!Number.isFinite(width) || width <= 0) return text3;
|
|
40116
|
-
const lines = text3.split("\n");
|
|
40117
|
-
const joined = [];
|
|
40118
|
-
let current = null;
|
|
40119
|
-
let previousLength = 0;
|
|
40120
|
-
for (const line of lines) {
|
|
40121
|
-
if (current === null) {
|
|
40122
|
-
current = line;
|
|
40123
|
-
} else if (previousLength >= width) {
|
|
40124
|
-
current += line;
|
|
40125
|
-
} else {
|
|
40126
|
-
joined.push(current);
|
|
40127
|
-
current = line;
|
|
40128
|
-
}
|
|
40129
|
-
previousLength = line.length;
|
|
40130
|
-
}
|
|
40131
|
-
if (current !== null) joined.push(current);
|
|
40132
|
-
return joined.join("\n");
|
|
40133
|
-
}
|
|
40134
|
-
function joinBrokenPathLines(text3) {
|
|
40135
|
-
const lines = text3.split("\n");
|
|
40136
|
-
const candidates = [];
|
|
40137
|
-
for (let i2 = 0; i2 + 1 < lines.length; i2 += 1) {
|
|
40138
|
-
const head = lastPathFragment(lines[i2] ?? "");
|
|
40139
|
-
const tail = firstWord(lines[i2 + 1] ?? "");
|
|
40140
|
-
if (!head || !tail) continue;
|
|
40141
|
-
candidates.push(head.text + tail);
|
|
40142
|
-
}
|
|
40143
|
-
return candidates.join("\n");
|
|
40144
|
-
}
|
|
40145
|
-
function lastPathFragment(line) {
|
|
40146
|
-
let found = null;
|
|
40147
|
-
const words = /\S+/g;
|
|
40148
|
-
for (const match2 of line.matchAll(words)) {
|
|
40149
|
-
if (match2[0].includes("/")) {
|
|
40150
|
-
found = { text: match2[0], index: match2.index ?? 0 };
|
|
40151
|
-
}
|
|
40152
|
-
}
|
|
40153
|
-
return found;
|
|
40154
|
-
}
|
|
40155
|
-
function firstWord(line) {
|
|
40156
|
-
return /\S+/.exec(line)?.[0] ?? "";
|
|
40157
|
-
}
|
|
40158
|
-
function findImagePathsInText(plain, width = 0, limit = MAX_TERMINAL_IMAGE_PATHS) {
|
|
40159
|
-
const merged = [];
|
|
40160
|
-
const seen = /* @__PURE__ */ new Set();
|
|
40161
|
-
const sources = [
|
|
40162
|
-
plain,
|
|
40163
|
-
width > 0 ? joinWrappedLines(plain, width) : "",
|
|
40164
|
-
joinBrokenPathLines(plain)
|
|
40165
|
-
];
|
|
40166
|
-
for (const source of sources) {
|
|
40167
|
-
if (!source) continue;
|
|
40168
|
-
for (const path of findImagePaths(source, limit)) {
|
|
40169
|
-
if (seen.has(path)) continue;
|
|
40170
|
-
seen.add(path);
|
|
40171
|
-
merged.push(path);
|
|
40172
|
-
if (merged.length >= limit) return merged;
|
|
40173
|
-
}
|
|
40174
|
-
}
|
|
40175
|
-
return merged;
|
|
40176
|
-
}
|
|
40177
|
-
function findPathAnchors(lines, candidates) {
|
|
40178
|
-
const anchors = [];
|
|
40179
|
-
for (const candidate of candidates) {
|
|
40180
|
-
if (!candidate) continue;
|
|
40181
|
-
const anchor = firstAnchor(lines, candidate);
|
|
40182
|
-
if (anchor) anchors.push(anchor);
|
|
40183
|
-
}
|
|
40184
|
-
return anchors;
|
|
40185
|
-
}
|
|
40186
|
-
function firstAnchor(lines, candidate) {
|
|
40187
|
-
for (let row = 0; row < lines.length; row += 1) {
|
|
40188
|
-
const line = lines[row] ?? "";
|
|
40189
|
-
const direct = line.indexOf(candidate);
|
|
40190
|
-
if (direct >= 0) return { candidate, row, col: direct, span: 1 };
|
|
40191
|
-
const head = lastPathFragment(line);
|
|
40192
|
-
if (!head) continue;
|
|
40193
|
-
const tail = firstWord(lines[row + 1] ?? "");
|
|
40194
|
-
if (tail && head.text + tail === candidate) {
|
|
40195
|
-
return { candidate, row, col: head.index, span: 2 };
|
|
40196
|
-
}
|
|
40197
|
-
}
|
|
40198
|
-
return null;
|
|
40199
|
-
}
|
|
40200
|
-
|
|
40201
40433
|
// web-src/core/xterm-loader.ts
|
|
40202
40434
|
var loadXterm = createBundleLoader("xterm.js");
|
|
40203
40435
|
|
|
@@ -40894,6 +41126,7 @@ ${formatErrorDetail(error2)}`
|
|
|
40894
41126
|
let board = null;
|
|
40895
41127
|
let lastTargetId = null;
|
|
40896
41128
|
let states = [];
|
|
41129
|
+
let stateErrors = [];
|
|
40897
41130
|
let screen = null;
|
|
40898
41131
|
let reloadBtn = null;
|
|
40899
41132
|
let fontSmaller = null;
|
|
@@ -41003,7 +41236,8 @@ ${formatErrorDetail(error2)}`);
|
|
|
41003
41236
|
clients: clients?.clients ?? [],
|
|
41004
41237
|
shellAvailable: shells?.available ?? true,
|
|
41005
41238
|
shellUnavailableReason: shells?.reason ?? "",
|
|
41006
|
-
states
|
|
41239
|
+
states,
|
|
41240
|
+
stateErrors
|
|
41007
41241
|
});
|
|
41008
41242
|
board?.setSelected(attached?.id ?? null);
|
|
41009
41243
|
}
|
|
@@ -41036,12 +41270,13 @@ ${formatErrorDetail(error2)}`);
|
|
|
41036
41270
|
}
|
|
41037
41271
|
const nextPanes = await paneRes.json();
|
|
41038
41272
|
const nextShells = await shellRes.json();
|
|
41039
|
-
const
|
|
41273
|
+
const nextStateResponse = await stateRes.json();
|
|
41040
41274
|
const nextClients = await clientRes.json();
|
|
41041
41275
|
if (stale()) return;
|
|
41042
41276
|
panes = nextPanes;
|
|
41043
41277
|
shells = nextShells;
|
|
41044
|
-
states =
|
|
41278
|
+
states = nextStateResponse.states ?? [];
|
|
41279
|
+
stateErrors = nextStateResponse.errors ?? [];
|
|
41045
41280
|
clients = nextClients;
|
|
41046
41281
|
renderLists();
|
|
41047
41282
|
if (attached && !findShell(attached.id)) {
|
|
@@ -42275,6 +42510,17 @@ ${formatErrorDetail(error2)}`);
|
|
|
42275
42510
|
{ value: "en", label: "English" },
|
|
42276
42511
|
{ value: "ja", label: "日本語" }
|
|
42277
42512
|
];
|
|
42513
|
+
var GENERAL_SETTING_FIELDS = [
|
|
42514
|
+
"language",
|
|
42515
|
+
"sidebarFontSize",
|
|
42516
|
+
"codeFontSize",
|
|
42517
|
+
"omitDirs",
|
|
42518
|
+
"excludeNames",
|
|
42519
|
+
"watchLimit",
|
|
42520
|
+
"uploadEnabled",
|
|
42521
|
+
"inferFkRails",
|
|
42522
|
+
"s3TooltipEnabled"
|
|
42523
|
+
];
|
|
42278
42524
|
function section() {
|
|
42279
42525
|
const div = document.createElement("div");
|
|
42280
42526
|
div.className = "scope-settings-section";
|
|
@@ -42316,8 +42562,8 @@ ${formatErrorDetail(error2)}`);
|
|
|
42316
42562
|
wrap.append(input2, text3);
|
|
42317
42563
|
return { wrap, input: input2, text: text3 };
|
|
42318
42564
|
}
|
|
42319
|
-
function setFieldValue(field2, value) {
|
|
42320
|
-
if (field2 === document.activeElement) return;
|
|
42565
|
+
function setFieldValue(field2, value, force = false) {
|
|
42566
|
+
if (!force && field2 === document.activeElement) return;
|
|
42321
42567
|
if (field2.value !== value) field2.value = value;
|
|
42322
42568
|
}
|
|
42323
42569
|
function createViewerSettings(deps) {
|
|
@@ -42341,22 +42587,66 @@ ${formatErrorDetail(error2)}`);
|
|
|
42341
42587
|
const watchLimitNumber = document.createElement("input");
|
|
42342
42588
|
const watchLimitRange = document.createElement("input");
|
|
42343
42589
|
const watchLimitHelp = helpText("scope-watch-limit-help");
|
|
42344
|
-
const
|
|
42590
|
+
const agentRules = document.createElement("textarea");
|
|
42591
|
+
const agentRulesHelp = helpText("agent-screen-rules-help");
|
|
42592
|
+
const agentRulesSource = helpText("agent-screen-rules-source");
|
|
42593
|
+
const agentRulesError = helpText("agent-screen-rules-error");
|
|
42594
|
+
agentRulesError.classList.add("scope-settings-refresh-error");
|
|
42595
|
+
agentRulesError.hidden = true;
|
|
42596
|
+
const agentRulesHighlightError = helpText(
|
|
42597
|
+
"agent-screen-rules-highlight-error"
|
|
42598
|
+
);
|
|
42599
|
+
agentRulesHighlightError.classList.add("scope-settings-refresh-error");
|
|
42600
|
+
agentRulesHighlightError.hidden = true;
|
|
42601
|
+
const agentRulesHighlight = document.createElement("pre");
|
|
42602
|
+
agentRulesHighlight.className = "agent-screen-rules-highlight";
|
|
42603
|
+
agentRulesHighlight.setAttribute("aria-hidden", "true");
|
|
42604
|
+
const agentRulesGuide = document.createElement("details");
|
|
42605
|
+
agentRulesGuide.id = "agent-screen-rules-guide";
|
|
42606
|
+
agentRulesGuide.className = "agent-screen-rules-guide";
|
|
42607
|
+
agentRulesGuide.open = true;
|
|
42608
|
+
const agentRulesGuideTitle = document.createElement("summary");
|
|
42609
|
+
const agentRulesGuideIntro = helpText();
|
|
42610
|
+
const agentRulesGuideFields = helpText();
|
|
42611
|
+
const agentRulesGuideMatchers = helpText();
|
|
42612
|
+
const agentRulesGuideRegions = helpText();
|
|
42613
|
+
const agentRulesGuideExample = document.createElement("pre");
|
|
42614
|
+
agentRulesGuideExample.className = "agent-screen-rules-guide-example";
|
|
42615
|
+
const agentRulesSave = document.createElement("button");
|
|
42616
|
+
const agentRulesReset = document.createElement("button");
|
|
42617
|
+
const saveNote = helpText("scope-settings-save-note");
|
|
42618
|
+
const saveStatus = helpText("scope-settings-save-status");
|
|
42619
|
+
saveStatus.setAttribute("aria-live", "polite");
|
|
42620
|
+
const saveError = helpText("scope-settings-save-error");
|
|
42621
|
+
saveError.classList.add("scope-settings-refresh-error");
|
|
42622
|
+
saveError.hidden = true;
|
|
42345
42623
|
const refreshError = helpText("scope-settings-refresh-error");
|
|
42346
42624
|
refreshError.classList.add("scope-settings-refresh-error");
|
|
42347
42625
|
refreshError.hidden = true;
|
|
42348
42626
|
const resetButton = document.createElement("button");
|
|
42627
|
+
const saveButton = document.createElement("button");
|
|
42349
42628
|
const displayTitle = sectionTitle();
|
|
42350
42629
|
const uploadsTitle = sectionTitle();
|
|
42351
42630
|
const excludedTitle = sectionTitle();
|
|
42352
42631
|
const datastoreTitle = sectionTitle();
|
|
42353
42632
|
const watchTitle = sectionTitle();
|
|
42633
|
+
const agentRulesTitle = sectionTitle();
|
|
42354
42634
|
const languageLabel = fieldLabel("viewer-language");
|
|
42355
42635
|
const sidebarFontSizeLabel = fieldLabel("sidebar-font-size");
|
|
42356
42636
|
const codeFontSizeLabel = fieldLabel("code-font-size");
|
|
42357
42637
|
const omitDirsLabel = fieldLabel("scope-omit-dirs");
|
|
42358
42638
|
const excludeNamesLabel = fieldLabel("scope-exclude-names");
|
|
42359
42639
|
const watchLimitLabel = fieldLabel("scope-watch-limit");
|
|
42640
|
+
const agentRulesLabel = fieldLabel("agent-screen-rules");
|
|
42641
|
+
let generalDirty = false;
|
|
42642
|
+
let generalSavePending = false;
|
|
42643
|
+
let restoreDefaults = false;
|
|
42644
|
+
const changedGeneralFields = /* @__PURE__ */ new Set();
|
|
42645
|
+
let generalStatus = "idle";
|
|
42646
|
+
let agentRulesPending = false;
|
|
42647
|
+
let agentRulesDirty = false;
|
|
42648
|
+
let jsonHighlighter = null;
|
|
42649
|
+
let refreshGeneration = 0;
|
|
42360
42650
|
function build() {
|
|
42361
42651
|
const wrap = document.createElement("div");
|
|
42362
42652
|
wrap.className = "scope-settings";
|
|
@@ -42424,63 +42714,318 @@ ${formatErrorDetail(error2)}`);
|
|
|
42424
42714
|
const watch = section();
|
|
42425
42715
|
watch.id = "watch-settings-section";
|
|
42426
42716
|
watch.append(watchTitle, watchLimitLabel, watchRow, watchLimitHelp);
|
|
42717
|
+
agentRulesTitle.id = "agent-screen-rules-title";
|
|
42718
|
+
agentRules.id = "agent-screen-rules";
|
|
42719
|
+
agentRules.rows = 24;
|
|
42720
|
+
agentRules.spellcheck = false;
|
|
42721
|
+
agentRules.setAttribute(
|
|
42722
|
+
"aria-describedby",
|
|
42723
|
+
[
|
|
42724
|
+
"agent-screen-rules-help",
|
|
42725
|
+
"agent-screen-rules-source",
|
|
42726
|
+
"agent-screen-rules-error",
|
|
42727
|
+
"agent-screen-rules-highlight-error"
|
|
42728
|
+
].join(" ")
|
|
42729
|
+
);
|
|
42730
|
+
const agentRulesEditor = document.createElement("div");
|
|
42731
|
+
agentRulesEditor.className = "agent-screen-rules-editor";
|
|
42732
|
+
agentRulesEditor.append(agentRulesHighlight, agentRules);
|
|
42733
|
+
agentRulesGuide.append(
|
|
42734
|
+
agentRulesGuideTitle,
|
|
42735
|
+
agentRulesGuideIntro,
|
|
42736
|
+
agentRulesGuideFields,
|
|
42737
|
+
agentRulesGuideMatchers,
|
|
42738
|
+
agentRulesGuideRegions,
|
|
42739
|
+
agentRulesGuideExample
|
|
42740
|
+
);
|
|
42741
|
+
agentRulesSave.type = "button";
|
|
42742
|
+
agentRulesSave.id = "agent-screen-rules-save";
|
|
42743
|
+
agentRulesReset.type = "button";
|
|
42744
|
+
agentRulesReset.id = "agent-screen-rules-reset";
|
|
42745
|
+
const agentRuleActions = document.createElement("div");
|
|
42746
|
+
agentRuleActions.className = "scope-settings-actions";
|
|
42747
|
+
agentRuleActions.append(agentRulesReset, agentRulesSave);
|
|
42748
|
+
const ruleSettings = section();
|
|
42749
|
+
ruleSettings.classList.add("agent-screen-rules-section");
|
|
42750
|
+
ruleSettings.append(
|
|
42751
|
+
agentRulesTitle,
|
|
42752
|
+
agentRulesLabel,
|
|
42753
|
+
agentRulesHelp,
|
|
42754
|
+
agentRulesGuide,
|
|
42755
|
+
agentRulesEditor,
|
|
42756
|
+
agentRulesSource,
|
|
42757
|
+
agentRulesError,
|
|
42758
|
+
agentRulesHighlightError,
|
|
42759
|
+
agentRuleActions
|
|
42760
|
+
);
|
|
42427
42761
|
resetButton.id = "scope-omit-reset";
|
|
42428
42762
|
resetButton.type = "button";
|
|
42763
|
+
saveButton.id = "scope-settings-save";
|
|
42764
|
+
saveButton.type = "button";
|
|
42765
|
+
saveButton.className = "scope-settings-primary-action";
|
|
42766
|
+
const generalActions = document.createElement("div");
|
|
42767
|
+
generalActions.className = "scope-settings-actions";
|
|
42768
|
+
generalActions.append(resetButton, saveButton);
|
|
42429
42769
|
const footer = document.createElement("div");
|
|
42430
42770
|
footer.className = "scope-settings-footer";
|
|
42431
|
-
footer.append(
|
|
42432
|
-
|
|
42771
|
+
footer.append(
|
|
42772
|
+
saveNote,
|
|
42773
|
+
saveStatus,
|
|
42774
|
+
saveError,
|
|
42775
|
+
refreshError,
|
|
42776
|
+
generalActions
|
|
42777
|
+
);
|
|
42778
|
+
wrap.append(
|
|
42779
|
+
display,
|
|
42780
|
+
uploads,
|
|
42781
|
+
excluded,
|
|
42782
|
+
datastores,
|
|
42783
|
+
watch,
|
|
42784
|
+
ruleSettings,
|
|
42785
|
+
footer
|
|
42786
|
+
);
|
|
42433
42787
|
wire();
|
|
42788
|
+
void initializeJsonHighlighting();
|
|
42434
42789
|
return wrap;
|
|
42435
42790
|
}
|
|
42436
42791
|
function wire() {
|
|
42437
|
-
language.addEventListener(
|
|
42438
|
-
"change",
|
|
42439
|
-
() => deps.onLanguageChange(language.value)
|
|
42440
|
-
);
|
|
42792
|
+
language.addEventListener("change", () => markGeneralDirty("language"));
|
|
42441
42793
|
sidebarFontSize.addEventListener(
|
|
42442
42794
|
"change",
|
|
42443
|
-
() =>
|
|
42795
|
+
() => markGeneralDirty("sidebarFontSize")
|
|
42444
42796
|
);
|
|
42445
42797
|
codeFontSize.addEventListener(
|
|
42446
42798
|
"change",
|
|
42447
|
-
() =>
|
|
42799
|
+
() => markGeneralDirty("codeFontSize")
|
|
42448
42800
|
);
|
|
42449
42801
|
upload.input.addEventListener(
|
|
42450
42802
|
"change",
|
|
42451
|
-
() =>
|
|
42452
|
-
);
|
|
42453
|
-
omitDirs.addEventListener(
|
|
42454
|
-
"change",
|
|
42455
|
-
() => deps.onOmitDirsChange(omitDirs.value)
|
|
42456
|
-
);
|
|
42457
|
-
excludeNames.addEventListener(
|
|
42458
|
-
"change",
|
|
42459
|
-
() => deps.onExcludeNamesChange(excludeNames.value)
|
|
42803
|
+
() => markGeneralDirty("uploadEnabled")
|
|
42460
42804
|
);
|
|
42461
42805
|
inferFk.input.addEventListener(
|
|
42462
42806
|
"change",
|
|
42463
|
-
() =>
|
|
42807
|
+
() => markGeneralDirty("inferFkRails")
|
|
42464
42808
|
);
|
|
42465
42809
|
s3Tooltip.input.addEventListener(
|
|
42466
42810
|
"change",
|
|
42467
|
-
() =>
|
|
42811
|
+
() => markGeneralDirty("s3TooltipEnabled")
|
|
42468
42812
|
);
|
|
42813
|
+
omitDirs.addEventListener("input", () => markGeneralDirty("omitDirs"));
|
|
42814
|
+
excludeNames.addEventListener(
|
|
42815
|
+
"input",
|
|
42816
|
+
() => markGeneralDirty("excludeNames")
|
|
42817
|
+
);
|
|
42818
|
+
watchLimitNumber.addEventListener("input", () => {
|
|
42819
|
+
watchLimitRange.value = watchLimitNumber.value;
|
|
42820
|
+
markGeneralDirty("watchLimit");
|
|
42821
|
+
});
|
|
42469
42822
|
watchLimitNumber.addEventListener("change", () => {
|
|
42470
42823
|
watchLimitRange.value = watchLimitNumber.value;
|
|
42471
|
-
|
|
42824
|
+
markGeneralDirty("watchLimit");
|
|
42472
42825
|
});
|
|
42473
42826
|
watchLimitRange.addEventListener("input", () => {
|
|
42474
42827
|
watchLimitNumber.value = watchLimitRange.value;
|
|
42828
|
+
markGeneralDirty("watchLimit");
|
|
42475
42829
|
});
|
|
42476
42830
|
watchLimitRange.addEventListener(
|
|
42477
42831
|
"change",
|
|
42478
|
-
() =>
|
|
42832
|
+
() => markGeneralDirty("watchLimit")
|
|
42479
42833
|
);
|
|
42480
|
-
|
|
42481
|
-
|
|
42482
|
-
|
|
42834
|
+
agentRules.addEventListener("input", () => {
|
|
42835
|
+
agentRulesDirty = true;
|
|
42836
|
+
syncAgentRulesHighlight();
|
|
42837
|
+
});
|
|
42838
|
+
agentRules.addEventListener("scroll", syncAgentRulesScroll);
|
|
42839
|
+
agentRulesSave.addEventListener("click", () => {
|
|
42840
|
+
void updateAgentRules(() => deps.onAgentRulesSave(agentRules.value));
|
|
42483
42841
|
});
|
|
42842
|
+
agentRulesReset.addEventListener("click", () => {
|
|
42843
|
+
void updateAgentRules(() => deps.onAgentRulesReset());
|
|
42844
|
+
});
|
|
42845
|
+
resetButton.addEventListener("click", () => {
|
|
42846
|
+
applyGeneralFields(deps.getDefaultValues(), true);
|
|
42847
|
+
restoreDefaults = true;
|
|
42848
|
+
changedGeneralFields.clear();
|
|
42849
|
+
for (const field2 of GENERAL_SETTING_FIELDS) {
|
|
42850
|
+
changedGeneralFields.add(field2);
|
|
42851
|
+
}
|
|
42852
|
+
generalDirty = true;
|
|
42853
|
+
generalStatus = "dirty";
|
|
42854
|
+
clearGeneralSaveError();
|
|
42855
|
+
renderGeneralSaveState();
|
|
42856
|
+
});
|
|
42857
|
+
saveButton.addEventListener("click", () => {
|
|
42858
|
+
void saveGeneralSettings();
|
|
42859
|
+
});
|
|
42860
|
+
}
|
|
42861
|
+
function markGeneralDirty(field2) {
|
|
42862
|
+
if (generalSavePending) return;
|
|
42863
|
+
changedGeneralFields.add(field2);
|
|
42864
|
+
generalDirty = true;
|
|
42865
|
+
restoreDefaults = false;
|
|
42866
|
+
generalStatus = "dirty";
|
|
42867
|
+
watchLimitNumber.setCustomValidity("");
|
|
42868
|
+
clearGeneralSaveError();
|
|
42869
|
+
renderGeneralSaveState();
|
|
42870
|
+
}
|
|
42871
|
+
function clearGeneralSaveError() {
|
|
42872
|
+
saveError.textContent = "";
|
|
42873
|
+
saveError.hidden = true;
|
|
42874
|
+
}
|
|
42875
|
+
function renderGeneralSaveState() {
|
|
42876
|
+
const text3 = deps.getText();
|
|
42877
|
+
saveButton.textContent = generalStatus === "saving" ? text3.saving : text3.save;
|
|
42878
|
+
saveButton.disabled = generalSavePending || !generalDirty;
|
|
42879
|
+
resetButton.disabled = generalSavePending;
|
|
42880
|
+
saveStatus.textContent = generalStatus === "saving" ? text3.saving : generalStatus === "saved" ? text3.saved : generalDirty ? text3.unsaved : "";
|
|
42881
|
+
}
|
|
42882
|
+
function setGeneralControlsDisabled(disabled) {
|
|
42883
|
+
for (const field2 of [
|
|
42884
|
+
language,
|
|
42885
|
+
sidebarFontSize,
|
|
42886
|
+
codeFontSize,
|
|
42887
|
+
upload.input,
|
|
42888
|
+
omitDirs,
|
|
42889
|
+
excludeNames,
|
|
42890
|
+
inferFk.input,
|
|
42891
|
+
s3Tooltip.input,
|
|
42892
|
+
watchLimitNumber,
|
|
42893
|
+
watchLimitRange
|
|
42894
|
+
]) {
|
|
42895
|
+
field2.disabled = disabled;
|
|
42896
|
+
}
|
|
42897
|
+
}
|
|
42898
|
+
function applyGeneralFields(values, force = false) {
|
|
42899
|
+
setFieldValue(language, values.language, force);
|
|
42900
|
+
setFieldValue(sidebarFontSize, values.sidebarFontSize, force);
|
|
42901
|
+
setFieldValue(codeFontSize, values.codeFontSize, force);
|
|
42902
|
+
setFieldValue(omitDirs, values.omitDirs, force);
|
|
42903
|
+
setFieldValue(excludeNames, values.excludeNames, force);
|
|
42904
|
+
setFieldValue(watchLimitNumber, String(values.watchLimit), force);
|
|
42905
|
+
setFieldValue(watchLimitRange, String(values.watchLimit), force);
|
|
42906
|
+
upload.input.checked = values.uploadEnabled;
|
|
42907
|
+
inferFk.input.checked = values.inferFkRails;
|
|
42908
|
+
s3Tooltip.input.checked = values.s3TooltipEnabled;
|
|
42909
|
+
}
|
|
42910
|
+
function readGeneralDraft() {
|
|
42911
|
+
const values = deps.getValues();
|
|
42912
|
+
const watchLimit = Number(watchLimitNumber.value);
|
|
42913
|
+
if (!Number.isInteger(watchLimit) || watchLimit < values.watchLimitMin || watchLimit > values.watchLimitMax) {
|
|
42914
|
+
const message = deps.getText().watchLimitInvalid(values.watchLimitMin, values.watchLimitMax);
|
|
42915
|
+
watchLimitNumber.setCustomValidity(message);
|
|
42916
|
+
saveError.textContent = message;
|
|
42917
|
+
saveError.hidden = false;
|
|
42918
|
+
generalStatus = "dirty";
|
|
42919
|
+
renderGeneralSaveState();
|
|
42920
|
+
return null;
|
|
42921
|
+
}
|
|
42922
|
+
watchLimitNumber.setCustomValidity("");
|
|
42923
|
+
return {
|
|
42924
|
+
language: language.value,
|
|
42925
|
+
sidebarFontSize: sidebarFontSize.value,
|
|
42926
|
+
codeFontSize: codeFontSize.value,
|
|
42927
|
+
omitDirs: omitDirs.value,
|
|
42928
|
+
excludeNames: excludeNames.value,
|
|
42929
|
+
watchLimit,
|
|
42930
|
+
uploadEnabled: upload.input.checked,
|
|
42931
|
+
inferFkRails: inferFk.input.checked,
|
|
42932
|
+
s3TooltipEnabled: s3Tooltip.input.checked
|
|
42933
|
+
};
|
|
42934
|
+
}
|
|
42935
|
+
async function saveGeneralSettings() {
|
|
42936
|
+
if (generalSavePending || !generalDirty) return;
|
|
42937
|
+
const draft = readGeneralDraft();
|
|
42938
|
+
if (!draft) return;
|
|
42939
|
+
refreshGeneration += 1;
|
|
42940
|
+
generalSavePending = true;
|
|
42941
|
+
generalStatus = "saving";
|
|
42942
|
+
clearGeneralSaveError();
|
|
42943
|
+
setGeneralControlsDisabled(true);
|
|
42944
|
+
renderGeneralSaveState();
|
|
42945
|
+
let completed = false;
|
|
42946
|
+
try {
|
|
42947
|
+
await deps.onSave(draft, {
|
|
42948
|
+
restoreDefaults,
|
|
42949
|
+
changedFields: [...changedGeneralFields]
|
|
42950
|
+
});
|
|
42951
|
+
generalDirty = false;
|
|
42952
|
+
restoreDefaults = false;
|
|
42953
|
+
changedGeneralFields.clear();
|
|
42954
|
+
generalStatus = "saved";
|
|
42955
|
+
completed = true;
|
|
42956
|
+
} catch (error2) {
|
|
42957
|
+
console.error("[code-viewer] viewer settings save failed", error2);
|
|
42958
|
+
saveError.textContent = formatErrorDetail(error2);
|
|
42959
|
+
saveError.hidden = false;
|
|
42960
|
+
generalStatus = "dirty";
|
|
42961
|
+
} finally {
|
|
42962
|
+
generalSavePending = false;
|
|
42963
|
+
setGeneralControlsDisabled(false);
|
|
42964
|
+
if (completed) sync();
|
|
42965
|
+
else renderGeneralSaveState();
|
|
42966
|
+
}
|
|
42967
|
+
}
|
|
42968
|
+
async function initializeJsonHighlighting() {
|
|
42969
|
+
try {
|
|
42970
|
+
jsonHighlighter = await loadShikiHighlighter({
|
|
42971
|
+
themes: ["github-light", "github-dark"],
|
|
42972
|
+
langs: ["json"],
|
|
42973
|
+
failureMode: "throw"
|
|
42974
|
+
});
|
|
42975
|
+
if (!jsonHighlighter) {
|
|
42976
|
+
throw new Error("JSON syntax highlighting could not be loaded");
|
|
42977
|
+
}
|
|
42978
|
+
syncAgentRulesHighlight();
|
|
42979
|
+
} catch (error2) {
|
|
42980
|
+
console.error("[code-viewer] JSON syntax highlighting failed", error2);
|
|
42981
|
+
agentRulesHighlightError.textContent = formatErrorDetail(error2);
|
|
42982
|
+
agentRulesHighlightError.hidden = false;
|
|
42983
|
+
}
|
|
42984
|
+
}
|
|
42985
|
+
function syncAgentRulesHighlight() {
|
|
42986
|
+
const highlighted = highlightToInnerHtml(
|
|
42987
|
+
agentRules.value,
|
|
42988
|
+
"json",
|
|
42989
|
+
jsonHighlighter
|
|
42990
|
+
);
|
|
42991
|
+
if (highlighted) agentRulesHighlight.innerHTML = highlighted;
|
|
42992
|
+
else agentRulesHighlight.textContent = agentRules.value;
|
|
42993
|
+
syncAgentRulesScroll();
|
|
42994
|
+
}
|
|
42995
|
+
function syncAgentRulesScroll() {
|
|
42996
|
+
agentRulesHighlight.scrollTop = agentRules.scrollTop;
|
|
42997
|
+
agentRulesHighlight.scrollLeft = agentRules.scrollLeft;
|
|
42998
|
+
}
|
|
42999
|
+
async function updateAgentRules(operation) {
|
|
43000
|
+
if (agentRulesPending) return;
|
|
43001
|
+
refreshGeneration += 1;
|
|
43002
|
+
agentRulesPending = true;
|
|
43003
|
+
agentRules.disabled = true;
|
|
43004
|
+
agentRulesSave.disabled = true;
|
|
43005
|
+
agentRulesReset.disabled = true;
|
|
43006
|
+
agentRulesSource.textContent = deps.getText().agentRulesSaving;
|
|
43007
|
+
agentRulesError.hidden = true;
|
|
43008
|
+
agentRulesError.textContent = "";
|
|
43009
|
+
let completed = false;
|
|
43010
|
+
try {
|
|
43011
|
+
await operation();
|
|
43012
|
+
agentRulesDirty = false;
|
|
43013
|
+
completed = true;
|
|
43014
|
+
} catch (error2) {
|
|
43015
|
+
console.error("[code-viewer] terminal rule update failed", error2);
|
|
43016
|
+
agentRulesError.textContent = formatErrorDetail(error2);
|
|
43017
|
+
agentRulesError.hidden = false;
|
|
43018
|
+
} finally {
|
|
43019
|
+
agentRulesPending = false;
|
|
43020
|
+
agentRules.disabled = false;
|
|
43021
|
+
agentRulesSave.disabled = false;
|
|
43022
|
+
agentRulesReset.disabled = false;
|
|
43023
|
+
if (completed) {
|
|
43024
|
+
sync();
|
|
43025
|
+
} else {
|
|
43026
|
+
agentRulesSource.textContent = deps.getValues().agentRulesSource === "saved" ? deps.getText().agentRulesSourceSaved : deps.getText().agentRulesSourceDefault;
|
|
43027
|
+
}
|
|
43028
|
+
}
|
|
42484
43029
|
}
|
|
42485
43030
|
function applyText() {
|
|
42486
43031
|
const text3 = deps.getText();
|
|
@@ -42490,12 +43035,14 @@ ${formatErrorDetail(error2)}`);
|
|
|
42490
43035
|
excludedTitle.textContent = text3.excludedDirectories;
|
|
42491
43036
|
datastoreTitle.textContent = text3.datastoreTitle;
|
|
42492
43037
|
watchTitle.textContent = text3.watchTitle;
|
|
43038
|
+
agentRulesTitle.textContent = text3.agentRulesTitle;
|
|
42493
43039
|
languageLabel.textContent = text3.language;
|
|
42494
43040
|
sidebarFontSizeLabel.textContent = text3.fileListFontSize;
|
|
42495
43041
|
codeFontSizeLabel.textContent = text3.codeFontSize;
|
|
42496
43042
|
omitDirsLabel.textContent = text3.omitDirs;
|
|
42497
43043
|
excludeNamesLabel.textContent = text3.excludeNames;
|
|
42498
43044
|
watchLimitLabel.textContent = text3.watchLimit;
|
|
43045
|
+
agentRulesLabel.textContent = text3.agentRulesLabel;
|
|
42499
43046
|
uiFontSizeHelp.textContent = text3.fileListFontSizeHelp;
|
|
42500
43047
|
displaySource.textContent = text3.displaySource;
|
|
42501
43048
|
upload.text.textContent = text3.uploadEnabledLabel;
|
|
@@ -42507,37 +43054,49 @@ ${formatErrorDetail(error2)}`);
|
|
|
42507
43054
|
s3Tooltip.text.textContent = text3.datastoreS3TooltipLabel;
|
|
42508
43055
|
s3TooltipHelp.textContent = text3.datastoreS3TooltipHelp;
|
|
42509
43056
|
watchLimitHelp.textContent = text3.watchLimitHelp(values.watchLimitDefault);
|
|
42510
|
-
|
|
43057
|
+
agentRulesHelp.textContent = text3.agentRulesHelp;
|
|
43058
|
+
agentRulesGuideTitle.textContent = text3.agentRulesGuideTitle;
|
|
43059
|
+
agentRulesGuideIntro.textContent = text3.agentRulesGuideIntro;
|
|
43060
|
+
agentRulesGuideFields.textContent = text3.agentRulesGuideFields;
|
|
43061
|
+
agentRulesGuideMatchers.textContent = text3.agentRulesGuideMatchers;
|
|
43062
|
+
agentRulesGuideRegions.textContent = text3.agentRulesGuideRegions;
|
|
43063
|
+
agentRulesGuideExample.textContent = text3.agentRulesGuideExample;
|
|
43064
|
+
agentRulesSave.textContent = text3.agentRulesSave;
|
|
43065
|
+
agentRulesReset.textContent = text3.agentRulesReset;
|
|
43066
|
+
saveNote.textContent = text3.saveNote;
|
|
42511
43067
|
resetButton.textContent = text3.reset;
|
|
43068
|
+
renderGeneralSaveState();
|
|
42512
43069
|
const sizeLabels = {
|
|
42513
43070
|
compact: text3.sizeSmall,
|
|
42514
43071
|
regular: text3.sizeRegular,
|
|
42515
43072
|
large: text3.sizeLarge,
|
|
42516
43073
|
xlarge: text3.sizeExtraLarge
|
|
42517
43074
|
};
|
|
42518
|
-
for (const select of [sidebarFontSize, codeFontSize])
|
|
42519
|
-
for (const option of Array.from(select.options))
|
|
43075
|
+
for (const select of [sidebarFontSize, codeFontSize]) {
|
|
43076
|
+
for (const option of Array.from(select.options)) {
|
|
42520
43077
|
option.textContent = sizeLabels[option.value] || option.value;
|
|
43078
|
+
}
|
|
43079
|
+
}
|
|
42521
43080
|
}
|
|
42522
43081
|
function sync() {
|
|
42523
43082
|
if (!root) return;
|
|
42524
43083
|
applyText();
|
|
42525
43084
|
const values = deps.getValues();
|
|
42526
|
-
setFieldValue(language, values.language);
|
|
42527
|
-
setFieldValue(sidebarFontSize, values.sidebarFontSize);
|
|
42528
|
-
setFieldValue(codeFontSize, values.codeFontSize);
|
|
42529
|
-
setFieldValue(omitDirs, values.omitDirs);
|
|
42530
|
-
setFieldValue(excludeNames, values.excludeNames);
|
|
42531
43085
|
watchLimitNumber.min = String(values.watchLimitMin);
|
|
42532
43086
|
watchLimitNumber.max = String(values.watchLimitMax);
|
|
42533
43087
|
watchLimitRange.min = String(values.watchLimitMin);
|
|
42534
43088
|
watchLimitRange.max = String(values.watchLimitMax);
|
|
42535
|
-
|
|
42536
|
-
setFieldValue(watchLimitRange, String(values.watchLimit));
|
|
42537
|
-
upload.input.checked = values.uploadEnabled;
|
|
42538
|
-
inferFk.input.checked = values.inferFkRails;
|
|
42539
|
-
s3Tooltip.input.checked = values.s3TooltipEnabled;
|
|
43089
|
+
if (!generalDirty && !generalSavePending) applyGeneralFields(values);
|
|
42540
43090
|
scopeSource.textContent = values.scopeSource;
|
|
43091
|
+
if (!agentRulesDirty && !agentRulesPending) {
|
|
43092
|
+
setFieldValue(agentRules, values.agentRulesJson);
|
|
43093
|
+
syncAgentRulesHighlight();
|
|
43094
|
+
}
|
|
43095
|
+
if (!agentRulesPending) {
|
|
43096
|
+
agentRulesSource.textContent = values.agentRulesSource === "saved" ? deps.getText().agentRulesSourceSaved : deps.getText().agentRulesSourceDefault;
|
|
43097
|
+
agentRulesError.textContent = values.agentRulesErrors;
|
|
43098
|
+
agentRulesError.hidden = !values.agentRulesErrors;
|
|
43099
|
+
}
|
|
42541
43100
|
}
|
|
42542
43101
|
function mount(host) {
|
|
42543
43102
|
if (!root) root = build();
|
|
@@ -42557,11 +43116,19 @@ ${formatErrorDetail(error2)}`);
|
|
|
42557
43116
|
sync();
|
|
42558
43117
|
refreshError.hidden = true;
|
|
42559
43118
|
refreshError.textContent = "";
|
|
42560
|
-
|
|
42561
|
-
|
|
42562
|
-
|
|
42563
|
-
|
|
42564
|
-
|
|
43119
|
+
const generation = ++refreshGeneration;
|
|
43120
|
+
void deps.refresh().then(
|
|
43121
|
+
() => {
|
|
43122
|
+
if (generation !== refreshGeneration) return;
|
|
43123
|
+
sync();
|
|
43124
|
+
},
|
|
43125
|
+
(error2) => {
|
|
43126
|
+
if (generation !== refreshGeneration) return;
|
|
43127
|
+
console.error("[code-viewer] viewer settings refresh failed", error2);
|
|
43128
|
+
refreshError.textContent = formatErrorDetail(error2);
|
|
43129
|
+
refreshError.hidden = false;
|
|
43130
|
+
}
|
|
43131
|
+
);
|
|
42565
43132
|
}
|
|
42566
43133
|
function localize() {
|
|
42567
43134
|
if (!root) return;
|
|
@@ -42591,6 +43158,11 @@ ${formatErrorDetail(error2)}`);
|
|
|
42591
43158
|
let PROJECT_BRANCH = "";
|
|
42592
43159
|
let REPO_WEB_URL = null;
|
|
42593
43160
|
let APP_SETTINGS = { version: 1 };
|
|
43161
|
+
let AGENT_SCREEN_RULES = formatAgentScreenRuleSet(DEFAULT_AGENT_SCREEN_RULES);
|
|
43162
|
+
let AGENT_SCREEN_RULES_SOURCE = "default";
|
|
43163
|
+
let AGENT_SCREEN_RULE_ERRORS = [];
|
|
43164
|
+
let AGENT_SCREEN_RULES_GENERATION = 0;
|
|
43165
|
+
let AGENT_SCREEN_RULE_REQUEST_GENERATION = 0;
|
|
42594
43166
|
let VIEW_STATE = {
|
|
42595
43167
|
version: 1,
|
|
42596
43168
|
collapsedDirs: [],
|
|
@@ -42759,6 +43331,13 @@ ${formatErrorDetail(error2)}`);
|
|
|
42759
43331
|
"X-Code-Viewer-Action": "1"
|
|
42760
43332
|
};
|
|
42761
43333
|
}
|
|
43334
|
+
function reportPersistenceError(operation, error2) {
|
|
43335
|
+
const failure = errorWithCause(`${operation} failed`, error2);
|
|
43336
|
+
console.error(failure);
|
|
43337
|
+
setStatus("error");
|
|
43338
|
+
const statusEl = document.querySelector("#status");
|
|
43339
|
+
if (statusEl) statusEl.title = formatErrorDetail(failure);
|
|
43340
|
+
}
|
|
42762
43341
|
let cachedKeymapOverrides;
|
|
42763
43342
|
let cachedKeyBindings = DEFAULT_KEY_BINDINGS;
|
|
42764
43343
|
function activeKeyBindings() {
|
|
@@ -42768,29 +43347,95 @@ ${formatErrorDetail(error2)}`);
|
|
|
42768
43347
|
}
|
|
42769
43348
|
return cachedKeyBindings;
|
|
42770
43349
|
}
|
|
42771
|
-
|
|
42772
|
-
|
|
42773
|
-
|
|
42774
|
-
|
|
42775
|
-
|
|
42776
|
-
|
|
42777
|
-
|
|
42778
|
-
|
|
42779
|
-
}
|
|
43350
|
+
let pendingSettingsPatch = null;
|
|
43351
|
+
let pendingSettingsKeepalive = false;
|
|
43352
|
+
let settingsPatchInFlight = null;
|
|
43353
|
+
async function flushSettingsPatch() {
|
|
43354
|
+
if (settingsPatchInFlight) {
|
|
43355
|
+
await settingsPatchInFlight;
|
|
43356
|
+
if (pendingSettingsPatch) await flushSettingsPatch();
|
|
43357
|
+
return;
|
|
43358
|
+
}
|
|
43359
|
+
if (!pendingSettingsPatch) return;
|
|
43360
|
+
const patch = pendingSettingsPatch;
|
|
43361
|
+
const keepalive = pendingSettingsKeepalive;
|
|
43362
|
+
pendingSettingsPatch = null;
|
|
43363
|
+
pendingSettingsKeepalive = false;
|
|
43364
|
+
const operation = sendSettingsPatch(patch, keepalive).then(() => void 0);
|
|
43365
|
+
settingsPatchInFlight = operation;
|
|
43366
|
+
try {
|
|
43367
|
+
await operation;
|
|
43368
|
+
} catch (error2) {
|
|
43369
|
+
pendingSettingsPatch = {
|
|
43370
|
+
...patch,
|
|
43371
|
+
...pendingSettingsPatch || {}
|
|
43372
|
+
};
|
|
43373
|
+
pendingSettingsKeepalive ||= keepalive;
|
|
43374
|
+
throw error2;
|
|
43375
|
+
} finally {
|
|
43376
|
+
if (settingsPatchInFlight === operation) settingsPatchInFlight = null;
|
|
43377
|
+
}
|
|
43378
|
+
if (pendingSettingsPatch) await flushSettingsPatch();
|
|
42780
43379
|
}
|
|
42781
|
-
async function
|
|
43380
|
+
async function sendSettingsPatch(patch, keepalive = false) {
|
|
42782
43381
|
const response = await trackLoad(
|
|
42783
43382
|
fetch("/_state/settings", {
|
|
42784
43383
|
method: "PATCH",
|
|
42785
43384
|
headers: actionHeaders(),
|
|
42786
|
-
body: JSON.stringify(patch)
|
|
43385
|
+
body: JSON.stringify(patch),
|
|
43386
|
+
keepalive
|
|
42787
43387
|
})
|
|
42788
43388
|
);
|
|
42789
|
-
if (!response.ok)
|
|
42790
|
-
|
|
43389
|
+
if (!response.ok) {
|
|
43390
|
+
throw new Error(
|
|
43391
|
+
await responseErrorMessage(response, "save viewer settings")
|
|
43392
|
+
);
|
|
43393
|
+
}
|
|
43394
|
+
try {
|
|
43395
|
+
return await response.json();
|
|
43396
|
+
} catch (error2) {
|
|
43397
|
+
throw errorWithCause(
|
|
43398
|
+
"save viewer settings: response is not valid JSON",
|
|
43399
|
+
error2
|
|
43400
|
+
);
|
|
43401
|
+
}
|
|
43402
|
+
}
|
|
43403
|
+
function patchSettings(patch, options = {}) {
|
|
43404
|
+
mergeLocalSettings(patch);
|
|
43405
|
+
pendingSettingsPatch = {
|
|
43406
|
+
...pendingSettingsPatch || {},
|
|
43407
|
+
...patch
|
|
43408
|
+
};
|
|
43409
|
+
pendingSettingsKeepalive ||= options.keepalive === true;
|
|
43410
|
+
if (!settingsPatchInFlight) {
|
|
43411
|
+
void flushSettingsPatch().catch((error2) => {
|
|
43412
|
+
reportPersistenceError("save viewer settings", error2);
|
|
43413
|
+
});
|
|
43414
|
+
}
|
|
43415
|
+
}
|
|
43416
|
+
async function persistSettingsPatch(patch) {
|
|
43417
|
+
await flushSettingsPatch();
|
|
43418
|
+
while (settingsPatchInFlight) await settingsPatchInFlight;
|
|
43419
|
+
const operation = sendSettingsPatch(patch).then((state) => {
|
|
43420
|
+
APP_SETTINGS = state;
|
|
43421
|
+
if (pendingSettingsPatch) mergeLocalSettings(pendingSettingsPatch);
|
|
43422
|
+
});
|
|
43423
|
+
settingsPatchInFlight = operation;
|
|
43424
|
+
try {
|
|
43425
|
+
await operation;
|
|
43426
|
+
} finally {
|
|
43427
|
+
if (settingsPatchInFlight === operation) settingsPatchInFlight = null;
|
|
43428
|
+
if (pendingSettingsPatch) {
|
|
43429
|
+
void flushSettingsPatch().catch((error2) => {
|
|
43430
|
+
reportPersistenceError("save viewer settings", error2);
|
|
43431
|
+
});
|
|
43432
|
+
}
|
|
43433
|
+
}
|
|
42791
43434
|
}
|
|
42792
43435
|
let pendingViewPatch = null;
|
|
42793
43436
|
let pendingViewTimer = null;
|
|
43437
|
+
let pendingViewKeepalive = false;
|
|
43438
|
+
let viewPatchInFlight = false;
|
|
42794
43439
|
function mergePathDelta(next, base2, patch, addKey, removeKey) {
|
|
42795
43440
|
const added = new Set(base2?.[addKey] || []);
|
|
42796
43441
|
const removed = new Set(base2?.[removeKey] || []);
|
|
@@ -42852,19 +43497,44 @@ ${formatErrorDetail(error2)}`);
|
|
|
42852
43497
|
viewedFiles: [...viewedFiles]
|
|
42853
43498
|
};
|
|
42854
43499
|
}
|
|
43500
|
+
async function sendPendingViewPatch() {
|
|
43501
|
+
if (viewPatchInFlight || !pendingViewPatch) return;
|
|
43502
|
+
const patch = pendingViewPatch;
|
|
43503
|
+
const keepalive = pendingViewKeepalive;
|
|
43504
|
+
pendingViewPatch = null;
|
|
43505
|
+
pendingViewKeepalive = false;
|
|
43506
|
+
viewPatchInFlight = true;
|
|
43507
|
+
let saved = false;
|
|
43508
|
+
try {
|
|
43509
|
+
const response = await trackLoad(
|
|
43510
|
+
fetch("/_state/view", {
|
|
43511
|
+
method: "PATCH",
|
|
43512
|
+
headers: actionHeaders(),
|
|
43513
|
+
body: JSON.stringify(patch),
|
|
43514
|
+
keepalive
|
|
43515
|
+
})
|
|
43516
|
+
);
|
|
43517
|
+
if (!response.ok) {
|
|
43518
|
+
throw new Error(
|
|
43519
|
+
await responseErrorMessage(response, "save viewer state")
|
|
43520
|
+
);
|
|
43521
|
+
}
|
|
43522
|
+
saved = true;
|
|
43523
|
+
} catch (error2) {
|
|
43524
|
+
pendingViewPatch = mergeViewPatch(patch, pendingViewPatch || {});
|
|
43525
|
+
reportPersistenceError("save viewer state", error2);
|
|
43526
|
+
} finally {
|
|
43527
|
+
viewPatchInFlight = false;
|
|
43528
|
+
}
|
|
43529
|
+
if (saved && pendingViewPatch) void sendPendingViewPatch();
|
|
43530
|
+
}
|
|
42855
43531
|
function patchViewState(patch, options = {}) {
|
|
42856
43532
|
VIEW_STATE = mergeLocalViewState(VIEW_STATE, patch);
|
|
42857
43533
|
pendingViewPatch = mergeViewPatch(pendingViewPatch, patch);
|
|
42858
43534
|
const send = (keepalive = false) => {
|
|
42859
43535
|
if (!pendingViewPatch) return;
|
|
42860
|
-
|
|
42861
|
-
|
|
42862
|
-
void fetch("/_state/view", {
|
|
42863
|
-
method: "PATCH",
|
|
42864
|
-
headers: actionHeaders(),
|
|
42865
|
-
body,
|
|
42866
|
-
keepalive
|
|
42867
|
-
}).catch(() => void 0);
|
|
43536
|
+
pendingViewKeepalive ||= keepalive;
|
|
43537
|
+
void sendPendingViewPatch();
|
|
42868
43538
|
};
|
|
42869
43539
|
if (options.keepalive) {
|
|
42870
43540
|
if (pendingViewTimer !== null) clearTimeout(pendingViewTimer);
|
|
@@ -42888,14 +43558,8 @@ ${formatErrorDetail(error2)}`);
|
|
|
42888
43558
|
if (!pendingViewPatch) return;
|
|
42889
43559
|
if (pendingViewTimer !== null) clearTimeout(pendingViewTimer);
|
|
42890
43560
|
pendingViewTimer = null;
|
|
42891
|
-
|
|
42892
|
-
|
|
42893
|
-
void fetch("/_state/view", {
|
|
42894
|
-
method: "PATCH",
|
|
42895
|
-
headers: actionHeaders(),
|
|
42896
|
-
body,
|
|
42897
|
-
keepalive
|
|
42898
|
-
}).catch(() => void 0);
|
|
43561
|
+
pendingViewKeepalive ||= keepalive;
|
|
43562
|
+
void sendPendingViewPatch();
|
|
42899
43563
|
}
|
|
42900
43564
|
function savedScopeOmitDirs() {
|
|
42901
43565
|
return APP_SETTINGS.scopeOmitDirs ? normalizeScopeOmitDirs(APP_SETTINGS.scopeOmitDirs) : null;
|
|
@@ -42975,50 +43639,152 @@ ${formatErrorDetail(error2)}`);
|
|
|
42975
43639
|
return `${ref}\0${omit ? omit.join("\0") : "server"}\0${exclude ? exclude.join("\0") : "server"}`;
|
|
42976
43640
|
}
|
|
42977
43641
|
async function loadSettings() {
|
|
43642
|
+
const res = await trackLoad(fetch("/_settings"));
|
|
43643
|
+
if (!res.ok) {
|
|
43644
|
+
throw new Error(
|
|
43645
|
+
await responseErrorMessage(res, "settings request failed")
|
|
43646
|
+
);
|
|
43647
|
+
}
|
|
43648
|
+
let settings;
|
|
42978
43649
|
try {
|
|
42979
|
-
|
|
42980
|
-
|
|
42981
|
-
|
|
42982
|
-
|
|
42983
|
-
|
|
42984
|
-
|
|
42985
|
-
|
|
42986
|
-
|
|
42987
|
-
|
|
42988
|
-
|
|
42989
|
-
|
|
42990
|
-
|
|
42991
|
-
|
|
43650
|
+
settings = await res.json();
|
|
43651
|
+
} catch (error2) {
|
|
43652
|
+
throw errorWithCause("settings response is not valid JSON", error2);
|
|
43653
|
+
}
|
|
43654
|
+
setProjectName(settings.project || "");
|
|
43655
|
+
setProjectBranch(settings.branch || "");
|
|
43656
|
+
REPO_WEB_URL = settings.repo_web_url;
|
|
43657
|
+
const repoLink = document.querySelector("#repo-web-link");
|
|
43658
|
+
if (repoLink) {
|
|
43659
|
+
repoLink.href = settings.repo_web_url || "#";
|
|
43660
|
+
repoLink.hidden = !settings.repo_web_url;
|
|
43661
|
+
}
|
|
43662
|
+
SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(
|
|
43663
|
+
settings.scope.omit_dirs_effective
|
|
43664
|
+
);
|
|
43665
|
+
SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(
|
|
43666
|
+
settings.scope.exclude_names_effective
|
|
43667
|
+
);
|
|
43668
|
+
if (typeof settings.scope.watch_limit_default === "number")
|
|
43669
|
+
SERVER_SCOPE_WATCH_LIMIT_DEFAULT = settings.scope.watch_limit_default;
|
|
43670
|
+
if (typeof settings.scope.watch_limit_min === "number")
|
|
43671
|
+
SERVER_SCOPE_WATCH_LIMIT_MIN = settings.scope.watch_limit_min;
|
|
43672
|
+
if (typeof settings.scope.watch_limit_max === "number")
|
|
43673
|
+
SERVER_SCOPE_WATCH_LIMIT_MAX = settings.scope.watch_limit_max;
|
|
43674
|
+
if (typeof settings.scope.watch_recursive === "boolean") {
|
|
43675
|
+
const watchSection = document.querySelector(
|
|
43676
|
+
"#watch-settings-section"
|
|
42992
43677
|
);
|
|
42993
|
-
|
|
42994
|
-
|
|
43678
|
+
if (watchSection) watchSection.hidden = settings.scope.watch_recursive;
|
|
43679
|
+
}
|
|
43680
|
+
return settings;
|
|
43681
|
+
}
|
|
43682
|
+
function agentScreenRuleErrorsText(errors2) {
|
|
43683
|
+
return errors2.map(
|
|
43684
|
+
(error2) => `${error2.path} [${error2.code}] ${error2.message}${error2.stack ? `
|
|
43685
|
+
${error2.stack}` : ""}`
|
|
43686
|
+
).join("\n\n");
|
|
43687
|
+
}
|
|
43688
|
+
async function agentScreenRuleResponse(response) {
|
|
43689
|
+
let body;
|
|
43690
|
+
try {
|
|
43691
|
+
body = await response.json();
|
|
43692
|
+
} catch (error2) {
|
|
43693
|
+
throw errorWithCause(
|
|
43694
|
+
`terminal rule request returned ${response.status} with invalid JSON`,
|
|
43695
|
+
error2
|
|
42995
43696
|
);
|
|
42996
|
-
|
|
42997
|
-
|
|
42998
|
-
|
|
42999
|
-
|
|
43000
|
-
|
|
43001
|
-
|
|
43002
|
-
|
|
43003
|
-
|
|
43004
|
-
|
|
43005
|
-
|
|
43006
|
-
|
|
43007
|
-
|
|
43008
|
-
|
|
43009
|
-
}
|
|
43010
|
-
|
|
43697
|
+
}
|
|
43698
|
+
if (!response.ok) {
|
|
43699
|
+
throw Object.assign(
|
|
43700
|
+
new Error(
|
|
43701
|
+
`terminal rule request failed with status ${response.status}`
|
|
43702
|
+
),
|
|
43703
|
+
{ status: response.status, response: body }
|
|
43704
|
+
);
|
|
43705
|
+
}
|
|
43706
|
+
if (!body || typeof body !== "object" || !("rules" in body)) {
|
|
43707
|
+
throw Object.assign(new Error("terminal rule response is incomplete"), {
|
|
43708
|
+
response: body
|
|
43709
|
+
});
|
|
43710
|
+
}
|
|
43711
|
+
if (!("generation" in body) || typeof body.generation !== "number") {
|
|
43712
|
+
throw Object.assign(
|
|
43713
|
+
new Error("terminal rule response has no generation"),
|
|
43714
|
+
{ response: body }
|
|
43715
|
+
);
|
|
43716
|
+
}
|
|
43717
|
+
return body;
|
|
43718
|
+
}
|
|
43719
|
+
function applyAgentScreenRuleResponse(response) {
|
|
43720
|
+
if (response.generation < AGENT_SCREEN_RULES_GENERATION) return;
|
|
43721
|
+
AGENT_SCREEN_RULES = formatAgentScreenRuleSet(response.rules);
|
|
43722
|
+
AGENT_SCREEN_RULES_SOURCE = response.source;
|
|
43723
|
+
AGENT_SCREEN_RULE_ERRORS = response.errors;
|
|
43724
|
+
AGENT_SCREEN_RULES_GENERATION = response.generation;
|
|
43725
|
+
}
|
|
43726
|
+
async function loadAgentScreenRules() {
|
|
43727
|
+
const generation = ++AGENT_SCREEN_RULE_REQUEST_GENERATION;
|
|
43728
|
+
const response = await agentScreenRuleResponse(
|
|
43729
|
+
await trackLoad(fetch("/_agent/rules"))
|
|
43730
|
+
);
|
|
43731
|
+
if (generation !== AGENT_SCREEN_RULE_REQUEST_GENERATION) return;
|
|
43732
|
+
applyAgentScreenRuleResponse(response);
|
|
43733
|
+
}
|
|
43734
|
+
async function saveAgentScreenRules(value) {
|
|
43735
|
+
let rules;
|
|
43736
|
+
try {
|
|
43737
|
+
rules = JSON.parse(value);
|
|
43738
|
+
} catch (error2) {
|
|
43739
|
+
throw errorWithCause("terminal rules are not valid JSON", error2);
|
|
43740
|
+
}
|
|
43741
|
+
const generation = ++AGENT_SCREEN_RULE_REQUEST_GENERATION;
|
|
43742
|
+
const response = await agentScreenRuleResponse(
|
|
43743
|
+
await trackLoad(
|
|
43744
|
+
fetch("/_agent/rules", {
|
|
43745
|
+
method: "PUT",
|
|
43746
|
+
headers: actionHeaders(),
|
|
43747
|
+
body: JSON.stringify(rules)
|
|
43748
|
+
})
|
|
43749
|
+
)
|
|
43750
|
+
);
|
|
43751
|
+
if (generation !== AGENT_SCREEN_RULE_REQUEST_GENERATION) return;
|
|
43752
|
+
applyAgentScreenRuleResponse(response);
|
|
43753
|
+
}
|
|
43754
|
+
async function resetAgentScreenRuleSettings() {
|
|
43755
|
+
const generation = ++AGENT_SCREEN_RULE_REQUEST_GENERATION;
|
|
43756
|
+
const response = await agentScreenRuleResponse(
|
|
43757
|
+
await trackLoad(
|
|
43758
|
+
fetch("/_agent/rules", {
|
|
43759
|
+
method: "DELETE",
|
|
43760
|
+
headers: actionHeaders()
|
|
43761
|
+
})
|
|
43762
|
+
)
|
|
43763
|
+
);
|
|
43764
|
+
if (generation !== AGENT_SCREEN_RULE_REQUEST_GENERATION) return;
|
|
43765
|
+
applyAgentScreenRuleResponse(response);
|
|
43766
|
+
}
|
|
43767
|
+
async function loadStateResponse(url, operation) {
|
|
43768
|
+
const response = await trackLoad(fetch(url));
|
|
43769
|
+
if (!response.ok) {
|
|
43770
|
+
throw new Error(await responseErrorMessage(response, operation));
|
|
43771
|
+
}
|
|
43772
|
+
try {
|
|
43773
|
+
return await response.json();
|
|
43774
|
+
} catch (error2) {
|
|
43775
|
+
throw errorWithCause(`${operation}: response is not valid JSON`, error2);
|
|
43011
43776
|
}
|
|
43012
43777
|
}
|
|
43013
43778
|
async function loadPersistedState() {
|
|
43014
43779
|
const [settings, view] = await Promise.all([
|
|
43015
|
-
|
|
43016
|
-
|
|
43017
|
-
|
|
43018
|
-
|
|
43780
|
+
loadStateResponse(
|
|
43781
|
+
"/_state/settings",
|
|
43782
|
+
"settings state request failed"
|
|
43783
|
+
),
|
|
43784
|
+
loadStateResponse("/_state/view", "view state request failed")
|
|
43019
43785
|
]);
|
|
43020
|
-
|
|
43021
|
-
|
|
43786
|
+
APP_SETTINGS = settings;
|
|
43787
|
+
VIEW_STATE = view;
|
|
43022
43788
|
}
|
|
43023
43789
|
function routeFromLocation() {
|
|
43024
43790
|
const savedLanguage = viewerLanguageFromSearch(window.location.search) || savedViewerLanguage();
|
|
@@ -43428,7 +44194,7 @@ ${formatErrorDetail(error2)}`);
|
|
|
43428
44194
|
getGrepGroupByFile: () => APP_SETTINGS.grepGroupByFile === true,
|
|
43429
44195
|
getGrepPaletteWidth: () => APP_SETTINGS.grepPaletteWidth,
|
|
43430
44196
|
getGrepPaletteHeight: () => APP_SETTINGS.grepPaletteHeight,
|
|
43431
|
-
persistGrepSettings,
|
|
44197
|
+
persistGrepSettings: persistSettingsPatch,
|
|
43432
44198
|
applyGrepHideTests: (hidden) => {
|
|
43433
44199
|
STATE.hideTests = hidden;
|
|
43434
44200
|
applyHideTests();
|
|
@@ -43714,7 +44480,12 @@ ${formatErrorDetail(error2)}`);
|
|
|
43714
44480
|
excludeNames: "Hide these file or directory names completely",
|
|
43715
44481
|
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI. Supports gitignore-style wildcards (*, ?, [abc], [!abc]).",
|
|
43716
44482
|
reset: "Restore defaults",
|
|
43717
|
-
|
|
44483
|
+
save: "Save changes",
|
|
44484
|
+
saving: "Saving…",
|
|
44485
|
+
saved: "Saved.",
|
|
44486
|
+
unsaved: "Unsaved changes.",
|
|
44487
|
+
saveNote: "Edits are not applied until you select Save changes.",
|
|
44488
|
+
watchLimitInvalid: (min, max) => `Enter a whole number from ${min} to ${max}.`,
|
|
43718
44489
|
scopeSource: (project, source) => `Saved for project "${project}" in this browser. Source: ${source}. Used by the sidebar, Ctrl+K, Ctrl+G, Datastores, and the file change watcher. Restore defaults removes the browser override.`,
|
|
43719
44490
|
browserOverride: "Browser override",
|
|
43720
44491
|
serverDefault: "Server default",
|
|
@@ -43728,7 +44499,34 @@ ${formatErrorDetail(error2)}`);
|
|
|
43728
44499
|
datastoreS3TooltipHelp: "Hovering an S3 object row shows the full key path and a content preview.",
|
|
43729
44500
|
watchTitle: "File change watcher",
|
|
43730
44501
|
watchLimit: "Maximum directories to watch",
|
|
43731
|
-
watchLimitHelp: (defaultLimit) => `Higher values reduce missed updates in deep trees at the cost of file handles. Combine with the Skip list above to keep heavy folders (node_modules, .git, dist...) out of the watch budget. Default: ${defaultLimit}
|
|
44502
|
+
watchLimitHelp: (defaultLimit) => `Higher values reduce missed updates in deep trees at the cost of file handles. Combine with the Skip list above to keep heavy folders (node_modules, .git, dist...) out of the watch budget. Default: ${defaultLimit}.`,
|
|
44503
|
+
agentRulesTitle: "Terminal status detection",
|
|
44504
|
+
agentRulesLabel: "Screen matching rules (JSON)",
|
|
44505
|
+
agentRulesHelp: "Rules can report working, waiting, idle, or skip. Configure priority, region, contains, regex, lineRegex, and nested all/any/not conditions. contains ignores letter case; regex accepts a leading (?i) for case-insensitive matching. To keep matching responsive, regex allows at most one variable-length repetition and rejects groups, alternation, and backreferences; express AND/OR with all/any. The highest-priority match wins; equal priorities keep the earlier rule. Save validates every rule before replacing the active set.",
|
|
44506
|
+
agentRulesGuideTitle: "JSON format and example",
|
|
44507
|
+
agentRulesGuideIntro: "Enter one object with version 1 and a rules array. Each rule needs the required fields listed below plus at least one matcher.",
|
|
44508
|
+
agentRulesGuideFields: "Required fields: id (unique name), state (working, waiting, idle, or skip), priority (higher wins), and region. lines is also required when region is bottom_non_empty.",
|
|
44509
|
+
agentRulesGuideMatchers: "Matchers: contains and regex test the selected region; lineRegex tests each line. Combine matcher objects with all, any, and not.",
|
|
44510
|
+
agentRulesGuideRegions: "Regions: osc_title checks the terminal title, whole_recent checks the recent screen, bottom_non_empty checks the last non-empty lines, and last_non_empty checks only the final non-empty line.",
|
|
44511
|
+
agentRulesGuideExample: `{
|
|
44512
|
+
"version": 1,
|
|
44513
|
+
"rules": [
|
|
44514
|
+
{
|
|
44515
|
+
"id": "waiting_for_confirmation",
|
|
44516
|
+
"state": "waiting",
|
|
44517
|
+
"priority": 900,
|
|
44518
|
+
"region": "bottom_non_empty",
|
|
44519
|
+
"lines": 12,
|
|
44520
|
+
"contains": ["enter to confirm"],
|
|
44521
|
+
"not": [{ "contains": ["finished"] }]
|
|
44522
|
+
}
|
|
44523
|
+
]
|
|
44524
|
+
}`,
|
|
44525
|
+
agentRulesSave: "Validate and save",
|
|
44526
|
+
agentRulesReset: "Use built-in rules",
|
|
44527
|
+
agentRulesSaving: "Validating and saving…",
|
|
44528
|
+
agentRulesSourceDefault: "Source: built-in rules",
|
|
44529
|
+
agentRulesSourceSaved: "Source: saved rules (active immediately)"
|
|
43732
44530
|
},
|
|
43733
44531
|
annotations: {
|
|
43734
44532
|
title: "Code annotations",
|
|
@@ -44017,7 +44815,12 @@ ${formatErrorDetail(error2)}`);
|
|
|
44017
44815
|
excludeNames: "完全に非表示にするファイル名またはディレクトリ名",
|
|
44018
44816
|
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。gitignore方式のワイルドカード(*, ?, [abc], [!abc])に対応しています。",
|
|
44019
44817
|
reset: "デフォルトに戻す",
|
|
44020
|
-
|
|
44818
|
+
save: "変更を保存",
|
|
44819
|
+
saving: "保存しています…",
|
|
44820
|
+
saved: "保存しました。",
|
|
44821
|
+
unsaved: "未保存の変更があります。",
|
|
44822
|
+
saveNote: "「変更を保存」を押すまで、編集内容は適用されません。",
|
|
44823
|
+
watchLimitInvalid: (min, max) => `${min}〜${max}の整数を入力してください。`,
|
|
44021
44824
|
scopeSource: (project, source) => `このブラウザのプロジェクト "${project}" に保存されます。ソース: ${source}。サイドバー、Ctrl+K、Ctrl+G、Datastores、File change watcher で使われます。「デフォルトに戻す」でブラウザ側の上書きを削除します。`,
|
|
44022
44825
|
browserOverride: "ブラウザ側の上書き",
|
|
44023
44826
|
serverDefault: "サーバ既定値",
|
|
@@ -44031,7 +44834,34 @@ ${formatErrorDetail(error2)}`);
|
|
|
44031
44834
|
datastoreS3TooltipHelp: "S3 オブジェクト行にホバーすると、完全な key とコンテンツプレビューを表示します。",
|
|
44032
44835
|
watchTitle: "ファイル変更の監視",
|
|
44033
44836
|
watchLimit: "監視するディレクトリ数の上限",
|
|
44034
|
-
watchLimitHelp: (defaultLimit) => `値を大きくすると深いツリーの変更を取りこぼしにくくなりますが、ファイルハンドル数を消費します。上の Skip リストと併用すると、重いフォルダ(node_modules, .git, dist など)を監視枠から外せます。既定値: ${defaultLimit}
|
|
44837
|
+
watchLimitHelp: (defaultLimit) => `値を大きくすると深いツリーの変更を取りこぼしにくくなりますが、ファイルハンドル数を消費します。上の Skip リストと併用すると、重いフォルダ(node_modules, .git, dist など)を監視枠から外せます。既定値: ${defaultLimit}。`,
|
|
44838
|
+
agentRulesTitle: "ターミナルのAI状態判定",
|
|
44839
|
+
agentRulesLabel: "画面の一致ルール(JSON)",
|
|
44840
|
+
agentRulesHelp: "各ルールで working(作業中)・waiting(入力待ち)・idle(待機中)・skip(状態を維持)を指定できます。priority、region、contains、regex、lineRegex、入れ子の all/any/not を編集できます。contains は大文字小文字を区別せず、regex は先頭の (?i) による大小無視に対応します。判定処理を止めないため、regex の可変長の繰返しは1個までで、グループ・選択・後方参照は使えません。AND/OR は all/any で表します。優先度が最大の一致が採用され、同点は上にあるルールが優先されます。保存前に全ルールを検証します。",
|
|
44841
|
+
agentRulesGuideTitle: "JSONの書式と入力例",
|
|
44842
|
+
agentRulesGuideIntro: "version が 1、rules が配列のJSONオブジェクトを入力します。各ルールには下記の必須項目と、1個以上の一致条件が必要です。",
|
|
44843
|
+
agentRulesGuideFields: "必須項目: id(一意の名前)、state(working / waiting / idle / skip)、priority(大きい値を優先)、region。region が bottom_non_empty の場合は lines も必要です。",
|
|
44844
|
+
agentRulesGuideMatchers: "一致条件: contains と regex は選択した領域全体、lineRegex は各行を調べます。一致条件のオブジェクトは all / any / not で組み合わせられます。",
|
|
44845
|
+
agentRulesGuideRegions: "region: osc_title はターミナルタイトル、whole_recent は直近の画面全体、bottom_non_empty は末尾の非空行、last_non_empty は最後の非空行だけを調べます。",
|
|
44846
|
+
agentRulesGuideExample: `{
|
|
44847
|
+
"version": 1,
|
|
44848
|
+
"rules": [
|
|
44849
|
+
{
|
|
44850
|
+
"id": "waiting_for_confirmation",
|
|
44851
|
+
"state": "waiting",
|
|
44852
|
+
"priority": 900,
|
|
44853
|
+
"region": "bottom_non_empty",
|
|
44854
|
+
"lines": 12,
|
|
44855
|
+
"contains": ["enter to confirm"],
|
|
44856
|
+
"not": [{ "contains": ["finished"] }]
|
|
44857
|
+
}
|
|
44858
|
+
]
|
|
44859
|
+
}`,
|
|
44860
|
+
agentRulesSave: "検証して保存",
|
|
44861
|
+
agentRulesReset: "組み込みルールに戻す",
|
|
44862
|
+
agentRulesSaving: "検証して保存しています…",
|
|
44863
|
+
agentRulesSourceDefault: "適用中: 組み込みルール",
|
|
44864
|
+
agentRulesSourceSaved: "適用中: 保存したルール(即時反映)"
|
|
44035
44865
|
},
|
|
44036
44866
|
annotations: {
|
|
44037
44867
|
title: "コード注釈",
|
|
@@ -44410,12 +45240,6 @@ ${formatErrorDetail(error2)}`);
|
|
|
44410
45240
|
const target = sourceTargetFromRoute();
|
|
44411
45241
|
if (target) renderRepoBlobSidebar(target.path, target.ref || "worktree");
|
|
44412
45242
|
}
|
|
44413
|
-
function saveSidebarFontSize(value) {
|
|
44414
|
-
const next = normalizeViewerFontSize(value);
|
|
44415
|
-
mergeLocalSettings({ sidebarFontSize: next });
|
|
44416
|
-
applySidebarFontSize();
|
|
44417
|
-
patchSettings({ sidebarFontSize: next });
|
|
44418
|
-
}
|
|
44419
45243
|
const CODE_FONT_STEPS = [
|
|
44420
45244
|
"compact",
|
|
44421
45245
|
"regular",
|
|
@@ -44444,22 +45268,6 @@ ${formatErrorDetail(error2)}`);
|
|
|
44444
45268
|
applyCodeFontSize();
|
|
44445
45269
|
patchSettings({ codeFontSize: next });
|
|
44446
45270
|
}
|
|
44447
|
-
function saveUploadEnabled(checked) {
|
|
44448
|
-
mergeLocalSettings({ uploadEnabled: checked });
|
|
44449
|
-
patchSettings({ uploadEnabled: checked });
|
|
44450
|
-
}
|
|
44451
|
-
function saveScopeOmitDirsField(value) {
|
|
44452
|
-
const next = normalizeScopeOmitDirs(value);
|
|
44453
|
-
mergeLocalSettings({ scopeOmitDirs: next });
|
|
44454
|
-
patchSettings({ scopeOmitDirs: next });
|
|
44455
|
-
refreshRepositoryTreeAfterSettings();
|
|
44456
|
-
}
|
|
44457
|
-
function saveScopeExcludeNamesField(value) {
|
|
44458
|
-
const next = normalizeScopeExcludeNames(value);
|
|
44459
|
-
mergeLocalSettings({ scopeExcludeNames: next });
|
|
44460
|
-
patchSettings({ scopeExcludeNames: next });
|
|
44461
|
-
refreshRepositoryTreeAfterSettings();
|
|
44462
|
-
}
|
|
44463
45271
|
function normalizeScopeWatchLimit(value) {
|
|
44464
45272
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
44465
45273
|
if (!Number.isFinite(parsed)) return null;
|
|
@@ -44474,34 +45282,94 @@ ${formatErrorDetail(error2)}`);
|
|
|
44474
45282
|
const saved = normalizeScopeWatchLimit(APP_SETTINGS.scopeWatchLimit);
|
|
44475
45283
|
return saved ?? SERVER_SCOPE_WATCH_LIMIT_DEFAULT;
|
|
44476
45284
|
}
|
|
44477
|
-
function
|
|
44478
|
-
|
|
44479
|
-
|
|
44480
|
-
|
|
44481
|
-
|
|
44482
|
-
|
|
44483
|
-
|
|
44484
|
-
|
|
44485
|
-
|
|
44486
|
-
|
|
44487
|
-
|
|
44488
|
-
|
|
44489
|
-
|
|
44490
|
-
|
|
44491
|
-
|
|
44492
|
-
|
|
44493
|
-
|
|
44494
|
-
|
|
44495
|
-
|
|
44496
|
-
|
|
44497
|
-
|
|
44498
|
-
|
|
44499
|
-
|
|
44500
|
-
|
|
44501
|
-
|
|
44502
|
-
|
|
44503
|
-
|
|
44504
|
-
|
|
45285
|
+
function defaultViewerSettingsDraft() {
|
|
45286
|
+
return {
|
|
45287
|
+
language: "en",
|
|
45288
|
+
sidebarFontSize: "regular",
|
|
45289
|
+
codeFontSize: "regular",
|
|
45290
|
+
omitDirs: serverScopeOmitDirsDefault().join("\n"),
|
|
45291
|
+
excludeNames: serverScopeExcludeNamesDefault().join("\n"),
|
|
45292
|
+
watchLimit: SERVER_SCOPE_WATCH_LIMIT_DEFAULT,
|
|
45293
|
+
uploadEnabled: true,
|
|
45294
|
+
inferFkRails: false,
|
|
45295
|
+
s3TooltipEnabled: true
|
|
45296
|
+
};
|
|
45297
|
+
}
|
|
45298
|
+
async function saveViewerSettings(draft, options) {
|
|
45299
|
+
const normalizedLanguage = normalizeViewerLanguage(draft.language);
|
|
45300
|
+
const normalizedSidebarFontSize = normalizeViewerFontSize(
|
|
45301
|
+
draft.sidebarFontSize
|
|
45302
|
+
);
|
|
45303
|
+
const normalizedCodeFontSize = normalizeViewerFontSize(draft.codeFontSize);
|
|
45304
|
+
const normalizedOmitDirs = normalizeScopeOmitDirs(draft.omitDirs);
|
|
45305
|
+
const normalizedExcludeNames = normalizeScopeExcludeNames(
|
|
45306
|
+
draft.excludeNames
|
|
45307
|
+
);
|
|
45308
|
+
const normalized = {
|
|
45309
|
+
language: normalizedLanguage,
|
|
45310
|
+
sidebarFontSize: normalizedSidebarFontSize,
|
|
45311
|
+
codeFontSize: normalizedCodeFontSize,
|
|
45312
|
+
omitDirs: normalizedOmitDirs.join("\n"),
|
|
45313
|
+
excludeNames: normalizedExcludeNames.join("\n"),
|
|
45314
|
+
watchLimit: normalizeScopeWatchLimit(draft.watchLimit) ?? SERVER_SCOPE_WATCH_LIMIT_DEFAULT,
|
|
45315
|
+
uploadEnabled: draft.uploadEnabled,
|
|
45316
|
+
inferFkRails: draft.inferFkRails,
|
|
45317
|
+
s3TooltipEnabled: draft.s3TooltipEnabled
|
|
45318
|
+
};
|
|
45319
|
+
const changed = new Set(options.changedFields);
|
|
45320
|
+
const appPatch = {};
|
|
45321
|
+
const dbPrefsPatch = {};
|
|
45322
|
+
if (options.restoreDefaults) {
|
|
45323
|
+
Object.assign(appPatch, {
|
|
45324
|
+
language: "en",
|
|
45325
|
+
sidebarFontSize: null,
|
|
45326
|
+
codeFontSize: null,
|
|
45327
|
+
scopeOmitDirs: null,
|
|
45328
|
+
scopeExcludeNames: null,
|
|
45329
|
+
scopeWatchLimit: null,
|
|
45330
|
+
uploadEnabled: null
|
|
45331
|
+
});
|
|
45332
|
+
dbPrefsPatch.inferFkRails = null;
|
|
45333
|
+
dbPrefsPatch.s3TooltipEnabled = null;
|
|
45334
|
+
} else {
|
|
45335
|
+
if (changed.has("language")) appPatch.language = normalizedLanguage;
|
|
45336
|
+
if (changed.has("sidebarFontSize"))
|
|
45337
|
+
appPatch.sidebarFontSize = normalizedSidebarFontSize;
|
|
45338
|
+
if (changed.has("codeFontSize"))
|
|
45339
|
+
appPatch.codeFontSize = normalizedCodeFontSize;
|
|
45340
|
+
if (changed.has("omitDirs")) appPatch.scopeOmitDirs = normalizedOmitDirs;
|
|
45341
|
+
if (changed.has("excludeNames"))
|
|
45342
|
+
appPatch.scopeExcludeNames = normalizedExcludeNames;
|
|
45343
|
+
if (changed.has("watchLimit"))
|
|
45344
|
+
appPatch.scopeWatchLimit = normalized.watchLimit;
|
|
45345
|
+
if (changed.has("uploadEnabled"))
|
|
45346
|
+
appPatch.uploadEnabled = normalized.uploadEnabled;
|
|
45347
|
+
if (changed.has("inferFkRails"))
|
|
45348
|
+
dbPrefsPatch.inferFkRails = normalized.inferFkRails;
|
|
45349
|
+
if (changed.has("s3TooltipEnabled"))
|
|
45350
|
+
dbPrefsPatch.s3TooltipEnabled = normalized.s3TooltipEnabled;
|
|
45351
|
+
}
|
|
45352
|
+
const operations = [];
|
|
45353
|
+
if (Object.keys(appPatch).length > 0)
|
|
45354
|
+
operations.push(persistSettingsPatch(appPatch));
|
|
45355
|
+
if (Object.keys(dbPrefsPatch).length > 0)
|
|
45356
|
+
operations.push(DATABASE_VIEW.saveDbUiPrefs(dbPrefsPatch));
|
|
45357
|
+
const results = await Promise.allSettled(operations);
|
|
45358
|
+
const errors2 = results.flatMap(
|
|
45359
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
45360
|
+
);
|
|
45361
|
+
if (errors2.length > 0) {
|
|
45362
|
+
throw errorWithCauses("save viewer settings failed", errors2);
|
|
45363
|
+
}
|
|
45364
|
+
if (options.restoreDefaults || changed.has("language"))
|
|
45365
|
+
setViewerLanguage(normalizedLanguage, false);
|
|
45366
|
+
if (options.restoreDefaults || changed.has("sidebarFontSize"))
|
|
45367
|
+
applySidebarFontSize();
|
|
45368
|
+
if (options.restoreDefaults || changed.has("codeFontSize"))
|
|
45369
|
+
applyCodeFontSize();
|
|
45370
|
+
if (options.restoreDefaults || changed.has("omitDirs") || changed.has("excludeNames")) {
|
|
45371
|
+
refreshRepositoryTreeAfterSettings();
|
|
45372
|
+
}
|
|
44505
45373
|
}
|
|
44506
45374
|
function createRefSelectorInput(options) {
|
|
44507
45375
|
const wrap = document.createElement("div");
|
|
@@ -45013,30 +45881,22 @@ ${formatErrorDetail(error2)}`);
|
|
|
45013
45881
|
scopeSource: uiText().settings.scopeSource(
|
|
45014
45882
|
PROJECT_NAME || "default",
|
|
45015
45883
|
scopeOmitSourceLabel()
|
|
45016
|
-
)
|
|
45884
|
+
),
|
|
45885
|
+
agentRulesJson: AGENT_SCREEN_RULES,
|
|
45886
|
+
agentRulesSource: AGENT_SCREEN_RULES_SOURCE,
|
|
45887
|
+
agentRulesErrors: agentScreenRuleErrorsText(AGENT_SCREEN_RULE_ERRORS)
|
|
45017
45888
|
}),
|
|
45889
|
+
getDefaultValues: defaultViewerSettingsDraft,
|
|
45018
45890
|
refresh: async () => {
|
|
45019
|
-
await
|
|
45020
|
-
|
|
45021
|
-
|
|
45022
|
-
|
|
45023
|
-
|
|
45024
|
-
},
|
|
45025
|
-
onSidebarFontSizeChange: saveSidebarFontSize,
|
|
45026
|
-
onCodeFontSizeChange: saveCodeFontSize,
|
|
45027
|
-
onUploadEnabledChange: saveUploadEnabled,
|
|
45028
|
-
onOmitDirsChange: (value) => {
|
|
45029
|
-
saveScopeOmitDirsField(value);
|
|
45030
|
-
VIEWER_SETTINGS.sync();
|
|
45031
|
-
},
|
|
45032
|
-
onExcludeNamesChange: (value) => {
|
|
45033
|
-
saveScopeExcludeNamesField(value);
|
|
45034
|
-
VIEWER_SETTINGS.sync();
|
|
45891
|
+
await Promise.all([
|
|
45892
|
+
loadSettings(),
|
|
45893
|
+
loadAgentScreenRules(),
|
|
45894
|
+
DATABASE_VIEW.loadDbUiPrefs()
|
|
45895
|
+
]);
|
|
45035
45896
|
},
|
|
45036
|
-
|
|
45037
|
-
|
|
45038
|
-
|
|
45039
|
-
onReset: resetScopeSettings
|
|
45897
|
+
onSave: saveViewerSettings,
|
|
45898
|
+
onAgentRulesSave: saveAgentScreenRules,
|
|
45899
|
+
onAgentRulesReset: resetAgentScreenRuleSettings
|
|
45040
45900
|
});
|
|
45041
45901
|
relocalizeViewerSettings = () => VIEWER_SETTINGS.localize();
|
|
45042
45902
|
const KEYBINDING_EDITOR = createHelpKeybindingEditor({
|