@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/dist/code-viewer.js
CHANGED
|
@@ -273,6 +273,185 @@ var init_routes = __esm({
|
|
|
273
273
|
}
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
+
// web-src/core/error-detail.ts
|
|
277
|
+
function errorWithCause(message, cause) {
|
|
278
|
+
return Object.assign(new Error(message), { cause });
|
|
279
|
+
}
|
|
280
|
+
function errorWithCauses(message, errors) {
|
|
281
|
+
return Object.assign(new Error(message), { errors: [...errors] });
|
|
282
|
+
}
|
|
283
|
+
function isSensitiveFieldName(key) {
|
|
284
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
285
|
+
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");
|
|
286
|
+
}
|
|
287
|
+
function errorName(error) {
|
|
288
|
+
try {
|
|
289
|
+
return typeof error.name === "string" ? error.name : "Error";
|
|
290
|
+
} catch {
|
|
291
|
+
return "Error";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function errorMessage(error) {
|
|
295
|
+
try {
|
|
296
|
+
return typeof error.message === "string" ? error.message : "unable to read error message";
|
|
297
|
+
} catch {
|
|
298
|
+
return "unable to read error message";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function sanitizeObjectFields(value, ancestors, excludedKeys = /* @__PURE__ */ new Set()) {
|
|
302
|
+
let keys;
|
|
303
|
+
try {
|
|
304
|
+
keys = Object.getOwnPropertyNames(value);
|
|
305
|
+
} catch {
|
|
306
|
+
return { value: "[Unserializable object]", removedSensitive: false };
|
|
307
|
+
}
|
|
308
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
309
|
+
let removedSensitive = false;
|
|
310
|
+
for (const key of keys) {
|
|
311
|
+
if (excludedKeys.has(key)) continue;
|
|
312
|
+
if (isSensitiveFieldName(key)) {
|
|
313
|
+
removedSensitive = true;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
let descriptor;
|
|
317
|
+
try {
|
|
318
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
319
|
+
} catch {
|
|
320
|
+
output[key] = "[Unserializable field]";
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!descriptor) continue;
|
|
324
|
+
if (!("value" in descriptor)) {
|
|
325
|
+
output[key] = "[Accessor]";
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const sanitized = sanitizeValue(descriptor.value, ancestors);
|
|
329
|
+
if (sanitized === OMIT_VALUE) {
|
|
330
|
+
removedSensitive = true;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
output[key] = sanitized.value;
|
|
334
|
+
removedSensitive ||= sanitized.removedSensitive;
|
|
335
|
+
}
|
|
336
|
+
if (Object.keys(output).length === 0 && removedSensitive) return OMIT_VALUE;
|
|
337
|
+
return { value: output, removedSensitive };
|
|
338
|
+
}
|
|
339
|
+
function sanitizeError(error, ancestors) {
|
|
340
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
341
|
+
output.name = errorName(error);
|
|
342
|
+
output.message = errorMessage(error);
|
|
343
|
+
const fields = sanitizeObjectFields(
|
|
344
|
+
error,
|
|
345
|
+
ancestors,
|
|
346
|
+
/* @__PURE__ */ new Set(["name", "message", "stack"])
|
|
347
|
+
);
|
|
348
|
+
if (fields !== OMIT_VALUE) Object.assign(output, fields.value);
|
|
349
|
+
return {
|
|
350
|
+
value: output,
|
|
351
|
+
removedSensitive: fields === OMIT_VALUE ? true : fields.removedSensitive
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function sanitizeValue(value, ancestors) {
|
|
355
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
356
|
+
return { value, removedSensitive: false };
|
|
357
|
+
if (typeof value === "number") {
|
|
358
|
+
return {
|
|
359
|
+
value: Number.isFinite(value) ? value : String(value),
|
|
360
|
+
removedSensitive: false
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (typeof value === "bigint") {
|
|
364
|
+
return { value: `${value}n`, removedSensitive: false };
|
|
365
|
+
}
|
|
366
|
+
if (typeof value === "undefined") {
|
|
367
|
+
return { value: "[undefined]", removedSensitive: false };
|
|
368
|
+
}
|
|
369
|
+
if (typeof value === "symbol") {
|
|
370
|
+
return { value: "[symbol]", removedSensitive: false };
|
|
371
|
+
}
|
|
372
|
+
if (typeof value === "function") {
|
|
373
|
+
return { value: "[function]", removedSensitive: false };
|
|
374
|
+
}
|
|
375
|
+
const objectValue = value;
|
|
376
|
+
if (ancestors.has(objectValue)) {
|
|
377
|
+
return { value: "[Circular]", removedSensitive: false };
|
|
378
|
+
}
|
|
379
|
+
ancestors.add(objectValue);
|
|
380
|
+
try {
|
|
381
|
+
if (Array.isArray(objectValue)) {
|
|
382
|
+
const output = [];
|
|
383
|
+
let removedSensitive = false;
|
|
384
|
+
for (const item of objectValue) {
|
|
385
|
+
const sanitized = sanitizeValue(item, ancestors);
|
|
386
|
+
if (sanitized === OMIT_VALUE) {
|
|
387
|
+
removedSensitive = true;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
output.push(sanitized.value);
|
|
391
|
+
removedSensitive ||= sanitized.removedSensitive;
|
|
392
|
+
}
|
|
393
|
+
if (output.length === 0 && removedSensitive) return OMIT_VALUE;
|
|
394
|
+
return { value: output, removedSensitive };
|
|
395
|
+
}
|
|
396
|
+
if (objectValue instanceof Error)
|
|
397
|
+
return sanitizeError(objectValue, ancestors);
|
|
398
|
+
return sanitizeObjectFields(objectValue, ancestors);
|
|
399
|
+
} catch {
|
|
400
|
+
return { value: "[Unserializable object]", removedSensitive: false };
|
|
401
|
+
} finally {
|
|
402
|
+
ancestors.delete(objectValue);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function formatNonError(value) {
|
|
406
|
+
if (typeof value === "string") return value;
|
|
407
|
+
try {
|
|
408
|
+
const sanitized = sanitizeValue(value, /* @__PURE__ */ new Set());
|
|
409
|
+
const serializable = sanitized === OMIT_VALUE ? {} : sanitized.value;
|
|
410
|
+
return JSON.stringify(serializable);
|
|
411
|
+
} catch {
|
|
412
|
+
return "[Unserializable value]";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function formatErrorFields(error) {
|
|
416
|
+
const fields = sanitizeObjectFields(
|
|
417
|
+
error,
|
|
418
|
+
/* @__PURE__ */ new Set([error]),
|
|
419
|
+
/* @__PURE__ */ new Set(["name", "message", "stack", "cause"])
|
|
420
|
+
);
|
|
421
|
+
if (fields === OMIT_VALUE) return "";
|
|
422
|
+
const output = fields.value;
|
|
423
|
+
return Object.keys(output).length > 0 ? `
|
|
424
|
+
Details: ${JSON.stringify(output)}` : "";
|
|
425
|
+
}
|
|
426
|
+
function formatErrorDetail(error) {
|
|
427
|
+
const parts = [];
|
|
428
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
429
|
+
let current = error;
|
|
430
|
+
while (current instanceof Error && !seen2.has(current)) {
|
|
431
|
+
seen2.add(current);
|
|
432
|
+
parts.push(
|
|
433
|
+
`${errorName(current)}: ${errorMessage(current)}${formatErrorFields(current)}`
|
|
434
|
+
);
|
|
435
|
+
try {
|
|
436
|
+
current = current.cause;
|
|
437
|
+
} catch {
|
|
438
|
+
current = "[Unserializable error cause]";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (current !== void 0) {
|
|
442
|
+
parts.push(
|
|
443
|
+
seen2.has(current) ? "Error cause cycle detected" : formatNonError(current)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return parts.join("\nCaused by: ") || formatNonError(error);
|
|
447
|
+
}
|
|
448
|
+
var OMIT_VALUE;
|
|
449
|
+
var init_error_detail = __esm({
|
|
450
|
+
"web-src/core/error-detail.ts"() {
|
|
451
|
+
OMIT_VALUE = /* @__PURE__ */ Symbol("omit-sensitive-error-field");
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
276
455
|
// web-src/server/json-store.ts
|
|
277
456
|
import { randomBytes } from "node:crypto";
|
|
278
457
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
@@ -284,14 +463,14 @@ function tmpPath(file) {
|
|
|
284
463
|
return `${file}.tmp-${process.pid}-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
285
464
|
}
|
|
286
465
|
async function backupInvalidFile(file, suffix) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
466
|
+
const backup = `${file}.${suffix}-${Date.now()}`;
|
|
467
|
+
await rename(file, backup);
|
|
468
|
+
return backup;
|
|
291
469
|
}
|
|
292
470
|
function createJsonFileStore(options) {
|
|
293
471
|
const queues = /* @__PURE__ */ new Map();
|
|
294
472
|
const backupSuffix = options.backupSuffix ?? "corrupt";
|
|
473
|
+
const invalidFileBehavior = options.invalidFileBehavior ?? "empty";
|
|
295
474
|
const serialize = options.serialize ?? ((state) => `${JSON.stringify(state, null, 2)}
|
|
296
475
|
`);
|
|
297
476
|
async function loadUnqueued(root) {
|
|
@@ -305,8 +484,25 @@ function createJsonFileStore(options) {
|
|
|
305
484
|
}
|
|
306
485
|
try {
|
|
307
486
|
return options.sanitize(JSON.parse(raw));
|
|
308
|
-
} catch {
|
|
309
|
-
|
|
487
|
+
} catch (invalidError) {
|
|
488
|
+
let backup;
|
|
489
|
+
try {
|
|
490
|
+
backup = await backupInvalidFile(file, backupSuffix);
|
|
491
|
+
} catch (backupError) {
|
|
492
|
+
throw errorWithCauses("invalid JSON state could not be backed up", [
|
|
493
|
+
invalidError,
|
|
494
|
+
backupError
|
|
495
|
+
]);
|
|
496
|
+
}
|
|
497
|
+
const recovered = Object.assign(
|
|
498
|
+
errorWithCause("invalid JSON state was moved aside", invalidError),
|
|
499
|
+
{ backup }
|
|
500
|
+
);
|
|
501
|
+
if (invalidFileBehavior === "throw") throw recovered;
|
|
502
|
+
console.error(
|
|
503
|
+
"[code-viewer] invalid JSON state was moved aside",
|
|
504
|
+
recovered
|
|
505
|
+
);
|
|
310
506
|
return options.empty();
|
|
311
507
|
}
|
|
312
508
|
}
|
|
@@ -322,55 +518,72 @@ function createJsonFileStore(options) {
|
|
|
322
518
|
try {
|
|
323
519
|
await writeFile(tmp, content, "utf8");
|
|
324
520
|
await rename(tmp, file);
|
|
325
|
-
} catch (
|
|
326
|
-
|
|
327
|
-
|
|
521
|
+
} catch (writeError) {
|
|
522
|
+
try {
|
|
523
|
+
await unlink(tmp);
|
|
524
|
+
} catch (cleanupError) {
|
|
525
|
+
if (!isEnoent(cleanupError)) {
|
|
526
|
+
throw errorWithCauses(
|
|
527
|
+
"failed to save JSON state and remove the temporary file",
|
|
528
|
+
[writeError, cleanupError]
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
throw errorWithCause("failed to save JSON state", writeError);
|
|
328
533
|
}
|
|
329
534
|
}
|
|
330
|
-
|
|
331
|
-
const pendingWrite = queues.get(options.filePath(root));
|
|
332
|
-
if (pendingWrite) await pendingWrite.catch(() => void 0);
|
|
333
|
-
return loadUnqueued(root);
|
|
334
|
-
}
|
|
335
|
-
async function save(root, state) {
|
|
535
|
+
function enqueue(root, operation) {
|
|
336
536
|
const file = options.filePath(root);
|
|
337
537
|
const previous = queues.get(file) ?? Promise.resolve();
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
538
|
+
const gateState = {
|
|
539
|
+
release: () => {
|
|
540
|
+
throw new Error("JSON store queue gate was not initialized");
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const gate = new Promise((resolve4) => {
|
|
544
|
+
gateState.release = resolve4;
|
|
545
|
+
});
|
|
546
|
+
const tail = previous.then(() => gate);
|
|
547
|
+
queues.set(file, tail);
|
|
548
|
+
return previous.then(async () => {
|
|
549
|
+
try {
|
|
550
|
+
return await operation();
|
|
551
|
+
} finally {
|
|
552
|
+
gateState.release();
|
|
553
|
+
if (queues.get(file) === tail) queues.delete(file);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function load(root) {
|
|
558
|
+
return enqueue(root, () => loadUnqueued(root));
|
|
559
|
+
}
|
|
560
|
+
function save(root, state) {
|
|
561
|
+
return enqueue(root, () => saveUnqueued(root, state));
|
|
562
|
+
}
|
|
563
|
+
function remove(root) {
|
|
564
|
+
return enqueue(root, async () => {
|
|
565
|
+
try {
|
|
566
|
+
await unlink(options.filePath(root));
|
|
567
|
+
return true;
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (isEnoent(error)) return false;
|
|
570
|
+
throw errorWithCause("failed to remove JSON state", error);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
349
573
|
}
|
|
350
574
|
async function update(root, updater) {
|
|
351
|
-
|
|
352
|
-
const previous = queues.get(file) ?? Promise.resolve();
|
|
353
|
-
const run2 = previous.catch(() => void 0).then(async () => {
|
|
575
|
+
return enqueue(root, async () => {
|
|
354
576
|
const current = await loadUnqueued(root);
|
|
355
577
|
const updated = await updater(current);
|
|
356
578
|
await saveUnqueued(root, updated.state);
|
|
357
579
|
return updated.result;
|
|
358
580
|
});
|
|
359
|
-
const queued = run2.then(
|
|
360
|
-
() => void 0,
|
|
361
|
-
() => void 0
|
|
362
|
-
);
|
|
363
|
-
queues.set(file, queued);
|
|
364
|
-
try {
|
|
365
|
-
return await run2;
|
|
366
|
-
} finally {
|
|
367
|
-
if (queues.get(file) === queued) queues.delete(file);
|
|
368
|
-
}
|
|
369
581
|
}
|
|
370
|
-
return { load, save, update };
|
|
582
|
+
return { load, save, remove, update };
|
|
371
583
|
}
|
|
372
584
|
var init_json_store = __esm({
|
|
373
585
|
"web-src/server/json-store.ts"() {
|
|
586
|
+
init_error_detail();
|
|
374
587
|
}
|
|
375
588
|
});
|
|
376
589
|
|
|
@@ -5051,31 +5264,31 @@ function extractGithubIssueLabels(raw) {
|
|
|
5051
5264
|
}
|
|
5052
5265
|
function normalizeGithubIssueListItem(raw) {
|
|
5053
5266
|
if (!raw || typeof raw !== "object") return null;
|
|
5054
|
-
const
|
|
5055
|
-
const number =
|
|
5056
|
-
const title =
|
|
5267
|
+
const issue2 = raw;
|
|
5268
|
+
const number = issue2.number;
|
|
5269
|
+
const title = issue2.title;
|
|
5057
5270
|
if (typeof number !== "number" || !Number.isInteger(number) || number <= 0 || typeof title !== "string" || !title.trim()) {
|
|
5058
5271
|
return null;
|
|
5059
5272
|
}
|
|
5060
|
-
const url = singleLineGithubOption(
|
|
5061
|
-
const state = typeof
|
|
5273
|
+
const url = singleLineGithubOption(issue2.url);
|
|
5274
|
+
const state = typeof issue2.state === "string" && issue2.state.trim() ? issue2.state.trim().toLowerCase() : "open";
|
|
5062
5275
|
return {
|
|
5063
5276
|
number,
|
|
5064
5277
|
title: title.trim().slice(0, 200),
|
|
5065
5278
|
state,
|
|
5066
5279
|
...url ? { url } : {},
|
|
5067
|
-
labels: extractGithubIssueLabels(
|
|
5280
|
+
labels: extractGithubIssueLabels(issue2.labels)
|
|
5068
5281
|
};
|
|
5069
5282
|
}
|
|
5070
5283
|
function parseGithubIssueListOutput(stdout) {
|
|
5071
5284
|
const parsed = JSON.parse(stdout);
|
|
5072
5285
|
if (!Array.isArray(parsed)) return [];
|
|
5073
|
-
return parsed.map(normalizeGithubIssueListItem).filter((
|
|
5286
|
+
return parsed.map(normalizeGithubIssueListItem).filter((issue2) => issue2 !== null);
|
|
5074
5287
|
}
|
|
5075
5288
|
function parseGithubIssueViewOutput(stdout) {
|
|
5076
|
-
const
|
|
5077
|
-
if (!
|
|
5078
|
-
return
|
|
5289
|
+
const issue2 = normalizeGithubIssueListItem(JSON.parse(stdout));
|
|
5290
|
+
if (!issue2) throw new GithubIssueListError("failed to parse gh issue output");
|
|
5291
|
+
return issue2;
|
|
5079
5292
|
}
|
|
5080
5293
|
function buildGithubIssueListArgs(options) {
|
|
5081
5294
|
const search = singleLineGithubOption(options.search);
|
|
@@ -5640,21 +5853,21 @@ function printGithubIssues(issues) {
|
|
|
5640
5853
|
console.log("no GitHub issues");
|
|
5641
5854
|
return;
|
|
5642
5855
|
}
|
|
5643
|
-
for (const
|
|
5644
|
-
const labels =
|
|
5645
|
-
const url =
|
|
5856
|
+
for (const issue2 of issues) {
|
|
5857
|
+
const labels = issue2.labels.length ? ` #${issue2.labels.join(" #")}` : "";
|
|
5858
|
+
const url = issue2.url ? ` ${issue2.url}` : "";
|
|
5646
5859
|
console.log(
|
|
5647
|
-
`#${
|
|
5860
|
+
`#${issue2.number} ${issue2.state} ${issue2.title}${labels}${url}`
|
|
5648
5861
|
);
|
|
5649
5862
|
}
|
|
5650
5863
|
}
|
|
5651
|
-
function taskLinkIssuePayload(command,
|
|
5864
|
+
function taskLinkIssuePayload(command, issue2) {
|
|
5652
5865
|
return {
|
|
5653
5866
|
action: "link-github-issue",
|
|
5654
|
-
issue_number:
|
|
5867
|
+
issue_number: issue2.number,
|
|
5655
5868
|
repo: command.repo,
|
|
5656
|
-
title:
|
|
5657
|
-
url:
|
|
5869
|
+
title: issue2.title,
|
|
5870
|
+
url: issue2.url,
|
|
5658
5871
|
memo_label: "Memo:",
|
|
5659
5872
|
status: command.status,
|
|
5660
5873
|
priority: command.priority,
|
|
@@ -5804,13 +6017,13 @@ async function runJournalCli(argv) {
|
|
|
5804
6017
|
console.error(commandConfig.error);
|
|
5805
6018
|
process.exit(1);
|
|
5806
6019
|
}
|
|
5807
|
-
const
|
|
6020
|
+
const issue2 = await readGithubIssueAsync({
|
|
5808
6021
|
cwd: root,
|
|
5809
6022
|
number: command.issueNumber,
|
|
5810
6023
|
repo: command.repo
|
|
5811
6024
|
});
|
|
5812
6025
|
if (dryRun) {
|
|
5813
|
-
writePayload(taskLinkIssuePayload(command,
|
|
6026
|
+
writePayload(taskLinkIssuePayload(command, issue2));
|
|
5814
6027
|
return;
|
|
5815
6028
|
}
|
|
5816
6029
|
const serverUrl2 = await ensureServerUrl(root, server2, "/_journal");
|
|
@@ -5818,13 +6031,13 @@ async function runJournalCli(argv) {
|
|
|
5818
6031
|
serverUrl2,
|
|
5819
6032
|
"POST",
|
|
5820
6033
|
"journal task-link-issue",
|
|
5821
|
-
taskLinkIssuePayload(command,
|
|
6034
|
+
taskLinkIssuePayload(command, issue2)
|
|
5822
6035
|
);
|
|
5823
6036
|
if (command.json)
|
|
5824
6037
|
console.log(
|
|
5825
6038
|
JSON.stringify(
|
|
5826
6039
|
{
|
|
5827
|
-
issue,
|
|
6040
|
+
issue: issue2,
|
|
5828
6041
|
task: result2.task,
|
|
5829
6042
|
action: result2.created ? "created" : result2.moved ? "moved" : "existing"
|
|
5830
6043
|
},
|
|
@@ -5833,11 +6046,11 @@ async function runJournalCli(argv) {
|
|
|
5833
6046
|
)
|
|
5834
6047
|
);
|
|
5835
6048
|
else if (result2.created)
|
|
5836
|
-
console.log(`linked issue #${
|
|
6049
|
+
console.log(`linked issue #${issue2.number} to task ${result2.task.id}`);
|
|
5837
6050
|
else if (result2.moved)
|
|
5838
|
-
console.log(`moved linked issue #${
|
|
6051
|
+
console.log(`moved linked issue #${issue2.number} task ${result2.task.id}`);
|
|
5839
6052
|
else
|
|
5840
|
-
console.log(`issue #${
|
|
6053
|
+
console.log(`issue #${issue2.number} is linked to task ${result2.task.id}`);
|
|
5841
6054
|
return;
|
|
5842
6055
|
}
|
|
5843
6056
|
const serverUrl = await ensureServerUrl(root, server2, "/_journal");
|
|
@@ -10255,9 +10468,9 @@ function parseTerminalArgs(argv) {
|
|
|
10255
10468
|
function formatStateLine(record) {
|
|
10256
10469
|
const mark = needsAttention(record.state) ? "*" : " ";
|
|
10257
10470
|
const state = record.state.padEnd(7, " ");
|
|
10258
|
-
const source = record.source === "hook" ? "hook" : "
|
|
10471
|
+
const source = record.source === "hook" ? "hook " : record.source === "screen" ? "screen" : "motion";
|
|
10259
10472
|
const text2 = record.note || record.lastPrompt || "";
|
|
10260
|
-
return `${mark} ${state} ${source}
|
|
10473
|
+
return `${mark} ${state} ${source} ${record.target.padEnd(16, " ")} ${text2}`;
|
|
10261
10474
|
}
|
|
10262
10475
|
async function runTerminalCli(argv) {
|
|
10263
10476
|
const parsed = parseTerminalArgs(argv);
|
|
@@ -10305,9 +10518,16 @@ async function runTerminalCli(argv) {
|
|
|
10305
10518
|
const all = response2.states ?? [];
|
|
10306
10519
|
const states2 = command.attentionOnly ? all.filter((record) => needsAttention(record.state)) : all;
|
|
10307
10520
|
if (command.json) {
|
|
10308
|
-
console.log(
|
|
10521
|
+
console.log(
|
|
10522
|
+
JSON.stringify({ states: states2, errors: response2.errors ?? [] }, null, 2)
|
|
10523
|
+
);
|
|
10309
10524
|
return;
|
|
10310
10525
|
}
|
|
10526
|
+
for (const error of response2.errors ?? []) {
|
|
10527
|
+
const target = error.target ? ` ${error.target}` : "";
|
|
10528
|
+
console.error(`[${error.operation}${target}] ${error.detail}`);
|
|
10529
|
+
if (error.stack) console.error(error.stack);
|
|
10530
|
+
}
|
|
10311
10531
|
if (states2.length === 0) {
|
|
10312
10532
|
console.log("no terminals are reporting a state.");
|
|
10313
10533
|
return;
|
|
@@ -22174,7 +22394,7 @@ function toFileInfo(entry) {
|
|
|
22174
22394
|
kind: entry.kind
|
|
22175
22395
|
};
|
|
22176
22396
|
}
|
|
22177
|
-
function
|
|
22397
|
+
function errorMessage2(err) {
|
|
22178
22398
|
const raw = err instanceof Error ? err.message : String(err);
|
|
22179
22399
|
const withoutControl = Array.from(
|
|
22180
22400
|
raw,
|
|
@@ -22202,7 +22422,7 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
|
|
|
22202
22422
|
if (isAbortLikeError(err, signal)) throw err;
|
|
22203
22423
|
return {
|
|
22204
22424
|
entries: [svc],
|
|
22205
|
-
errors: [`${svc.serviceName}: ${
|
|
22425
|
+
errors: [`${svc.serviceName}: ${errorMessage2(err)}`]
|
|
22206
22426
|
};
|
|
22207
22427
|
}
|
|
22208
22428
|
if (dbs.length <= 1) {
|
|
@@ -22250,7 +22470,7 @@ async function createDbFilesResponse(cwd2, omitDirNames, signal, deps = DEFAULT_
|
|
|
22250
22470
|
const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
|
|
22251
22471
|
const dockerErrors = [];
|
|
22252
22472
|
if (dockerSettled.status === "rejected") {
|
|
22253
|
-
dockerErrors.push(
|
|
22473
|
+
dockerErrors.push(errorMessage2(dockerSettled.reason));
|
|
22254
22474
|
}
|
|
22255
22475
|
const dockerTruncated = dockerServices.truncated === true;
|
|
22256
22476
|
const { entries: dockerEntries, errors: listingErrors } = await expandDockerServicesForFiles(
|
|
@@ -23963,208 +24183,29 @@ var init_handle = __esm({
|
|
|
23963
24183
|
}
|
|
23964
24184
|
});
|
|
23965
24185
|
|
|
23966
|
-
// web-src/
|
|
23967
|
-
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
|
|
23973
|
-
|
|
23974
|
-
|
|
23975
|
-
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
}
|
|
23988
|
-
|
|
23989
|
-
}
|
|
23990
|
-
}
|
|
23991
|
-
function sanitizeObjectFields(value, ancestors, excludedKeys = /* @__PURE__ */ new Set()) {
|
|
23992
|
-
let keys;
|
|
23993
|
-
try {
|
|
23994
|
-
keys = Object.getOwnPropertyNames(value);
|
|
23995
|
-
} catch {
|
|
23996
|
-
return { value: "[Unserializable object]", removedSensitive: false };
|
|
23997
|
-
}
|
|
23998
|
-
const output = /* @__PURE__ */ Object.create(null);
|
|
23999
|
-
let removedSensitive = false;
|
|
24000
|
-
for (const key of keys) {
|
|
24001
|
-
if (excludedKeys.has(key)) continue;
|
|
24002
|
-
if (isSensitiveFieldName(key)) {
|
|
24003
|
-
removedSensitive = true;
|
|
24004
|
-
continue;
|
|
24005
|
-
}
|
|
24006
|
-
let descriptor;
|
|
24007
|
-
try {
|
|
24008
|
-
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
24009
|
-
} catch {
|
|
24010
|
-
output[key] = "[Unserializable field]";
|
|
24011
|
-
continue;
|
|
24012
|
-
}
|
|
24013
|
-
if (!descriptor) continue;
|
|
24014
|
-
if (!("value" in descriptor)) {
|
|
24015
|
-
output[key] = "[Accessor]";
|
|
24016
|
-
continue;
|
|
24017
|
-
}
|
|
24018
|
-
const sanitized = sanitizeValue(descriptor.value, ancestors);
|
|
24019
|
-
if (sanitized === OMIT_VALUE) {
|
|
24020
|
-
removedSensitive = true;
|
|
24021
|
-
continue;
|
|
24022
|
-
}
|
|
24023
|
-
output[key] = sanitized.value;
|
|
24024
|
-
removedSensitive ||= sanitized.removedSensitive;
|
|
24025
|
-
}
|
|
24026
|
-
if (Object.keys(output).length === 0 && removedSensitive) return OMIT_VALUE;
|
|
24027
|
-
return { value: output, removedSensitive };
|
|
24028
|
-
}
|
|
24029
|
-
function sanitizeError(error, ancestors) {
|
|
24030
|
-
const output = /* @__PURE__ */ Object.create(null);
|
|
24031
|
-
output.name = errorName(error);
|
|
24032
|
-
output.message = errorMessage2(error);
|
|
24033
|
-
const fields = sanitizeObjectFields(
|
|
24034
|
-
error,
|
|
24035
|
-
ancestors,
|
|
24036
|
-
/* @__PURE__ */ new Set(["name", "message", "stack"])
|
|
24037
|
-
);
|
|
24038
|
-
if (fields !== OMIT_VALUE) Object.assign(output, fields.value);
|
|
24039
|
-
return {
|
|
24040
|
-
value: output,
|
|
24041
|
-
removedSensitive: fields === OMIT_VALUE ? true : fields.removedSensitive
|
|
24042
|
-
};
|
|
24043
|
-
}
|
|
24044
|
-
function sanitizeValue(value, ancestors) {
|
|
24045
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
24046
|
-
return { value, removedSensitive: false };
|
|
24047
|
-
if (typeof value === "number") {
|
|
24048
|
-
return {
|
|
24049
|
-
value: Number.isFinite(value) ? value : String(value),
|
|
24050
|
-
removedSensitive: false
|
|
24051
|
-
};
|
|
24052
|
-
}
|
|
24053
|
-
if (typeof value === "bigint") {
|
|
24054
|
-
return { value: `${value}n`, removedSensitive: false };
|
|
24055
|
-
}
|
|
24056
|
-
if (typeof value === "undefined") {
|
|
24057
|
-
return { value: "[undefined]", removedSensitive: false };
|
|
24058
|
-
}
|
|
24059
|
-
if (typeof value === "symbol") {
|
|
24060
|
-
return { value: "[symbol]", removedSensitive: false };
|
|
24061
|
-
}
|
|
24062
|
-
if (typeof value === "function") {
|
|
24063
|
-
return { value: "[function]", removedSensitive: false };
|
|
24064
|
-
}
|
|
24065
|
-
const objectValue = value;
|
|
24066
|
-
if (ancestors.has(objectValue)) {
|
|
24067
|
-
return { value: "[Circular]", removedSensitive: false };
|
|
24068
|
-
}
|
|
24069
|
-
ancestors.add(objectValue);
|
|
24070
|
-
try {
|
|
24071
|
-
if (Array.isArray(objectValue)) {
|
|
24072
|
-
const output = [];
|
|
24073
|
-
let removedSensitive = false;
|
|
24074
|
-
for (const item of objectValue) {
|
|
24075
|
-
const sanitized = sanitizeValue(item, ancestors);
|
|
24076
|
-
if (sanitized === OMIT_VALUE) {
|
|
24077
|
-
removedSensitive = true;
|
|
24078
|
-
continue;
|
|
24079
|
-
}
|
|
24080
|
-
output.push(sanitized.value);
|
|
24081
|
-
removedSensitive ||= sanitized.removedSensitive;
|
|
24082
|
-
}
|
|
24083
|
-
if (output.length === 0 && removedSensitive) return OMIT_VALUE;
|
|
24084
|
-
return { value: output, removedSensitive };
|
|
24085
|
-
}
|
|
24086
|
-
if (objectValue instanceof Error)
|
|
24087
|
-
return sanitizeError(objectValue, ancestors);
|
|
24088
|
-
return sanitizeObjectFields(objectValue, ancestors);
|
|
24089
|
-
} catch {
|
|
24090
|
-
return { value: "[Unserializable object]", removedSensitive: false };
|
|
24091
|
-
} finally {
|
|
24092
|
-
ancestors.delete(objectValue);
|
|
24093
|
-
}
|
|
24094
|
-
}
|
|
24095
|
-
function formatNonError(value) {
|
|
24096
|
-
if (typeof value === "string") return value;
|
|
24097
|
-
try {
|
|
24098
|
-
const sanitized = sanitizeValue(value, /* @__PURE__ */ new Set());
|
|
24099
|
-
const serializable = sanitized === OMIT_VALUE ? {} : sanitized.value;
|
|
24100
|
-
return JSON.stringify(serializable);
|
|
24101
|
-
} catch {
|
|
24102
|
-
return "[Unserializable value]";
|
|
24103
|
-
}
|
|
24104
|
-
}
|
|
24105
|
-
function formatErrorFields(error) {
|
|
24106
|
-
const fields = sanitizeObjectFields(
|
|
24107
|
-
error,
|
|
24108
|
-
/* @__PURE__ */ new Set([error]),
|
|
24109
|
-
/* @__PURE__ */ new Set(["name", "message", "stack", "cause"])
|
|
24110
|
-
);
|
|
24111
|
-
if (fields === OMIT_VALUE) return "";
|
|
24112
|
-
const output = fields.value;
|
|
24113
|
-
return Object.keys(output).length > 0 ? `
|
|
24114
|
-
Details: ${JSON.stringify(output)}` : "";
|
|
24115
|
-
}
|
|
24116
|
-
function formatErrorDetail(error) {
|
|
24117
|
-
const parts = [];
|
|
24118
|
-
const seen2 = /* @__PURE__ */ new Set();
|
|
24119
|
-
let current = error;
|
|
24120
|
-
while (current instanceof Error && !seen2.has(current)) {
|
|
24121
|
-
seen2.add(current);
|
|
24122
|
-
parts.push(
|
|
24123
|
-
`${errorName(current)}: ${errorMessage2(current)}${formatErrorFields(current)}`
|
|
24124
|
-
);
|
|
24125
|
-
try {
|
|
24126
|
-
current = current.cause;
|
|
24127
|
-
} catch {
|
|
24128
|
-
current = "[Unserializable error cause]";
|
|
24129
|
-
}
|
|
24130
|
-
}
|
|
24131
|
-
if (current !== void 0) {
|
|
24132
|
-
parts.push(
|
|
24133
|
-
seen2.has(current) ? "Error cause cycle detected" : formatNonError(current)
|
|
24134
|
-
);
|
|
24135
|
-
}
|
|
24136
|
-
return parts.join("\nCaused by: ") || formatNonError(error);
|
|
24137
|
-
}
|
|
24138
|
-
var OMIT_VALUE;
|
|
24139
|
-
var init_error_detail = __esm({
|
|
24140
|
-
"web-src/core/error-detail.ts"() {
|
|
24141
|
-
OMIT_VALUE = /* @__PURE__ */ Symbol("omit-sensitive-error-field");
|
|
24142
|
-
}
|
|
24143
|
-
});
|
|
24144
|
-
|
|
24145
|
-
// web-src/server/shell/session.ts
|
|
24146
|
-
var session_exports = {};
|
|
24147
|
-
__export(session_exports, {
|
|
24148
|
-
closeAllShellSessions: () => closeAllShellSessions,
|
|
24149
|
-
closeShellSession: () => closeShellSession,
|
|
24150
|
-
createShellSession: () => createShellSession,
|
|
24151
|
-
describeShellAvailability: () => describeShellAvailability,
|
|
24152
|
-
getShellSession: () => getShellSession,
|
|
24153
|
-
listShellSessions: () => listShellSessions,
|
|
24154
|
-
readShellBuffer: () => readShellBuffer,
|
|
24155
|
-
resizeShell: () => resizeShell,
|
|
24156
|
-
subscribeShell: () => subscribeShell,
|
|
24157
|
-
writeToShell: () => writeToShell,
|
|
24158
|
-
writeToShellWhenReady: () => writeToShellWhenReady
|
|
24159
|
-
});
|
|
24160
|
-
function loadPty() {
|
|
24161
|
-
if (!ptyModulePromise) {
|
|
24162
|
-
ptyModulePromise = import("@lydell/node-pty").then((mod) => mod).catch((err) => {
|
|
24163
|
-
ptyLoadError = errorWithCause("failed to load the PTY module", err);
|
|
24164
|
-
return null;
|
|
24165
|
-
});
|
|
24166
|
-
}
|
|
24167
|
-
return ptyModulePromise;
|
|
24186
|
+
// web-src/server/shell/session.ts
|
|
24187
|
+
var session_exports = {};
|
|
24188
|
+
__export(session_exports, {
|
|
24189
|
+
closeAllShellSessions: () => closeAllShellSessions,
|
|
24190
|
+
closeShellSession: () => closeShellSession,
|
|
24191
|
+
createShellSession: () => createShellSession,
|
|
24192
|
+
describeShellAvailability: () => describeShellAvailability,
|
|
24193
|
+
getShellSession: () => getShellSession,
|
|
24194
|
+
listShellSessions: () => listShellSessions,
|
|
24195
|
+
readShellBuffer: () => readShellBuffer,
|
|
24196
|
+
resizeShell: () => resizeShell,
|
|
24197
|
+
subscribeShell: () => subscribeShell,
|
|
24198
|
+
writeToShell: () => writeToShell,
|
|
24199
|
+
writeToShellWhenReady: () => writeToShellWhenReady
|
|
24200
|
+
});
|
|
24201
|
+
function loadPty() {
|
|
24202
|
+
if (!ptyModulePromise) {
|
|
24203
|
+
ptyModulePromise = import("@lydell/node-pty").then((mod) => mod).catch((err) => {
|
|
24204
|
+
ptyLoadError = errorWithCause("failed to load the PTY module", err);
|
|
24205
|
+
return null;
|
|
24206
|
+
});
|
|
24207
|
+
}
|
|
24208
|
+
return ptyModulePromise;
|
|
24168
24209
|
}
|
|
24169
24210
|
async function describeShellAvailability() {
|
|
24170
24211
|
const pty = await loadPty();
|
|
@@ -26986,76 +27027,676 @@ var init_search_service = __esm({
|
|
|
26986
27027
|
}
|
|
26987
27028
|
});
|
|
26988
27029
|
|
|
26989
|
-
// web-src/
|
|
26990
|
-
function
|
|
26991
|
-
|
|
27030
|
+
// web-src/core/terminal-paste.ts
|
|
27031
|
+
function pasteImageExtension(mime) {
|
|
27032
|
+
if (typeof mime !== "string") return null;
|
|
27033
|
+
const base = mime.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
27034
|
+
return PASTE_IMAGE_TYPES[base] ?? null;
|
|
26992
27035
|
}
|
|
26993
|
-
function
|
|
26994
|
-
|
|
26995
|
-
let oldestKey = null;
|
|
26996
|
-
let oldestAt = Number.POSITIVE_INFINITY;
|
|
26997
|
-
for (const [key, record] of states) {
|
|
26998
|
-
if (record.updatedAt < oldestAt) {
|
|
26999
|
-
oldestAt = record.updatedAt;
|
|
27000
|
-
oldestKey = key;
|
|
27001
|
-
}
|
|
27002
|
-
}
|
|
27003
|
-
if (oldestKey === null) return;
|
|
27004
|
-
states.delete(oldestKey);
|
|
27005
|
-
}
|
|
27036
|
+
function looksLikeBase64(value) {
|
|
27037
|
+
return typeof value === "string" && value.length > 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
27006
27038
|
}
|
|
27007
|
-
function
|
|
27008
|
-
const
|
|
27009
|
-
|
|
27010
|
-
if (!next) return null;
|
|
27011
|
-
if (input.source === "activity" && previous?.source === "hook") {
|
|
27012
|
-
const promoting = input.override === true && next === "working";
|
|
27013
|
-
if (!promoting) return previous;
|
|
27014
|
-
}
|
|
27015
|
-
const at = Number.isFinite(input.at) ? input.at : Date.now();
|
|
27016
|
-
if (previous && previous.source === "hook" && input.source === "hook" && at < previous.updatedAt) {
|
|
27017
|
-
return previous;
|
|
27018
|
-
}
|
|
27019
|
-
const record = {
|
|
27020
|
-
target: input.target,
|
|
27021
|
-
state: next,
|
|
27022
|
-
source: input.source,
|
|
27023
|
-
updatedAt: at,
|
|
27024
|
-
// 添え物は送られてこなければ前の値を残す。ターンの途中で毎回指示文を
|
|
27025
|
-
// 送り直させないため。
|
|
27026
|
-
lastPrompt: clip(input.lastPrompt ?? previous?.lastPrompt ?? ""),
|
|
27027
|
-
note: clip(input.note ?? previous?.note ?? "")
|
|
27028
|
-
};
|
|
27029
|
-
states.set(input.target, record);
|
|
27030
|
-
evictOldest2();
|
|
27031
|
-
return record;
|
|
27039
|
+
function base64ByteLength(value) {
|
|
27040
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
27041
|
+
return Math.floor(value.length * 3 / 4) - padding;
|
|
27032
27042
|
}
|
|
27033
|
-
|
|
27034
|
-
|
|
27043
|
+
var PASTE_IMAGE_TYPES, MAX_PASTE_IMAGE_BYTES, MAX_PASTE_BODY_BYTES, SHIFT_ENTER_SEQUENCE;
|
|
27044
|
+
var init_terminal_paste = __esm({
|
|
27045
|
+
"web-src/core/terminal-paste.ts"() {
|
|
27046
|
+
PASTE_IMAGE_TYPES = {
|
|
27047
|
+
"image/png": "png",
|
|
27048
|
+
"image/jpeg": "jpg",
|
|
27049
|
+
"image/gif": "gif",
|
|
27050
|
+
"image/webp": "webp"
|
|
27051
|
+
};
|
|
27052
|
+
MAX_PASTE_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27053
|
+
MAX_PASTE_BODY_BYTES = Math.ceil(MAX_PASTE_IMAGE_BYTES * 1.4);
|
|
27054
|
+
SHIFT_ENTER_SEQUENCE = `${String.fromCharCode(27)}[200~${String.fromCharCode(10)}${String.fromCharCode(27)}[201~`;
|
|
27055
|
+
}
|
|
27056
|
+
});
|
|
27057
|
+
|
|
27058
|
+
// web-src/core/terminal-images.ts
|
|
27059
|
+
function stripAnsi(text2) {
|
|
27060
|
+
return text2.replace(ANSI_RE, "");
|
|
27035
27061
|
}
|
|
27036
|
-
function
|
|
27037
|
-
|
|
27038
|
-
|
|
27039
|
-
|
|
27062
|
+
function terminalImageExtension(path) {
|
|
27063
|
+
const dot = path.lastIndexOf(".");
|
|
27064
|
+
if (dot < 0) return null;
|
|
27065
|
+
const extension = path.slice(dot + 1).toLowerCase();
|
|
27066
|
+
return TERMINAL_IMAGE_EXTENSIONS.includes(extension) ? extension : null;
|
|
27067
|
+
}
|
|
27068
|
+
var TERMINAL_IMAGE_EXTENSIONS, MAX_TERMINAL_IMAGE_QUERY, ESC, BEL, ANSI_RE, PATH_CHAR, NAME_CHAR, IMAGE_PATH_RE;
|
|
27069
|
+
var init_terminal_images = __esm({
|
|
27070
|
+
"web-src/core/terminal-images.ts"() {
|
|
27071
|
+
init_terminal_paste();
|
|
27072
|
+
TERMINAL_IMAGE_EXTENSIONS = [
|
|
27073
|
+
...new Set(Object.values(PASTE_IMAGE_TYPES)),
|
|
27074
|
+
"jpeg"
|
|
27075
|
+
];
|
|
27076
|
+
MAX_TERMINAL_IMAGE_QUERY = 16;
|
|
27077
|
+
ESC = String.fromCharCode(27);
|
|
27078
|
+
BEL = String.fromCharCode(7);
|
|
27079
|
+
ANSI_RE = new RegExp(
|
|
27080
|
+
`${ESC}\\[[0-9;?]*[ -/]*[@-~]|${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|${ESC}[@-Z\\\\-_]`,
|
|
27081
|
+
"g"
|
|
27082
|
+
);
|
|
27083
|
+
PATH_CHAR = "[\\p{L}\\p{N}._~+@%/-]";
|
|
27084
|
+
NAME_CHAR = "[\\p{L}\\p{N}_~+@%-]";
|
|
27085
|
+
IMAGE_PATH_RE = new RegExp(
|
|
27086
|
+
`${PATH_CHAR}*${NAME_CHAR}\\.(?:${TERMINAL_IMAGE_EXTENSIONS.join("|")})(?![\\p{L}\\p{N}])`,
|
|
27087
|
+
"giu"
|
|
27088
|
+
);
|
|
27089
|
+
}
|
|
27090
|
+
});
|
|
27091
|
+
|
|
27092
|
+
// web-src/core/agent-screen.ts
|
|
27093
|
+
function isRecord2(value) {
|
|
27094
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
27095
|
+
}
|
|
27096
|
+
function issue(errors, path, code, message) {
|
|
27097
|
+
errors.push({ path, code, message });
|
|
27098
|
+
}
|
|
27099
|
+
function stringList(value, path, maxLength, errors, validate) {
|
|
27100
|
+
if (value === void 0) return void 0;
|
|
27101
|
+
if (!Array.isArray(value)) {
|
|
27102
|
+
issue(errors, path, "invalid_type", "must be an array of strings");
|
|
27103
|
+
return void 0;
|
|
27104
|
+
}
|
|
27105
|
+
if (value.length === 0 || value.length > MAX_MATCHERS_PER_LIST) {
|
|
27106
|
+
issue(
|
|
27107
|
+
errors,
|
|
27108
|
+
path,
|
|
27109
|
+
"invalid_length",
|
|
27110
|
+
`must contain 1-${MAX_MATCHERS_PER_LIST} items`
|
|
27111
|
+
);
|
|
27112
|
+
}
|
|
27113
|
+
const out = [];
|
|
27114
|
+
value.forEach((item, index) => {
|
|
27115
|
+
const itemPath = `${path}[${index}]`;
|
|
27116
|
+
if (typeof item !== "string") {
|
|
27117
|
+
issue(errors, itemPath, "invalid_type", "must be a string");
|
|
27118
|
+
return;
|
|
27119
|
+
}
|
|
27120
|
+
if (item.length === 0 || item.length > maxLength) {
|
|
27121
|
+
issue(
|
|
27122
|
+
errors,
|
|
27123
|
+
itemPath,
|
|
27124
|
+
"invalid_length",
|
|
27125
|
+
`must contain 1-${maxLength} characters`
|
|
27126
|
+
);
|
|
27127
|
+
return;
|
|
27128
|
+
}
|
|
27129
|
+
validate?.(item, itemPath);
|
|
27130
|
+
out.push(item);
|
|
27040
27131
|
});
|
|
27132
|
+
return out;
|
|
27041
27133
|
}
|
|
27042
|
-
function
|
|
27043
|
-
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
|
|
27047
|
-
|
|
27134
|
+
function validateRegex(pattern, path, errors) {
|
|
27135
|
+
try {
|
|
27136
|
+
compileNativeRegex(pattern);
|
|
27137
|
+
} catch (error) {
|
|
27138
|
+
issue(
|
|
27139
|
+
errors,
|
|
27140
|
+
path,
|
|
27141
|
+
"invalid_regex",
|
|
27142
|
+
error instanceof Error ? error.message : String(error)
|
|
27143
|
+
);
|
|
27144
|
+
return;
|
|
27145
|
+
}
|
|
27146
|
+
const unsafeReason = unsafeRegexReason(pattern);
|
|
27147
|
+
if (unsafeReason) {
|
|
27148
|
+
issue(errors, path, "unsafe_regex", unsafeReason);
|
|
27149
|
+
return;
|
|
27150
|
+
}
|
|
27151
|
+
}
|
|
27152
|
+
function unsafeRegexReason(pattern) {
|
|
27153
|
+
const source = pattern.startsWith("(?i)") ? pattern.slice(4) : pattern;
|
|
27154
|
+
let inCharacterClass = false;
|
|
27155
|
+
let quantifiers = 0;
|
|
27156
|
+
let variableRepetitions = 0;
|
|
27157
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
27158
|
+
const char = source[index];
|
|
27159
|
+
if (char === "\\") {
|
|
27160
|
+
const escaped = source[index + 1];
|
|
27161
|
+
if (escaped === void 0) break;
|
|
27162
|
+
if (/^[1-9]$/.test(escaped) || escaped === "k") {
|
|
27163
|
+
return "backreferences are not supported";
|
|
27164
|
+
}
|
|
27165
|
+
if ((escaped === "p" || escaped === "P") && source[index + 2] === "{") {
|
|
27166
|
+
const end = source.indexOf("}", index + 3);
|
|
27167
|
+
if (end < 0) break;
|
|
27168
|
+
index = end;
|
|
27169
|
+
continue;
|
|
27170
|
+
}
|
|
27171
|
+
index += 1;
|
|
27172
|
+
continue;
|
|
27173
|
+
}
|
|
27174
|
+
if (char === "[") {
|
|
27175
|
+
inCharacterClass = true;
|
|
27176
|
+
continue;
|
|
27177
|
+
}
|
|
27178
|
+
if (char === "]" && inCharacterClass) {
|
|
27179
|
+
inCharacterClass = false;
|
|
27180
|
+
continue;
|
|
27181
|
+
}
|
|
27182
|
+
if (inCharacterClass) continue;
|
|
27183
|
+
if (char === "(" || char === ")" || char === "|") {
|
|
27184
|
+
return "groups and alternation are not supported; use all or any matchers";
|
|
27185
|
+
}
|
|
27186
|
+
if (char === "*" || char === "+" || char === "?") {
|
|
27187
|
+
quantifiers += 1;
|
|
27188
|
+
variableRepetitions += 1;
|
|
27189
|
+
continue;
|
|
27048
27190
|
}
|
|
27191
|
+
if (char !== "{") continue;
|
|
27192
|
+
const match = source.slice(index).match(/^\{(\d+)(?:,(\d*))?\}/);
|
|
27193
|
+
if (!match) continue;
|
|
27194
|
+
quantifiers += 1;
|
|
27195
|
+
const lower = Number(match[1]);
|
|
27196
|
+
const hasComma = match[2] !== void 0;
|
|
27197
|
+
const upper = hasComma && match[2] !== "" ? Number(match[2]) : lower;
|
|
27198
|
+
if (hasComma && (match[2] === "" || upper !== lower)) {
|
|
27199
|
+
variableRepetitions += 1;
|
|
27200
|
+
}
|
|
27201
|
+
if (lower > MAX_REGEX_BOUNDED_REPEAT || upper > MAX_REGEX_BOUNDED_REPEAT) {
|
|
27202
|
+
return `bounded repetitions must not exceed ${MAX_REGEX_BOUNDED_REPEAT}`;
|
|
27203
|
+
}
|
|
27204
|
+
index += match[0].length - 1;
|
|
27049
27205
|
}
|
|
27050
|
-
|
|
27206
|
+
if (quantifiers > MAX_REGEX_QUANTIFIERS) {
|
|
27207
|
+
return `must not contain more than ${MAX_REGEX_QUANTIFIERS} quantifiers`;
|
|
27208
|
+
}
|
|
27209
|
+
if (variableRepetitions > MAX_REGEX_VARIABLE_REPETITIONS) {
|
|
27210
|
+
return "must not contain more than one variable-length repetition";
|
|
27211
|
+
}
|
|
27212
|
+
return null;
|
|
27051
27213
|
}
|
|
27052
|
-
|
|
27053
|
-
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27057
|
-
|
|
27058
|
-
|
|
27214
|
+
function parseMatcher(raw, path, depth, errors) {
|
|
27215
|
+
if (!isRecord2(raw)) {
|
|
27216
|
+
issue(errors, path, "invalid_type", "must be an object");
|
|
27217
|
+
return null;
|
|
27218
|
+
}
|
|
27219
|
+
if (depth > MAX_MATCHER_DEPTH) {
|
|
27220
|
+
issue(
|
|
27221
|
+
errors,
|
|
27222
|
+
path,
|
|
27223
|
+
"too_deep",
|
|
27224
|
+
`matcher nesting must not exceed ${MAX_MATCHER_DEPTH}`
|
|
27225
|
+
);
|
|
27226
|
+
return null;
|
|
27227
|
+
}
|
|
27228
|
+
for (const key of Object.keys(raw)) {
|
|
27229
|
+
if (!MATCHER_KEYS.has(key)) {
|
|
27230
|
+
issue(errors, `${path}.${key}`, "unknown_field", "is not supported");
|
|
27231
|
+
}
|
|
27232
|
+
}
|
|
27233
|
+
const matcher = {};
|
|
27234
|
+
const contains = stringList(
|
|
27235
|
+
raw.contains,
|
|
27236
|
+
`${path}.contains`,
|
|
27237
|
+
MAX_CONTAINS_LENGTH,
|
|
27238
|
+
errors
|
|
27239
|
+
);
|
|
27240
|
+
if (contains) matcher.contains = contains;
|
|
27241
|
+
const regex = stringList(
|
|
27242
|
+
raw.regex,
|
|
27243
|
+
`${path}.regex`,
|
|
27244
|
+
MAX_PATTERN_LENGTH,
|
|
27245
|
+
errors,
|
|
27246
|
+
(pattern, itemPath) => validateRegex(pattern, itemPath, errors)
|
|
27247
|
+
);
|
|
27248
|
+
if (regex) matcher.regex = regex;
|
|
27249
|
+
const lineRegex = stringList(
|
|
27250
|
+
raw.lineRegex,
|
|
27251
|
+
`${path}.lineRegex`,
|
|
27252
|
+
MAX_PATTERN_LENGTH,
|
|
27253
|
+
errors,
|
|
27254
|
+
(pattern, itemPath) => validateRegex(pattern, itemPath, errors)
|
|
27255
|
+
);
|
|
27256
|
+
if (lineRegex) matcher.lineRegex = lineRegex;
|
|
27257
|
+
for (const key of ["all", "any", "not"]) {
|
|
27258
|
+
const value = raw[key];
|
|
27259
|
+
if (value === void 0) continue;
|
|
27260
|
+
if (!Array.isArray(value)) {
|
|
27261
|
+
issue(errors, `${path}.${key}`, "invalid_type", "must be an array");
|
|
27262
|
+
continue;
|
|
27263
|
+
}
|
|
27264
|
+
if (value.length === 0 || value.length > MAX_MATCHERS_PER_LIST) {
|
|
27265
|
+
issue(
|
|
27266
|
+
errors,
|
|
27267
|
+
`${path}.${key}`,
|
|
27268
|
+
"invalid_length",
|
|
27269
|
+
`must contain 1-${MAX_MATCHERS_PER_LIST} matchers`
|
|
27270
|
+
);
|
|
27271
|
+
}
|
|
27272
|
+
const nested = value.flatMap((item, index) => {
|
|
27273
|
+
const parsed = parseMatcher(
|
|
27274
|
+
item,
|
|
27275
|
+
`${path}.${key}[${index}]`,
|
|
27276
|
+
depth + 1,
|
|
27277
|
+
errors
|
|
27278
|
+
);
|
|
27279
|
+
return parsed ? [parsed] : [];
|
|
27280
|
+
});
|
|
27281
|
+
matcher[key] = nested;
|
|
27282
|
+
}
|
|
27283
|
+
if (!Object.keys(matcher).length) {
|
|
27284
|
+
issue(errors, path, "empty_matcher", "must contain a match condition");
|
|
27285
|
+
}
|
|
27286
|
+
return matcher;
|
|
27287
|
+
}
|
|
27288
|
+
function parseRule(raw, index, errors) {
|
|
27289
|
+
const path = `rules[${index}]`;
|
|
27290
|
+
if (!isRecord2(raw)) {
|
|
27291
|
+
issue(errors, path, "invalid_type", "must be an object");
|
|
27292
|
+
return null;
|
|
27293
|
+
}
|
|
27294
|
+
for (const key of Object.keys(raw)) {
|
|
27295
|
+
if (!RULE_KEYS.has(key)) {
|
|
27296
|
+
issue(errors, `${path}.${key}`, "unknown_field", "is not supported");
|
|
27297
|
+
}
|
|
27298
|
+
}
|
|
27299
|
+
const matcher = parseMatcher(
|
|
27300
|
+
Object.fromEntries(
|
|
27301
|
+
Object.entries(raw).filter(([key]) => MATCHER_KEYS.has(key))
|
|
27302
|
+
),
|
|
27303
|
+
path,
|
|
27304
|
+
0,
|
|
27305
|
+
errors
|
|
27306
|
+
);
|
|
27307
|
+
const id = raw.id;
|
|
27308
|
+
if (typeof id !== "string" || !RULE_ID_RE.test(id)) {
|
|
27309
|
+
issue(
|
|
27310
|
+
errors,
|
|
27311
|
+
`${path}.id`,
|
|
27312
|
+
"invalid_id",
|
|
27313
|
+
"must use 1-64 lowercase letters, digits, underscores, or hyphens"
|
|
27314
|
+
);
|
|
27315
|
+
}
|
|
27316
|
+
const state = raw.state;
|
|
27317
|
+
if (state !== "working" && state !== "waiting" && state !== "idle" && state !== "skip") {
|
|
27318
|
+
issue(
|
|
27319
|
+
errors,
|
|
27320
|
+
`${path}.state`,
|
|
27321
|
+
"invalid_state",
|
|
27322
|
+
"must be working, waiting, idle, or skip"
|
|
27323
|
+
);
|
|
27324
|
+
}
|
|
27325
|
+
const priority = raw.priority;
|
|
27326
|
+
if (typeof priority !== "number" || !Number.isInteger(priority) || Math.abs(priority) > MAX_PRIORITY) {
|
|
27327
|
+
issue(
|
|
27328
|
+
errors,
|
|
27329
|
+
`${path}.priority`,
|
|
27330
|
+
"invalid_priority",
|
|
27331
|
+
`must be an integer between -${MAX_PRIORITY} and ${MAX_PRIORITY}`
|
|
27332
|
+
);
|
|
27333
|
+
}
|
|
27334
|
+
const region = raw.region;
|
|
27335
|
+
if (typeof region !== "string" || !AGENT_SCREEN_REGIONS.includes(region)) {
|
|
27336
|
+
issue(
|
|
27337
|
+
errors,
|
|
27338
|
+
`${path}.region`,
|
|
27339
|
+
"invalid_region",
|
|
27340
|
+
`must be one of ${AGENT_SCREEN_REGIONS.join(", ")}`
|
|
27341
|
+
);
|
|
27342
|
+
}
|
|
27343
|
+
const lines = raw.lines;
|
|
27344
|
+
if (region === "bottom_non_empty") {
|
|
27345
|
+
if (typeof lines !== "number" || !Number.isInteger(lines) || lines < 1 || lines > MAX_REGION_LINES) {
|
|
27346
|
+
issue(
|
|
27347
|
+
errors,
|
|
27348
|
+
`${path}.lines`,
|
|
27349
|
+
"invalid_lines",
|
|
27350
|
+
`must be an integer between 1 and ${MAX_REGION_LINES}`
|
|
27351
|
+
);
|
|
27352
|
+
}
|
|
27353
|
+
} else if (lines !== void 0) {
|
|
27354
|
+
issue(
|
|
27355
|
+
errors,
|
|
27356
|
+
`${path}.lines`,
|
|
27357
|
+
"unexpected_lines",
|
|
27358
|
+
"is only valid with bottom_non_empty"
|
|
27359
|
+
);
|
|
27360
|
+
}
|
|
27361
|
+
if (!matcher || typeof id !== "string" || !RULE_ID_RE.test(id) || state !== "working" && state !== "waiting" && state !== "idle" && state !== "skip" || typeof priority !== "number" || !Number.isInteger(priority) || Math.abs(priority) > MAX_PRIORITY || typeof region !== "string" || !AGENT_SCREEN_REGIONS.includes(region) || region === "bottom_non_empty" && (typeof lines !== "number" || !Number.isInteger(lines) || lines < 1 || lines > MAX_REGION_LINES) || region !== "bottom_non_empty" && lines !== void 0) {
|
|
27362
|
+
return null;
|
|
27363
|
+
}
|
|
27364
|
+
return {
|
|
27365
|
+
id,
|
|
27366
|
+
state,
|
|
27367
|
+
priority,
|
|
27368
|
+
region,
|
|
27369
|
+
...typeof lines === "number" ? { lines } : {},
|
|
27370
|
+
...matcher
|
|
27371
|
+
};
|
|
27372
|
+
}
|
|
27373
|
+
function parseAgentScreenRuleSet(raw) {
|
|
27374
|
+
const errors = [];
|
|
27375
|
+
if (!isRecord2(raw)) {
|
|
27376
|
+
return {
|
|
27377
|
+
ok: false,
|
|
27378
|
+
errors: [
|
|
27379
|
+
{ path: "$", code: "invalid_type", message: "must be an object" }
|
|
27380
|
+
]
|
|
27381
|
+
};
|
|
27382
|
+
}
|
|
27383
|
+
for (const key of Object.keys(raw)) {
|
|
27384
|
+
if (key !== "version" && key !== "rules") {
|
|
27385
|
+
issue(errors, key, "unknown_field", "is not supported");
|
|
27386
|
+
}
|
|
27387
|
+
}
|
|
27388
|
+
if (raw.version !== AGENT_SCREEN_RULE_SET_VERSION) {
|
|
27389
|
+
issue(
|
|
27390
|
+
errors,
|
|
27391
|
+
"version",
|
|
27392
|
+
"unsupported_version",
|
|
27393
|
+
`must be ${AGENT_SCREEN_RULE_SET_VERSION}`
|
|
27394
|
+
);
|
|
27395
|
+
}
|
|
27396
|
+
if (!Array.isArray(raw.rules)) {
|
|
27397
|
+
issue(errors, "rules", "invalid_type", "must be an array");
|
|
27398
|
+
return { ok: false, errors };
|
|
27399
|
+
}
|
|
27400
|
+
if (raw.rules.length > MAX_RULES) {
|
|
27401
|
+
issue(
|
|
27402
|
+
errors,
|
|
27403
|
+
"rules",
|
|
27404
|
+
"too_many_rules",
|
|
27405
|
+
`must contain at most ${MAX_RULES} rules`
|
|
27406
|
+
);
|
|
27407
|
+
}
|
|
27408
|
+
const rules = raw.rules.flatMap((item, index) => {
|
|
27409
|
+
const parsed = parseRule(item, index, errors);
|
|
27410
|
+
return parsed ? [parsed] : [];
|
|
27411
|
+
});
|
|
27412
|
+
const ids = /* @__PURE__ */ new Map();
|
|
27413
|
+
raw.rules.forEach((rule, index) => {
|
|
27414
|
+
if (!isRecord2(rule) || typeof rule.id !== "string" || !RULE_ID_RE.test(rule.id)) {
|
|
27415
|
+
return;
|
|
27416
|
+
}
|
|
27417
|
+
const previous = ids.get(rule.id);
|
|
27418
|
+
if (previous !== void 0) {
|
|
27419
|
+
issue(
|
|
27420
|
+
errors,
|
|
27421
|
+
`rules[${index}].id`,
|
|
27422
|
+
"duplicate_id",
|
|
27423
|
+
`duplicates rules[${previous}].id`
|
|
27424
|
+
);
|
|
27425
|
+
} else {
|
|
27426
|
+
ids.set(rule.id, index);
|
|
27427
|
+
}
|
|
27428
|
+
});
|
|
27429
|
+
if (errors.length) return { ok: false, errors };
|
|
27430
|
+
return {
|
|
27431
|
+
ok: true,
|
|
27432
|
+
value: { version: AGENT_SCREEN_RULE_SET_VERSION, rules }
|
|
27433
|
+
};
|
|
27434
|
+
}
|
|
27435
|
+
function formatAgentScreenRuleSet(rules) {
|
|
27436
|
+
return `${JSON.stringify(rules, null, 2)}
|
|
27437
|
+
`;
|
|
27438
|
+
}
|
|
27439
|
+
function lastOscTitle(raw) {
|
|
27440
|
+
let title = "";
|
|
27441
|
+
for (const match of raw.matchAll(OSC_TITLE_RE)) title = match[1] ?? "";
|
|
27442
|
+
return title;
|
|
27443
|
+
}
|
|
27444
|
+
function recentScreen(raw) {
|
|
27445
|
+
const tail = raw.slice(-MAX_RAW_SCREEN_CHARS);
|
|
27446
|
+
const lines = stripAnsi(tail).split("\r").join("\n").split("\n");
|
|
27447
|
+
return lines.slice(-MAX_RECENT_LINES).join("\n");
|
|
27448
|
+
}
|
|
27449
|
+
function nonEmptyLines(text2) {
|
|
27450
|
+
return text2.split("\n").filter((line) => line.trim() !== "");
|
|
27451
|
+
}
|
|
27452
|
+
function regionText(rule, screen, title) {
|
|
27453
|
+
if (rule.region === "osc_title") return title;
|
|
27454
|
+
if (rule.region === "whole_recent") return screen;
|
|
27455
|
+
const lines = nonEmptyLines(screen);
|
|
27456
|
+
if (rule.region === "last_non_empty") return lines[lines.length - 1] ?? "";
|
|
27457
|
+
return lines.slice(-(rule.lines ?? 1)).join("\n");
|
|
27458
|
+
}
|
|
27459
|
+
function compileNativeRegex(pattern) {
|
|
27460
|
+
const caseInsensitive = pattern.startsWith("(?i)");
|
|
27461
|
+
return new RegExp(
|
|
27462
|
+
caseInsensitive ? pattern.slice(4) : pattern,
|
|
27463
|
+
caseInsensitive ? "iu" : "u"
|
|
27464
|
+
);
|
|
27465
|
+
}
|
|
27466
|
+
function compileRegex(pattern) {
|
|
27467
|
+
const unsafeReason = unsafeRegexReason(pattern);
|
|
27468
|
+
if (unsafeReason) throw new Error(unsafeReason);
|
|
27469
|
+
return compileNativeRegex(pattern);
|
|
27470
|
+
}
|
|
27471
|
+
function regexMatches(pattern, text2) {
|
|
27472
|
+
return compileRegex(pattern).test(text2);
|
|
27473
|
+
}
|
|
27474
|
+
function matcherMatches(matcher, text2) {
|
|
27475
|
+
const lower = text2.toLowerCase();
|
|
27476
|
+
if (!(matcher.contains ?? []).every(
|
|
27477
|
+
(value) => lower.includes(value.toLowerCase())
|
|
27478
|
+
)) {
|
|
27479
|
+
return false;
|
|
27480
|
+
}
|
|
27481
|
+
if (!(matcher.regex ?? []).every((pattern) => regexMatches(pattern, text2))) {
|
|
27482
|
+
return false;
|
|
27483
|
+
}
|
|
27484
|
+
const lines = text2.split("\n");
|
|
27485
|
+
if (!(matcher.lineRegex ?? []).every(
|
|
27486
|
+
(pattern) => lines.some((line) => regexMatches(pattern, line))
|
|
27487
|
+
)) {
|
|
27488
|
+
return false;
|
|
27489
|
+
}
|
|
27490
|
+
if (!(matcher.all ?? []).every((nested) => matcherMatches(nested, text2))) {
|
|
27491
|
+
return false;
|
|
27492
|
+
}
|
|
27493
|
+
if ((matcher.any?.length ?? 0) > 0 && !matcher.any?.some((nested) => matcherMatches(nested, text2))) {
|
|
27494
|
+
return false;
|
|
27495
|
+
}
|
|
27496
|
+
return !(matcher.not ?? []).some((nested) => matcherMatches(nested, text2));
|
|
27497
|
+
}
|
|
27498
|
+
function detectAgentScreen(input, ruleSet = DEFAULT_AGENT_SCREEN_RULES) {
|
|
27499
|
+
const rawTail = input.screen.slice(-MAX_RAW_SCREEN_CHARS);
|
|
27500
|
+
const screen = recentScreen(input.screen);
|
|
27501
|
+
const title = stripAnsi(input.title || lastOscTitle(rawTail));
|
|
27502
|
+
let winner = null;
|
|
27503
|
+
for (const rule of ruleSet.rules) {
|
|
27504
|
+
const text2 = regionText(rule, screen, title);
|
|
27505
|
+
if (!matcherMatches(rule, text2)) continue;
|
|
27506
|
+
if (!winner || rule.priority > winner.priority) winner = rule;
|
|
27507
|
+
}
|
|
27508
|
+
if (!winner) return { kind: "none" };
|
|
27509
|
+
if (winner.state === "skip") {
|
|
27510
|
+
return { kind: "skip", ruleId: winner.id, priority: winner.priority };
|
|
27511
|
+
}
|
|
27512
|
+
return {
|
|
27513
|
+
kind: "state",
|
|
27514
|
+
state: winner.state,
|
|
27515
|
+
ruleId: winner.id,
|
|
27516
|
+
priority: winner.priority
|
|
27517
|
+
};
|
|
27518
|
+
}
|
|
27519
|
+
var AGENT_SCREEN_RULE_SET_VERSION, AGENT_SCREEN_REGIONS, MAX_RAW_SCREEN_CHARS, MAX_RECENT_LINES, MAX_RULES, MAX_MATCHERS_PER_LIST, MAX_MATCHER_DEPTH, MAX_PATTERN_LENGTH, MAX_CONTAINS_LENGTH, MAX_REGION_LINES, MAX_PRIORITY, RULE_ID_RE, ESC2, BEL2, OSC_TITLE_RE, BLOCKING_HINTS, DEFAULT_AGENT_SCREEN_RULES, MATCHER_KEYS, RULE_KEYS, MAX_REGEX_QUANTIFIERS, MAX_REGEX_BOUNDED_REPEAT, MAX_REGEX_VARIABLE_REPETITIONS;
|
|
27520
|
+
var init_agent_screen = __esm({
|
|
27521
|
+
"web-src/core/agent-screen.ts"() {
|
|
27522
|
+
init_terminal_images();
|
|
27523
|
+
AGENT_SCREEN_RULE_SET_VERSION = 1;
|
|
27524
|
+
AGENT_SCREEN_REGIONS = [
|
|
27525
|
+
"osc_title",
|
|
27526
|
+
"whole_recent",
|
|
27527
|
+
"bottom_non_empty",
|
|
27528
|
+
"last_non_empty"
|
|
27529
|
+
];
|
|
27530
|
+
MAX_RAW_SCREEN_CHARS = 64e3;
|
|
27531
|
+
MAX_RECENT_LINES = 120;
|
|
27532
|
+
MAX_RULES = 100;
|
|
27533
|
+
MAX_MATCHERS_PER_LIST = 20;
|
|
27534
|
+
MAX_MATCHER_DEPTH = 4;
|
|
27535
|
+
MAX_PATTERN_LENGTH = 1e3;
|
|
27536
|
+
MAX_CONTAINS_LENGTH = 200;
|
|
27537
|
+
MAX_REGION_LINES = 120;
|
|
27538
|
+
MAX_PRIORITY = 1e5;
|
|
27539
|
+
RULE_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
27540
|
+
ESC2 = String.fromCharCode(27);
|
|
27541
|
+
BEL2 = String.fromCharCode(7);
|
|
27542
|
+
OSC_TITLE_RE = new RegExp(
|
|
27543
|
+
`${ESC2}\\](?:0|2);([^${BEL2}${ESC2}]*)(?:${BEL2}|${ESC2}\\\\)`,
|
|
27544
|
+
"g"
|
|
27545
|
+
);
|
|
27546
|
+
BLOCKING_HINTS = [
|
|
27547
|
+
{ contains: ["enter to confirm"] },
|
|
27548
|
+
{ contains: ["enter to select"] },
|
|
27549
|
+
{ contains: ["enter to submit"] },
|
|
27550
|
+
{ contains: ["allow command?"] },
|
|
27551
|
+
{ contains: ["[y/n]"] },
|
|
27552
|
+
{ contains: ["yes (y)"] },
|
|
27553
|
+
{ contains: ["do you want to proceed?"] }
|
|
27554
|
+
];
|
|
27555
|
+
DEFAULT_AGENT_SCREEN_RULES = {
|
|
27556
|
+
version: AGENT_SCREEN_RULE_SET_VERSION,
|
|
27557
|
+
rules: [
|
|
27558
|
+
{
|
|
27559
|
+
id: "title_requires_input",
|
|
27560
|
+
state: "waiting",
|
|
27561
|
+
priority: 1100,
|
|
27562
|
+
region: "osc_title",
|
|
27563
|
+
contains: ["action required"]
|
|
27564
|
+
},
|
|
27565
|
+
{
|
|
27566
|
+
id: "title_spinner",
|
|
27567
|
+
state: "working",
|
|
27568
|
+
priority: 1050,
|
|
27569
|
+
region: "osc_title",
|
|
27570
|
+
regex: ["^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]"]
|
|
27571
|
+
},
|
|
27572
|
+
{
|
|
27573
|
+
id: "transcript_view",
|
|
27574
|
+
state: "skip",
|
|
27575
|
+
priority: 1e3,
|
|
27576
|
+
region: "bottom_non_empty",
|
|
27577
|
+
lines: 8,
|
|
27578
|
+
any: [
|
|
27579
|
+
{ contains: ["showing detailed transcript"] },
|
|
27580
|
+
{ contains: ["pgup/pgdn", "home/end to jump", "q to quit"] }
|
|
27581
|
+
]
|
|
27582
|
+
},
|
|
27583
|
+
{
|
|
27584
|
+
id: "interactive_form",
|
|
27585
|
+
state: "waiting",
|
|
27586
|
+
priority: 980,
|
|
27587
|
+
region: "bottom_non_empty",
|
|
27588
|
+
lines: 14,
|
|
27589
|
+
contains: ["esc to cancel"],
|
|
27590
|
+
any: [
|
|
27591
|
+
{ contains: ["enter to confirm"] },
|
|
27592
|
+
{ contains: ["enter to select"] },
|
|
27593
|
+
{ contains: ["enter to submit"] }
|
|
27594
|
+
]
|
|
27595
|
+
},
|
|
27596
|
+
{
|
|
27597
|
+
id: "live_reasoning",
|
|
27598
|
+
state: "working",
|
|
27599
|
+
priority: 970,
|
|
27600
|
+
region: "bottom_non_empty",
|
|
27601
|
+
lines: 8,
|
|
27602
|
+
contains: ["tokens", "thinking"],
|
|
27603
|
+
any: [{ lineRegex: ["^\\s*[✢✻✽✶✳]"] }, { lineRegex: ["(?i)^\\s*·"] }]
|
|
27604
|
+
},
|
|
27605
|
+
{
|
|
27606
|
+
id: "prompt_box",
|
|
27607
|
+
state: "idle",
|
|
27608
|
+
priority: 950,
|
|
27609
|
+
region: "bottom_non_empty",
|
|
27610
|
+
lines: 8,
|
|
27611
|
+
lineRegex: ["^\\s*❯"],
|
|
27612
|
+
not: [
|
|
27613
|
+
...BLOCKING_HINTS,
|
|
27614
|
+
{ contains: ["esc to cancel"] },
|
|
27615
|
+
{ contains: ["arrow keys"] },
|
|
27616
|
+
{ contains: ["↑/↓ to navigate"] }
|
|
27617
|
+
]
|
|
27618
|
+
},
|
|
27619
|
+
{
|
|
27620
|
+
id: "strong_input_request",
|
|
27621
|
+
state: "waiting",
|
|
27622
|
+
priority: 900,
|
|
27623
|
+
region: "bottom_non_empty",
|
|
27624
|
+
lines: 12,
|
|
27625
|
+
any: [
|
|
27626
|
+
{ contains: ["press enter to confirm or esc to cancel"] },
|
|
27627
|
+
{ contains: ["enter to submit answer"] },
|
|
27628
|
+
{ contains: ["enter to submit all"] },
|
|
27629
|
+
{ contains: ["allow command?"] }
|
|
27630
|
+
]
|
|
27631
|
+
},
|
|
27632
|
+
{
|
|
27633
|
+
id: "permission_request",
|
|
27634
|
+
state: "waiting",
|
|
27635
|
+
priority: 850,
|
|
27636
|
+
region: "bottom_non_empty",
|
|
27637
|
+
lines: 14,
|
|
27638
|
+
contains: ["do you want to proceed?"],
|
|
27639
|
+
any: [
|
|
27640
|
+
{ lineRegex: ["(?i)^\\P{L}*yes\\b"] },
|
|
27641
|
+
{ lineRegex: ["(?i)^\\P{L}*no\\b"] }
|
|
27642
|
+
]
|
|
27643
|
+
},
|
|
27644
|
+
{
|
|
27645
|
+
id: "weak_input_request",
|
|
27646
|
+
state: "waiting",
|
|
27647
|
+
priority: 600,
|
|
27648
|
+
region: "bottom_non_empty",
|
|
27649
|
+
lines: 8,
|
|
27650
|
+
any: [
|
|
27651
|
+
{ contains: ["[y/n]"] },
|
|
27652
|
+
{ contains: ["yes (y)"] },
|
|
27653
|
+
{
|
|
27654
|
+
any: [
|
|
27655
|
+
{ contains: ["do you want to"] },
|
|
27656
|
+
{ contains: ["would you like to"] }
|
|
27657
|
+
],
|
|
27658
|
+
all: [{ any: [{ contains: ["yes"] }, { contains: ["❯"] }] }]
|
|
27659
|
+
}
|
|
27660
|
+
]
|
|
27661
|
+
},
|
|
27662
|
+
{
|
|
27663
|
+
id: "live_working_status",
|
|
27664
|
+
state: "working",
|
|
27665
|
+
priority: 500,
|
|
27666
|
+
region: "bottom_non_empty",
|
|
27667
|
+
lines: 3,
|
|
27668
|
+
lineRegex: ["^[•◦]\\s+Working"],
|
|
27669
|
+
not: [{ contains: ["conversation interrupted"] }]
|
|
27670
|
+
},
|
|
27671
|
+
{
|
|
27672
|
+
id: "last_prompt",
|
|
27673
|
+
state: "idle",
|
|
27674
|
+
priority: 400,
|
|
27675
|
+
region: "last_non_empty",
|
|
27676
|
+
lineRegex: ["^\\s*[›❯]"],
|
|
27677
|
+
not: BLOCKING_HINTS
|
|
27678
|
+
}
|
|
27679
|
+
]
|
|
27680
|
+
};
|
|
27681
|
+
MATCHER_KEYS = /* @__PURE__ */ new Set([
|
|
27682
|
+
"contains",
|
|
27683
|
+
"regex",
|
|
27684
|
+
"lineRegex",
|
|
27685
|
+
"all",
|
|
27686
|
+
"any",
|
|
27687
|
+
"not"
|
|
27688
|
+
]);
|
|
27689
|
+
RULE_KEYS = /* @__PURE__ */ new Set([
|
|
27690
|
+
...MATCHER_KEYS,
|
|
27691
|
+
"id",
|
|
27692
|
+
"state",
|
|
27693
|
+
"priority",
|
|
27694
|
+
"region",
|
|
27695
|
+
"lines"
|
|
27696
|
+
]);
|
|
27697
|
+
MAX_REGEX_QUANTIFIERS = 8;
|
|
27698
|
+
MAX_REGEX_BOUNDED_REPEAT = 100;
|
|
27699
|
+
MAX_REGEX_VARIABLE_REPETITIONS = 1;
|
|
27059
27700
|
}
|
|
27060
27701
|
});
|
|
27061
27702
|
|
|
@@ -27144,80 +27785,583 @@ function sliceShellBuffer(replay, totalChars, cursor) {
|
|
|
27144
27785
|
reset: false
|
|
27145
27786
|
};
|
|
27146
27787
|
}
|
|
27147
|
-
var ANCHOR_LINES, TMUX_CURSOR_RE, SHELL_CURSOR_RE;
|
|
27148
|
-
var init_terminal_capture = __esm({
|
|
27149
|
-
"web-src/core/terminal-capture.ts"() {
|
|
27150
|
-
ANCHOR_LINES = 8;
|
|
27151
|
-
TMUX_CURSOR_RE = /^t(\d+)\.([0-9a-z]+)\.([0-9a-z]+)$/;
|
|
27152
|
-
SHELL_CURSOR_RE = /^s(\d+)$/;
|
|
27153
|
-
}
|
|
27154
|
-
});
|
|
27155
|
-
|
|
27156
|
-
// web-src/server/tmux/capture.ts
|
|
27157
|
-
function closeLineColors(content) {
|
|
27158
|
-
if (!content) return content;
|
|
27159
|
-
return content.split("\n").map((line) => line ? `${line}${SGR_RESET}` : line).join("\n");
|
|
27160
|
-
}
|
|
27161
|
-
async function captureTmuxPane(paneId, cwd2, historyLines = 0) {
|
|
27162
|
-
const history = Math.min(
|
|
27163
|
-
Math.max(Math.trunc(historyLines) || 0, 0),
|
|
27164
|
-
MAX_TMUX_HISTORY_LINES
|
|
27165
|
-
);
|
|
27166
|
-
const result = await runTmux(
|
|
27167
|
-
[
|
|
27168
|
-
"display-message",
|
|
27169
|
-
"-p",
|
|
27170
|
-
"-t",
|
|
27171
|
-
paneId,
|
|
27172
|
-
"-F",
|
|
27173
|
-
META_FORMAT,
|
|
27174
|
-
";",
|
|
27175
|
-
"capture-pane",
|
|
27176
|
-
"-e",
|
|
27177
|
-
"-p",
|
|
27178
|
-
"-t",
|
|
27179
|
-
paneId,
|
|
27180
|
-
...history > 0 ? ["-S", `-${history}`] : []
|
|
27181
|
-
],
|
|
27182
|
-
cwd2
|
|
27788
|
+
var ANCHOR_LINES, TMUX_CURSOR_RE, SHELL_CURSOR_RE;
|
|
27789
|
+
var init_terminal_capture = __esm({
|
|
27790
|
+
"web-src/core/terminal-capture.ts"() {
|
|
27791
|
+
ANCHOR_LINES = 8;
|
|
27792
|
+
TMUX_CURSOR_RE = /^t(\d+)\.([0-9a-z]+)\.([0-9a-z]+)$/;
|
|
27793
|
+
SHELL_CURSOR_RE = /^s(\d+)$/;
|
|
27794
|
+
}
|
|
27795
|
+
});
|
|
27796
|
+
|
|
27797
|
+
// web-src/server/tmux/capture.ts
|
|
27798
|
+
function closeLineColors(content) {
|
|
27799
|
+
if (!content) return content;
|
|
27800
|
+
return content.split("\n").map((line) => line ? `${line}${SGR_RESET}` : line).join("\n");
|
|
27801
|
+
}
|
|
27802
|
+
async function captureTmuxPane(paneId, cwd2, historyLines = 0) {
|
|
27803
|
+
const history = Math.min(
|
|
27804
|
+
Math.max(Math.trunc(historyLines) || 0, 0),
|
|
27805
|
+
MAX_TMUX_HISTORY_LINES
|
|
27806
|
+
);
|
|
27807
|
+
const result = await runTmux(
|
|
27808
|
+
[
|
|
27809
|
+
"display-message",
|
|
27810
|
+
"-p",
|
|
27811
|
+
"-t",
|
|
27812
|
+
paneId,
|
|
27813
|
+
"-F",
|
|
27814
|
+
META_FORMAT,
|
|
27815
|
+
";",
|
|
27816
|
+
"capture-pane",
|
|
27817
|
+
"-e",
|
|
27818
|
+
"-p",
|
|
27819
|
+
"-t",
|
|
27820
|
+
paneId,
|
|
27821
|
+
...history > 0 ? ["-S", `-${history}`] : []
|
|
27822
|
+
],
|
|
27823
|
+
cwd2
|
|
27824
|
+
);
|
|
27825
|
+
if (result.status === "missing" || result.status === "no-server") {
|
|
27826
|
+
return { status: "gone" };
|
|
27827
|
+
}
|
|
27828
|
+
if (result.status === "no-target") return { status: "gone" };
|
|
27829
|
+
if (result.status === "error") {
|
|
27830
|
+
return result;
|
|
27831
|
+
}
|
|
27832
|
+
const newline = result.stdout.indexOf("\n");
|
|
27833
|
+
if (newline < 0) {
|
|
27834
|
+
return {
|
|
27835
|
+
status: "error",
|
|
27836
|
+
error: new Error("tmux capture returned no screen")
|
|
27837
|
+
};
|
|
27838
|
+
}
|
|
27839
|
+
const meta = result.stdout.slice(0, newline).split(" ");
|
|
27840
|
+
const toInt2 = (value) => Number.parseInt(value ?? "", 10) || 0;
|
|
27841
|
+
return {
|
|
27842
|
+
status: "ok",
|
|
27843
|
+
screen: {
|
|
27844
|
+
pane: paneId,
|
|
27845
|
+
content: closeLineColors(result.stdout.slice(newline + 1)),
|
|
27846
|
+
width: toInt2(meta[0]),
|
|
27847
|
+
height: toInt2(meta[1]),
|
|
27848
|
+
cursorX: toInt2(meta[2]),
|
|
27849
|
+
cursorY: toInt2(meta[3]),
|
|
27850
|
+
// 頼んだ行数と、実際に在る履歴の短いほう。
|
|
27851
|
+
historyLines: Math.min(history, toInt2(meta[4]))
|
|
27852
|
+
}
|
|
27853
|
+
};
|
|
27854
|
+
}
|
|
27855
|
+
var META_FORMAT, MAX_TMUX_HISTORY_LINES, SGR_RESET;
|
|
27856
|
+
var init_capture = __esm({
|
|
27857
|
+
"web-src/server/tmux/capture.ts"() {
|
|
27858
|
+
init_command();
|
|
27859
|
+
META_FORMAT = "#{pane_width} #{pane_height} #{cursor_x} #{cursor_y} #{history_size}";
|
|
27860
|
+
MAX_TMUX_HISTORY_LINES = 5e3;
|
|
27861
|
+
SGR_RESET = `${String.fromCharCode(27)}[0m`;
|
|
27862
|
+
}
|
|
27863
|
+
});
|
|
27864
|
+
|
|
27865
|
+
// web-src/server/tmux/panes.ts
|
|
27866
|
+
function toInt(value) {
|
|
27867
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
27868
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
27869
|
+
}
|
|
27870
|
+
function toFlag(value) {
|
|
27871
|
+
return value === "1";
|
|
27872
|
+
}
|
|
27873
|
+
function parseTmuxPanes(stdout, worktrees = []) {
|
|
27874
|
+
const sessions2 = [];
|
|
27875
|
+
const sessionByName = /* @__PURE__ */ new Map();
|
|
27876
|
+
const windowByKey = /* @__PURE__ */ new Map();
|
|
27877
|
+
for (const line of stdout.split("\n")) {
|
|
27878
|
+
if (!line) continue;
|
|
27879
|
+
const fields = line.split(TMUX_FIELD_SEP);
|
|
27880
|
+
if (fields.length < PANE_FIELDS.length) continue;
|
|
27881
|
+
const paneId = fields[FIELD.paneId];
|
|
27882
|
+
if (!paneId) continue;
|
|
27883
|
+
const sessionName = fields[FIELD.sessionName] ?? "";
|
|
27884
|
+
let session = sessionByName.get(sessionName);
|
|
27885
|
+
if (!session) {
|
|
27886
|
+
session = {
|
|
27887
|
+
name: sessionName,
|
|
27888
|
+
attached: toFlag(fields[FIELD.sessionAttached]),
|
|
27889
|
+
windows: []
|
|
27890
|
+
};
|
|
27891
|
+
sessionByName.set(sessionName, session);
|
|
27892
|
+
sessions2.push(session);
|
|
27893
|
+
}
|
|
27894
|
+
const windowIndex = toInt(fields[FIELD.windowIndex]);
|
|
27895
|
+
const windowKey = `${sessionName}${TMUX_FIELD_SEP}${windowIndex}`;
|
|
27896
|
+
let window = windowByKey.get(windowKey);
|
|
27897
|
+
if (!window) {
|
|
27898
|
+
window = {
|
|
27899
|
+
index: windowIndex,
|
|
27900
|
+
name: fields[FIELD.windowName] ?? "",
|
|
27901
|
+
active: toFlag(fields[FIELD.windowActive]),
|
|
27902
|
+
panes: []
|
|
27903
|
+
};
|
|
27904
|
+
windowByKey.set(windowKey, window);
|
|
27905
|
+
session.windows.push(window);
|
|
27906
|
+
}
|
|
27907
|
+
const paneIndex = toInt(fields[FIELD.paneIndex]);
|
|
27908
|
+
const pane = {
|
|
27909
|
+
id: paneId,
|
|
27910
|
+
label: `${sessionName}:${windowIndex}.${paneIndex}`,
|
|
27911
|
+
paneIndex,
|
|
27912
|
+
title: fields[FIELD.paneTitle] ?? "",
|
|
27913
|
+
command: fields[FIELD.paneCommand] ?? "",
|
|
27914
|
+
path: fields[FIELD.panePath] ?? "",
|
|
27915
|
+
width: toInt(fields[FIELD.paneWidth]),
|
|
27916
|
+
height: toInt(fields[FIELD.paneHeight]),
|
|
27917
|
+
active: toFlag(fields[FIELD.paneActive]),
|
|
27918
|
+
inRepo: worktrees.length === 0 || isPathInsideAny(fields[FIELD.panePath] ?? "", worktrees)
|
|
27919
|
+
};
|
|
27920
|
+
window.panes.push(pane);
|
|
27921
|
+
}
|
|
27922
|
+
return sessions2;
|
|
27923
|
+
}
|
|
27924
|
+
async function listTmuxPanes(cwd2) {
|
|
27925
|
+
const [result, worktrees] = await Promise.all([
|
|
27926
|
+
runTmux(["list-panes", "-a", "-F", PANE_FORMAT], cwd2),
|
|
27927
|
+
worktreePathsAsync(cwd2)
|
|
27928
|
+
]);
|
|
27929
|
+
if (result.status === "missing") {
|
|
27930
|
+
return { available: false, running: false, sessions: [] };
|
|
27931
|
+
}
|
|
27932
|
+
if (result.status === "no-server" || result.status === "no-target") {
|
|
27933
|
+
return { available: true, running: false, sessions: [] };
|
|
27934
|
+
}
|
|
27935
|
+
if (result.status === "error") {
|
|
27936
|
+
throw errorWithCause("failed to list tmux panes", result.error);
|
|
27937
|
+
}
|
|
27938
|
+
return {
|
|
27939
|
+
available: true,
|
|
27940
|
+
running: true,
|
|
27941
|
+
sessions: parseTmuxPanes(result.stdout, worktrees)
|
|
27942
|
+
};
|
|
27943
|
+
}
|
|
27944
|
+
var PANE_FIELDS, PANE_FORMAT, FIELD;
|
|
27945
|
+
var init_panes = __esm({
|
|
27946
|
+
"web-src/server/tmux/panes.ts"() {
|
|
27947
|
+
init_error_detail();
|
|
27948
|
+
init_tmux();
|
|
27949
|
+
init_git();
|
|
27950
|
+
init_command();
|
|
27951
|
+
PANE_FIELDS = [
|
|
27952
|
+
"#{pane_id}",
|
|
27953
|
+
"#{session_name}",
|
|
27954
|
+
"#{session_attached}",
|
|
27955
|
+
"#{window_index}",
|
|
27956
|
+
"#{window_name}",
|
|
27957
|
+
"#{window_active}",
|
|
27958
|
+
"#{pane_index}",
|
|
27959
|
+
"#{pane_active}",
|
|
27960
|
+
"#{pane_width}",
|
|
27961
|
+
"#{pane_height}",
|
|
27962
|
+
"#{pane_current_command}",
|
|
27963
|
+
"#{pane_current_path}",
|
|
27964
|
+
// タイトルは自由文字列なので必ず最後に置く。
|
|
27965
|
+
"#{pane_title}"
|
|
27966
|
+
];
|
|
27967
|
+
PANE_FORMAT = PANE_FIELDS.join(TMUX_FIELD_SEP);
|
|
27968
|
+
FIELD = {
|
|
27969
|
+
paneId: 0,
|
|
27970
|
+
sessionName: 1,
|
|
27971
|
+
sessionAttached: 2,
|
|
27972
|
+
windowIndex: 3,
|
|
27973
|
+
windowName: 4,
|
|
27974
|
+
windowActive: 5,
|
|
27975
|
+
paneIndex: 6,
|
|
27976
|
+
paneActive: 7,
|
|
27977
|
+
paneWidth: 8,
|
|
27978
|
+
paneHeight: 9,
|
|
27979
|
+
paneCommand: 10,
|
|
27980
|
+
panePath: 11,
|
|
27981
|
+
paneTitle: 12
|
|
27982
|
+
};
|
|
27983
|
+
}
|
|
27984
|
+
});
|
|
27985
|
+
|
|
27986
|
+
// web-src/server/terminal/agent-state.ts
|
|
27987
|
+
function clip(value) {
|
|
27988
|
+
return value.length > MAX_TEXT_LENGTH ? value.slice(0, MAX_TEXT_LENGTH) : value;
|
|
27989
|
+
}
|
|
27990
|
+
function evictOldest2() {
|
|
27991
|
+
while (states.size > MAX_TRACKED_TARGETS) {
|
|
27992
|
+
let oldestKey = null;
|
|
27993
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
27994
|
+
for (const [key, record] of states) {
|
|
27995
|
+
if (record.updatedAt < oldestAt) {
|
|
27996
|
+
oldestAt = record.updatedAt;
|
|
27997
|
+
oldestKey = key;
|
|
27998
|
+
}
|
|
27999
|
+
}
|
|
28000
|
+
if (oldestKey === null) return;
|
|
28001
|
+
states.delete(oldestKey);
|
|
28002
|
+
}
|
|
28003
|
+
}
|
|
28004
|
+
function recordAgentState(input) {
|
|
28005
|
+
const previous = states.get(input.target);
|
|
28006
|
+
const next = input.state ?? (input.event ? agentStateForEvent(input.event, previous?.state ?? null) : null);
|
|
28007
|
+
if (!next) return null;
|
|
28008
|
+
if (input.source !== "hook" && previous?.source === "hook") {
|
|
28009
|
+
if (previous.state === "done") return previous;
|
|
28010
|
+
const visibleRule = input.source === "screen" && input.override === true;
|
|
28011
|
+
const motionPromoting = input.source === "activity" && input.override === true && next === "working";
|
|
28012
|
+
if (!visibleRule && !motionPromoting) return previous;
|
|
28013
|
+
}
|
|
28014
|
+
const at = Number.isFinite(input.at) ? input.at : Date.now();
|
|
28015
|
+
if (previous && previous.source === "hook" && input.source === "hook" && at < previous.updatedAt) {
|
|
28016
|
+
return previous;
|
|
28017
|
+
}
|
|
28018
|
+
const record = {
|
|
28019
|
+
target: input.target,
|
|
28020
|
+
state: next,
|
|
28021
|
+
source: input.source,
|
|
28022
|
+
updatedAt: input.source !== "hook" && previous?.state === next ? previous.updatedAt : at,
|
|
28023
|
+
// 添え物は送られてこなければ前の値を残す。ターンの途中で毎回指示文を
|
|
28024
|
+
// 送り直させないため。
|
|
28025
|
+
lastPrompt: clip(input.lastPrompt ?? previous?.lastPrompt ?? ""),
|
|
28026
|
+
note: clip(input.note ?? previous?.note ?? "")
|
|
28027
|
+
};
|
|
28028
|
+
states.set(input.target, record);
|
|
28029
|
+
evictOldest2();
|
|
28030
|
+
return record;
|
|
28031
|
+
}
|
|
28032
|
+
function getAgentState(target) {
|
|
28033
|
+
return states.get(target) ?? null;
|
|
28034
|
+
}
|
|
28035
|
+
function listAgentStates() {
|
|
28036
|
+
return [...states.values()].sort((a, b) => {
|
|
28037
|
+
const mine = Number(needsAttention(b.state)) - Number(needsAttention(a.state));
|
|
28038
|
+
return mine !== 0 ? mine : a.updatedAt - b.updatedAt;
|
|
28039
|
+
});
|
|
28040
|
+
}
|
|
28041
|
+
function retainAgentStates(known) {
|
|
28042
|
+
let removed = 0;
|
|
28043
|
+
for (const target of [...states.keys()]) {
|
|
28044
|
+
if (!known.has(target)) {
|
|
28045
|
+
states.delete(target);
|
|
28046
|
+
removed += 1;
|
|
28047
|
+
}
|
|
28048
|
+
}
|
|
28049
|
+
return removed;
|
|
28050
|
+
}
|
|
28051
|
+
var MAX_TRACKED_TARGETS, MAX_TEXT_LENGTH, states;
|
|
28052
|
+
var init_agent_state2 = __esm({
|
|
28053
|
+
"web-src/server/terminal/agent-state.ts"() {
|
|
28054
|
+
init_agent_state();
|
|
28055
|
+
MAX_TRACKED_TARGETS = 200;
|
|
28056
|
+
MAX_TEXT_LENGTH = 2e3;
|
|
28057
|
+
states = /* @__PURE__ */ new Map();
|
|
28058
|
+
}
|
|
28059
|
+
});
|
|
28060
|
+
|
|
28061
|
+
// web-src/server/terminal/rules.ts
|
|
28062
|
+
import { join as join20 } from "node:path";
|
|
28063
|
+
function errorIssue(code, error) {
|
|
28064
|
+
return {
|
|
28065
|
+
path: "$",
|
|
28066
|
+
code,
|
|
28067
|
+
message: formatErrorDetail(error),
|
|
28068
|
+
...error instanceof Error && error.stack ? { stack: error.stack } : {}
|
|
28069
|
+
};
|
|
28070
|
+
}
|
|
28071
|
+
function defaultResponse(errors = []) {
|
|
28072
|
+
return { rules: DEFAULT_AGENT_SCREEN_RULES, source: "default", errors };
|
|
28073
|
+
}
|
|
28074
|
+
function agentScreenRulesFilePath(root) {
|
|
28075
|
+
return join20(root, ".code-viewer", RULES_FILE_NAME);
|
|
28076
|
+
}
|
|
28077
|
+
function parseStoredRules(raw) {
|
|
28078
|
+
const parsed = parseAgentScreenRuleSet(raw);
|
|
28079
|
+
if ("errors" in parsed) {
|
|
28080
|
+
throw Object.assign(new Error("saved terminal rules are invalid"), {
|
|
28081
|
+
issues: parsed.errors
|
|
28082
|
+
});
|
|
28083
|
+
}
|
|
28084
|
+
return parsed.value;
|
|
28085
|
+
}
|
|
28086
|
+
function getActiveAgentScreenRules() {
|
|
28087
|
+
return activeRules;
|
|
28088
|
+
}
|
|
28089
|
+
function issuesFromLoadError(error) {
|
|
28090
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
28091
|
+
let current = error;
|
|
28092
|
+
while (current && typeof current === "object" && !seen2.has(current)) {
|
|
28093
|
+
seen2.add(current);
|
|
28094
|
+
const issues = current.issues;
|
|
28095
|
+
if (Array.isArray(issues)) {
|
|
28096
|
+
return issues.filter(
|
|
28097
|
+
(issue2) => !!issue2 && typeof issue2 === "object" && typeof issue2.path === "string" && typeof issue2.code === "string" && typeof issue2.message === "string"
|
|
28098
|
+
);
|
|
28099
|
+
}
|
|
28100
|
+
if (current instanceof SyntaxError)
|
|
28101
|
+
return [errorIssue("invalid_json", current)];
|
|
28102
|
+
current = current.cause;
|
|
28103
|
+
}
|
|
28104
|
+
return [errorIssue("load_failed", error)];
|
|
28105
|
+
}
|
|
28106
|
+
function activate(response) {
|
|
28107
|
+
activeRules = response.rules;
|
|
28108
|
+
activeErrors = response.errors.map((error) => ({ ...error }));
|
|
28109
|
+
activeGeneration += 1;
|
|
28110
|
+
return { ...response, generation: activeGeneration };
|
|
28111
|
+
}
|
|
28112
|
+
async function reloadAgentScreenRules(root) {
|
|
28113
|
+
try {
|
|
28114
|
+
const rules = await rulesStore.load(root);
|
|
28115
|
+
return activate(
|
|
28116
|
+
rules === null ? defaultResponse() : { rules, source: "saved", errors: [] }
|
|
28117
|
+
);
|
|
28118
|
+
} catch (error) {
|
|
28119
|
+
console.error("[code-viewer] terminal rule load failed", error);
|
|
28120
|
+
return activate(defaultResponse(issuesFromLoadError(error)));
|
|
28121
|
+
}
|
|
28122
|
+
}
|
|
28123
|
+
async function saveAgentScreenRules(root, raw) {
|
|
28124
|
+
const parsed = parseAgentScreenRuleSet(raw);
|
|
28125
|
+
if ("errors" in parsed) return { errors: parsed.errors };
|
|
28126
|
+
await rulesStore.save(root, parsed.value);
|
|
28127
|
+
return activate({ rules: parsed.value, source: "saved", errors: [] });
|
|
28128
|
+
}
|
|
28129
|
+
async function resetAgentScreenRules(root) {
|
|
28130
|
+
await rulesStore.remove(root);
|
|
28131
|
+
return activate(defaultResponse());
|
|
28132
|
+
}
|
|
28133
|
+
var MAX_AGENT_SCREEN_RULES_BYTES, RULES_FILE_NAME, activeRules, activeErrors, activeGeneration, rulesStore;
|
|
28134
|
+
var init_rules = __esm({
|
|
28135
|
+
"web-src/server/terminal/rules.ts"() {
|
|
28136
|
+
init_agent_screen();
|
|
28137
|
+
init_error_detail();
|
|
28138
|
+
init_json_store();
|
|
28139
|
+
MAX_AGENT_SCREEN_RULES_BYTES = 2e5;
|
|
28140
|
+
RULES_FILE_NAME = "agent-screen-rules.json";
|
|
28141
|
+
activeRules = DEFAULT_AGENT_SCREEN_RULES;
|
|
28142
|
+
activeErrors = [];
|
|
28143
|
+
activeGeneration = 0;
|
|
28144
|
+
rulesStore = createJsonFileStore({
|
|
28145
|
+
filePath: agentScreenRulesFilePath,
|
|
28146
|
+
empty: () => null,
|
|
28147
|
+
sanitize: (raw) => parseStoredRules(raw),
|
|
28148
|
+
maxBytes: MAX_AGENT_SCREEN_RULES_BYTES,
|
|
28149
|
+
backupSuffix: "corrupt",
|
|
28150
|
+
sizeErrorMessage: `terminal rules must not exceed ${MAX_AGENT_SCREEN_RULES_BYTES} bytes`,
|
|
28151
|
+
serialize: (rules) => {
|
|
28152
|
+
if (rules === null) throw new Error("terminal rules must not be null");
|
|
28153
|
+
return formatAgentScreenRuleSet(rules);
|
|
28154
|
+
},
|
|
28155
|
+
invalidFileBehavior: "throw"
|
|
28156
|
+
});
|
|
28157
|
+
}
|
|
28158
|
+
});
|
|
28159
|
+
|
|
28160
|
+
// web-src/server/terminal/activity.ts
|
|
28161
|
+
var activity_exports = {};
|
|
28162
|
+
__export(activity_exports, {
|
|
28163
|
+
ACTIVITY_IDLE_AFTER_MS: () => ACTIVITY_IDLE_AFTER_MS,
|
|
28164
|
+
ACTIVITY_POLL_INTERVAL_MS: () => ACTIVITY_POLL_INTERVAL_MS,
|
|
28165
|
+
MAX_PANES_PER_SWEEP: () => MAX_PANES_PER_SWEEP,
|
|
28166
|
+
OVERRIDE_CHANGE_STREAK: () => OVERRIDE_CHANGE_STREAK,
|
|
28167
|
+
getAgentActivityErrors: () => getAgentActivityErrors,
|
|
28168
|
+
nextActivityState: () => nextActivityState,
|
|
28169
|
+
nextObservedState: () => nextObservedState,
|
|
28170
|
+
rotateForSweep: () => rotateForSweep,
|
|
28171
|
+
startAgentActivityWatch: () => startAgentActivityWatch,
|
|
28172
|
+
stopAgentActivityWatch: () => stopAgentActivityWatch
|
|
28173
|
+
});
|
|
28174
|
+
function nextActivityState(previous, hash, now) {
|
|
28175
|
+
const changed = previous === void 0 || previous.hash !== hash;
|
|
28176
|
+
const changedAt = changed ? now : previous.changedAt;
|
|
28177
|
+
const changeStreak = changed ? previous === void 0 ? 0 : previous.changeStreak + 1 : 0;
|
|
28178
|
+
return {
|
|
28179
|
+
state: agentStateFromActivity(
|
|
28180
|
+
changed,
|
|
28181
|
+
now - changedAt,
|
|
28182
|
+
ACTIVITY_IDLE_AFTER_MS
|
|
28183
|
+
),
|
|
28184
|
+
seen: { hash, changedAt, changeStreak },
|
|
28185
|
+
override: changeStreak >= OVERRIDE_CHANGE_STREAK
|
|
28186
|
+
};
|
|
28187
|
+
}
|
|
28188
|
+
function nextObservedState(previous, content, title, now, rules = getActiveAgentScreenRules(), previousState = null) {
|
|
28189
|
+
const activity = nextActivityState(
|
|
28190
|
+
previous,
|
|
28191
|
+
hashLine(`${title ?? ""}\0${content}`),
|
|
28192
|
+
now
|
|
27183
28193
|
);
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
if (result.status === "no-target") return { status: "gone" };
|
|
27188
|
-
if (result.status === "error") {
|
|
27189
|
-
return result;
|
|
28194
|
+
const detected = detectAgentScreen({ screen: content, title }, rules);
|
|
28195
|
+
if (detected.kind === "skip") {
|
|
28196
|
+
return { kind: "skip", seen: activity.seen, ruleId: detected.ruleId };
|
|
27190
28197
|
}
|
|
27191
|
-
|
|
27192
|
-
|
|
28198
|
+
if (detected.kind === "state") {
|
|
28199
|
+
const contentChanged = previous !== void 0 && previous.hash !== activity.seen.hash;
|
|
28200
|
+
if (detected.state === "idle" && previousState === "working" && contentChanged) {
|
|
28201
|
+
return {
|
|
28202
|
+
kind: "hold",
|
|
28203
|
+
seen: activity.seen,
|
|
28204
|
+
ruleId: detected.ruleId
|
|
28205
|
+
};
|
|
28206
|
+
}
|
|
28207
|
+
if (detected.state === "working" && activity.state === "idle") {
|
|
28208
|
+
return { kind: "record", ...activity, ruleId: null };
|
|
28209
|
+
}
|
|
27193
28210
|
return {
|
|
27194
|
-
|
|
27195
|
-
|
|
28211
|
+
kind: "record",
|
|
28212
|
+
state: detected.state,
|
|
28213
|
+
seen: activity.seen,
|
|
28214
|
+
override: detected.state === "working" ? activity.override : true,
|
|
28215
|
+
ruleId: detected.ruleId
|
|
27196
28216
|
};
|
|
27197
28217
|
}
|
|
27198
|
-
|
|
27199
|
-
|
|
28218
|
+
if (previousState === null) {
|
|
28219
|
+
return { kind: "unidentified", seen: activity.seen };
|
|
28220
|
+
}
|
|
28221
|
+
return { kind: "record", ...activity, ruleId: null };
|
|
28222
|
+
}
|
|
28223
|
+
function observe(target, content, note, title) {
|
|
28224
|
+
const next = nextObservedState(
|
|
28225
|
+
seen.get(target),
|
|
28226
|
+
content,
|
|
28227
|
+
title,
|
|
28228
|
+
Date.now(),
|
|
28229
|
+
getActiveAgentScreenRules(),
|
|
28230
|
+
getAgentState(target)?.state ?? null
|
|
28231
|
+
);
|
|
28232
|
+
seen.set(target, next.seen);
|
|
28233
|
+
if (next.kind === "skip" || next.kind === "hold" || next.kind === "unidentified") {
|
|
28234
|
+
return;
|
|
28235
|
+
}
|
|
28236
|
+
recordAgentState({
|
|
28237
|
+
target,
|
|
28238
|
+
state: next.state,
|
|
28239
|
+
source: next.ruleId ? "screen" : "activity",
|
|
28240
|
+
note,
|
|
28241
|
+
override: next.override
|
|
28242
|
+
});
|
|
28243
|
+
}
|
|
28244
|
+
function observationError(operation, target, error) {
|
|
27200
28245
|
return {
|
|
27201
|
-
|
|
27202
|
-
|
|
27203
|
-
|
|
27204
|
-
|
|
27205
|
-
|
|
27206
|
-
height: toInt2(meta[1]),
|
|
27207
|
-
cursorX: toInt2(meta[2]),
|
|
27208
|
-
cursorY: toInt2(meta[3]),
|
|
27209
|
-
// 頼んだ行数と、実際に在る履歴の短いほう。
|
|
27210
|
-
historyLines: Math.min(history, toInt2(meta[4]))
|
|
27211
|
-
}
|
|
28246
|
+
operation,
|
|
28247
|
+
target,
|
|
28248
|
+
at: Date.now(),
|
|
28249
|
+
detail: formatErrorDetail(error),
|
|
28250
|
+
stack: error instanceof Error ? error.stack ?? "" : ""
|
|
27212
28251
|
};
|
|
27213
28252
|
}
|
|
27214
|
-
|
|
27215
|
-
|
|
27216
|
-
|
|
27217
|
-
|
|
27218
|
-
|
|
27219
|
-
|
|
27220
|
-
|
|
28253
|
+
function getAgentActivityErrors() {
|
|
28254
|
+
return [...activityErrors.values()].sort((a, b) => a.at - b.at).map((error) => ({ ...error }));
|
|
28255
|
+
}
|
|
28256
|
+
function activityErrorKey(operation, target) {
|
|
28257
|
+
return `${operation}\0${target}`;
|
|
28258
|
+
}
|
|
28259
|
+
function rotateForSweep(items, offset, limit) {
|
|
28260
|
+
if (items.length === 0) return { batch: [], nextOffset: 0 };
|
|
28261
|
+
const take = Math.min(limit, items.length);
|
|
28262
|
+
const start = (offset % items.length + items.length) % items.length;
|
|
28263
|
+
const batch = [];
|
|
28264
|
+
for (let i = 0; i < take; i += 1) {
|
|
28265
|
+
batch.push(items[(start + i) % items.length]);
|
|
28266
|
+
}
|
|
28267
|
+
return { batch, nextOffset: (start + take) % items.length };
|
|
28268
|
+
}
|
|
28269
|
+
async function sweep(cwd2) {
|
|
28270
|
+
if (inFlight) return;
|
|
28271
|
+
inFlight = true;
|
|
28272
|
+
try {
|
|
28273
|
+
const panes = await listTmuxPanes(cwd2);
|
|
28274
|
+
activityErrors.delete(activityErrorKey("list_terminals", ""));
|
|
28275
|
+
const shells = listShellSessions();
|
|
28276
|
+
const allPanes = panes.running ? flattenTmuxPanes(panes.sessions) : [];
|
|
28277
|
+
if (panes.running) {
|
|
28278
|
+
const known = /* @__PURE__ */ new Set([
|
|
28279
|
+
...allPanes.map((pane) => pane.id),
|
|
28280
|
+
...shells.map((session) => session.id)
|
|
28281
|
+
]);
|
|
28282
|
+
retainAgentStates(known);
|
|
28283
|
+
for (const target of [...seen.keys()]) {
|
|
28284
|
+
if (!known.has(target)) seen.delete(target);
|
|
28285
|
+
}
|
|
28286
|
+
}
|
|
28287
|
+
for (const session of shells) {
|
|
28288
|
+
const buffer = readShellBuffer(session.id);
|
|
28289
|
+
if (!buffer) continue;
|
|
28290
|
+
observe(session.id, buffer.replay, session.command);
|
|
28291
|
+
}
|
|
28292
|
+
const targets = allPanes;
|
|
28293
|
+
const { batch, nextOffset } = rotateForSweep(
|
|
28294
|
+
targets,
|
|
28295
|
+
sweepOffset,
|
|
28296
|
+
MAX_PANES_PER_SWEEP
|
|
28297
|
+
);
|
|
28298
|
+
sweepOffset = nextOffset;
|
|
28299
|
+
for (const pane of batch) {
|
|
28300
|
+
const result = await captureTmuxPane(pane.id, cwd2);
|
|
28301
|
+
if (result.status === "gone") {
|
|
28302
|
+
activityErrors.delete(activityErrorKey("capture_screen", pane.id));
|
|
28303
|
+
seen.delete(pane.id);
|
|
28304
|
+
continue;
|
|
28305
|
+
}
|
|
28306
|
+
if (result.status === "error") {
|
|
28307
|
+
console.error(
|
|
28308
|
+
`[code-viewer] terminal screen capture failed for ${pane.id}`,
|
|
28309
|
+
result.error
|
|
28310
|
+
);
|
|
28311
|
+
activityErrors.set(
|
|
28312
|
+
activityErrorKey("capture_screen", pane.id),
|
|
28313
|
+
observationError("capture_screen", pane.id, result.error)
|
|
28314
|
+
);
|
|
28315
|
+
continue;
|
|
28316
|
+
}
|
|
28317
|
+
activityErrors.delete(activityErrorKey("capture_screen", pane.id));
|
|
28318
|
+
observe(pane.id, result.screen.content, pane.title, pane.title);
|
|
28319
|
+
}
|
|
28320
|
+
} catch (error) {
|
|
28321
|
+
console.error("[code-viewer] terminal state observation failed", error);
|
|
28322
|
+
activityErrors.set(
|
|
28323
|
+
activityErrorKey("list_terminals", ""),
|
|
28324
|
+
observationError("list_terminals", "", error)
|
|
28325
|
+
);
|
|
28326
|
+
} finally {
|
|
28327
|
+
inFlight = false;
|
|
28328
|
+
}
|
|
28329
|
+
}
|
|
28330
|
+
function startAgentActivityWatch(cwd2) {
|
|
28331
|
+
if (timer) return;
|
|
28332
|
+
void reloadAgentScreenRules(cwd2);
|
|
28333
|
+
timer = setInterval(() => void sweep(cwd2), ACTIVITY_POLL_INTERVAL_MS);
|
|
28334
|
+
timer.unref?.();
|
|
28335
|
+
}
|
|
28336
|
+
function stopAgentActivityWatch() {
|
|
28337
|
+
if (timer) clearInterval(timer);
|
|
28338
|
+
timer = null;
|
|
28339
|
+
seen.clear();
|
|
28340
|
+
activityErrors.clear();
|
|
28341
|
+
sweepOffset = 0;
|
|
28342
|
+
}
|
|
28343
|
+
var ACTIVITY_POLL_INTERVAL_MS, ACTIVITY_IDLE_AFTER_MS, OVERRIDE_CHANGE_STREAK, MAX_PANES_PER_SWEEP, seen, timer, inFlight, activityErrors, sweepOffset;
|
|
28344
|
+
var init_activity = __esm({
|
|
28345
|
+
"web-src/server/terminal/activity.ts"() {
|
|
28346
|
+
init_agent_screen();
|
|
28347
|
+
init_agent_state();
|
|
28348
|
+
init_error_detail();
|
|
28349
|
+
init_terminal_capture();
|
|
28350
|
+
init_tmux();
|
|
28351
|
+
init_session();
|
|
28352
|
+
init_capture();
|
|
28353
|
+
init_panes();
|
|
28354
|
+
init_agent_state2();
|
|
28355
|
+
init_rules();
|
|
28356
|
+
ACTIVITY_POLL_INTERVAL_MS = 3e3;
|
|
28357
|
+
ACTIVITY_IDLE_AFTER_MS = 15e3;
|
|
28358
|
+
OVERRIDE_CHANGE_STREAK = 4;
|
|
28359
|
+
MAX_PANES_PER_SWEEP = 12;
|
|
28360
|
+
seen = /* @__PURE__ */ new Map();
|
|
28361
|
+
timer = null;
|
|
28362
|
+
inFlight = false;
|
|
28363
|
+
activityErrors = /* @__PURE__ */ new Map();
|
|
28364
|
+
sweepOffset = 0;
|
|
27221
28365
|
}
|
|
27222
28366
|
});
|
|
27223
28367
|
|
|
@@ -27277,7 +28421,7 @@ var init_capture2 = __esm({
|
|
|
27277
28421
|
|
|
27278
28422
|
// web-src/server/mcp.ts
|
|
27279
28423
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
27280
|
-
import { join as
|
|
28424
|
+
import { join as join21 } from "node:path";
|
|
27281
28425
|
function defaultMcpTools(options = {}) {
|
|
27282
28426
|
return [
|
|
27283
28427
|
{
|
|
@@ -27775,7 +28919,7 @@ function defaultMcpTools(options = {}) {
|
|
|
27775
28919
|
{
|
|
27776
28920
|
name: "code_viewer_terminal_list",
|
|
27777
28921
|
title: "code-viewer terminal list",
|
|
27778
|
-
description: "Returns the state of every terminal this server knows about, the same payload `code-viewer terminal list --json` emits: { states: [{ target, state, source, updatedAt, lastPrompt, note }] }. state is working | waiting | done | idle, where done means the turn finished and nobody has read the output yet. source is hook
|
|
28922
|
+
description: "Returns the state of every terminal this server knows about, plus every observation error, using the same payload `code-viewer terminal list --json` emits: { states: [{ target, state, source, updatedAt, lastPrompt, note }], errors: [{ operation, target, at, detail, stack }] }. state is working | waiting | done | idle, where done means the turn finished and nobody has read the output yet. source is hook for a reported event, screen for a visible matched rule, and activity for the motion fallback. Read-only. Call this before asking the human anything — another agent may already be blocking them.",
|
|
27779
28923
|
inputSchema: {
|
|
27780
28924
|
type: "object",
|
|
27781
28925
|
properties: {
|
|
@@ -27861,7 +29005,9 @@ function runTerminalListTool(input) {
|
|
|
27861
29005
|
}
|
|
27862
29006
|
const all = listAgentStates();
|
|
27863
29007
|
const states2 = attentionOnly ? all.filter((record) => needsAttention(record.state)) : all;
|
|
27864
|
-
return {
|
|
29008
|
+
return {
|
|
29009
|
+
text: JSON.stringify({ states: states2, errors: getAgentActivityErrors() }, null, 2)
|
|
29010
|
+
};
|
|
27865
29011
|
}
|
|
27866
29012
|
async function runTerminalCaptureTool(input, options) {
|
|
27867
29013
|
const params = isPlainObject(input) ? input : {};
|
|
@@ -29023,12 +30169,13 @@ var init_mcp = __esm({
|
|
|
29023
30169
|
init_search_cli();
|
|
29024
30170
|
init_search_service();
|
|
29025
30171
|
init_status_cli();
|
|
30172
|
+
init_activity();
|
|
29026
30173
|
init_agent_state2();
|
|
29027
30174
|
init_capture2();
|
|
29028
30175
|
init_capture();
|
|
29029
30176
|
MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
29030
30177
|
PACKAGE_VERSION = JSON.parse(
|
|
29031
|
-
readFileSync7(
|
|
30178
|
+
readFileSync7(join21(ROOT, "package.json"), "utf8")
|
|
29032
30179
|
).version;
|
|
29033
30180
|
MCP_SERVER_INFO = {
|
|
29034
30181
|
name: "code-viewer",
|
|
@@ -29220,7 +30367,7 @@ var init_os_opener = __esm({
|
|
|
29220
30367
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
29221
30368
|
import { existsSync as existsSync8, lstatSync as lstatSync5, mkdirSync as mkdirSync4, renameSync } from "node:fs";
|
|
29222
30369
|
import { homedir as homedir3, release as osRelease2 } from "node:os";
|
|
29223
|
-
import { basename as basename3, dirname as dirname6, join as
|
|
30370
|
+
import { basename as basename3, dirname as dirname6, join as join22, resolve as resolve2 } from "node:path";
|
|
29224
30371
|
function windowsTrashScript(path) {
|
|
29225
30372
|
const quotedPath = path.replace(/'/g, "''");
|
|
29226
30373
|
return [
|
|
@@ -29311,12 +30458,12 @@ async function runRequiredCommand(operation, command, cwd2) {
|
|
|
29311
30458
|
}
|
|
29312
30459
|
}
|
|
29313
30460
|
function managedTrashRoot(cwd2) {
|
|
29314
|
-
return
|
|
30461
|
+
return join22(cwd2, ".code-viewer", "trash");
|
|
29315
30462
|
}
|
|
29316
30463
|
function movePathIntoTrashDirectory(path, trashRoot) {
|
|
29317
30464
|
mkdirSync4(trashRoot, { recursive: true });
|
|
29318
30465
|
const name = basename3(path) || "trash-item";
|
|
29319
|
-
const trashPath =
|
|
30466
|
+
const trashPath = join22(trashRoot, `${name}-${randomUUID2()}`);
|
|
29320
30467
|
if (existsSync8(trashPath)) {
|
|
29321
30468
|
throw Object.assign(new Error("trash destination already exists"), {
|
|
29322
30469
|
trashPath
|
|
@@ -29326,7 +30473,7 @@ function movePathIntoTrashDirectory(path, trashRoot) {
|
|
|
29326
30473
|
return { trashPath };
|
|
29327
30474
|
}
|
|
29328
30475
|
function trashRootForHandle(cwd2, platform, release) {
|
|
29329
|
-
if (platform === "darwin") return
|
|
30476
|
+
if (platform === "darwin") return join22(homedir3(), ".Trash");
|
|
29330
30477
|
if (isWsl(platform, release)) return managedTrashRoot(cwd2);
|
|
29331
30478
|
return null;
|
|
29332
30479
|
}
|
|
@@ -29339,7 +30486,7 @@ function unsupportedTrashError(operation, platform, release) {
|
|
|
29339
30486
|
async function movePathToTrash(path, cwd2, platform = process.platform, release = osRelease2()) {
|
|
29340
30487
|
lstatSync5(path);
|
|
29341
30488
|
if (platform === "darwin") {
|
|
29342
|
-
return movePathIntoTrashDirectory(path,
|
|
30489
|
+
return movePathIntoTrashDirectory(path, join22(homedir3(), ".Trash"));
|
|
29343
30490
|
}
|
|
29344
30491
|
if (isWsl(platform, release)) {
|
|
29345
30492
|
return movePathIntoTrashDirectory(path, managedTrashRoot(cwd2));
|
|
@@ -29428,12 +30575,12 @@ var init_request_origin = __esm({
|
|
|
29428
30575
|
|
|
29429
30576
|
// web-src/server/watch-supervisor.ts
|
|
29430
30577
|
import { spawn as spawn3 } from "node:child_process";
|
|
29431
|
-
import { join as
|
|
30578
|
+
import { join as join23 } from "node:path";
|
|
29432
30579
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
29433
30580
|
function watchChildCommand() {
|
|
29434
30581
|
const entry = process.argv[1] ?? "";
|
|
29435
30582
|
const isTypeScriptEntry = entry.endsWith(".ts");
|
|
29436
|
-
const script = isTypeScriptEntry ?
|
|
30583
|
+
const script = isTypeScriptEntry ? join23(fileURLToPath3(new URL(".", import.meta.url)), "cli.ts") : entry;
|
|
29437
30584
|
const loaderArgs = isTypeScriptEntry ? process.execArgv : [];
|
|
29438
30585
|
return [process.argv[0], ...loaderArgs, script, "watch-child"];
|
|
29439
30586
|
}
|
|
@@ -29601,12 +30748,12 @@ function parseTmuxClients(stdout) {
|
|
|
29601
30748
|
if (!line) continue;
|
|
29602
30749
|
const fields = line.split(TMUX_FIELD_SEP);
|
|
29603
30750
|
if (fields.length < CLIENT_FIELDS.length) continue;
|
|
29604
|
-
const tty = fields[
|
|
30751
|
+
const tty = fields[FIELD2.tty] ?? "";
|
|
29605
30752
|
if (!tty) continue;
|
|
29606
30753
|
clients.push({
|
|
29607
30754
|
tty,
|
|
29608
|
-
session: fields[
|
|
29609
|
-
pane: fields[
|
|
30755
|
+
session: fields[FIELD2.session] ?? "",
|
|
30756
|
+
pane: fields[FIELD2.pane] ?? ""
|
|
29610
30757
|
});
|
|
29611
30758
|
}
|
|
29612
30759
|
return clients;
|
|
@@ -29621,7 +30768,7 @@ function findClientByTty(clients, tty) {
|
|
|
29621
30768
|
if (!tty) return null;
|
|
29622
30769
|
return clients.find((client) => client.tty === tty) ?? null;
|
|
29623
30770
|
}
|
|
29624
|
-
var CLIENT_FIELDS, CLIENT_FORMAT,
|
|
30771
|
+
var CLIENT_FIELDS, CLIENT_FORMAT, FIELD2;
|
|
29625
30772
|
var init_clients = __esm({
|
|
29626
30773
|
"web-src/server/tmux/clients.ts"() {
|
|
29627
30774
|
init_command();
|
|
@@ -29633,7 +30780,7 @@ var init_clients = __esm({
|
|
|
29633
30780
|
"#{pane_id}"
|
|
29634
30781
|
];
|
|
29635
30782
|
CLIENT_FORMAT = CLIENT_FIELDS.join(TMUX_FIELD_SEP);
|
|
29636
|
-
|
|
30783
|
+
FIELD2 = {
|
|
29637
30784
|
tty: 0,
|
|
29638
30785
|
session: 1,
|
|
29639
30786
|
pane: 2
|
|
@@ -29719,141 +30866,20 @@ async function openTmuxPaneInShell(paneId, cwd2, size = {}) {
|
|
|
29719
30866
|
status: "error",
|
|
29720
30867
|
error: errorWithCauses(
|
|
29721
30868
|
"failed to initialize the shell and close it afterward",
|
|
29722
|
-
[writeError, closed.error]
|
|
29723
|
-
)
|
|
29724
|
-
};
|
|
29725
|
-
}
|
|
29726
|
-
return written;
|
|
29727
|
-
}
|
|
29728
|
-
return { status: "ok", session: created.session, action: "attached" };
|
|
29729
|
-
}
|
|
29730
|
-
var init_open = __esm({
|
|
29731
|
-
"web-src/server/terminal/open.ts"() {
|
|
29732
|
-
init_error_detail();
|
|
29733
|
-
init_session();
|
|
29734
|
-
init_clients();
|
|
29735
|
-
init_focus();
|
|
29736
|
-
}
|
|
29737
|
-
});
|
|
29738
|
-
|
|
29739
|
-
// web-src/server/tmux/panes.ts
|
|
29740
|
-
function toInt(value) {
|
|
29741
|
-
const parsed = Number.parseInt(value ?? "", 10);
|
|
29742
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
29743
|
-
}
|
|
29744
|
-
function toFlag(value) {
|
|
29745
|
-
return value === "1";
|
|
29746
|
-
}
|
|
29747
|
-
function parseTmuxPanes(stdout, worktrees = []) {
|
|
29748
|
-
const sessions2 = [];
|
|
29749
|
-
const sessionByName = /* @__PURE__ */ new Map();
|
|
29750
|
-
const windowByKey = /* @__PURE__ */ new Map();
|
|
29751
|
-
for (const line of stdout.split("\n")) {
|
|
29752
|
-
if (!line) continue;
|
|
29753
|
-
const fields = line.split(TMUX_FIELD_SEP);
|
|
29754
|
-
if (fields.length < PANE_FIELDS.length) continue;
|
|
29755
|
-
const paneId = fields[FIELD2.paneId];
|
|
29756
|
-
if (!paneId) continue;
|
|
29757
|
-
const sessionName = fields[FIELD2.sessionName] ?? "";
|
|
29758
|
-
let session = sessionByName.get(sessionName);
|
|
29759
|
-
if (!session) {
|
|
29760
|
-
session = {
|
|
29761
|
-
name: sessionName,
|
|
29762
|
-
attached: toFlag(fields[FIELD2.sessionAttached]),
|
|
29763
|
-
windows: []
|
|
29764
|
-
};
|
|
29765
|
-
sessionByName.set(sessionName, session);
|
|
29766
|
-
sessions2.push(session);
|
|
29767
|
-
}
|
|
29768
|
-
const windowIndex = toInt(fields[FIELD2.windowIndex]);
|
|
29769
|
-
const windowKey = `${sessionName}${TMUX_FIELD_SEP}${windowIndex}`;
|
|
29770
|
-
let window = windowByKey.get(windowKey);
|
|
29771
|
-
if (!window) {
|
|
29772
|
-
window = {
|
|
29773
|
-
index: windowIndex,
|
|
29774
|
-
name: fields[FIELD2.windowName] ?? "",
|
|
29775
|
-
active: toFlag(fields[FIELD2.windowActive]),
|
|
29776
|
-
panes: []
|
|
29777
|
-
};
|
|
29778
|
-
windowByKey.set(windowKey, window);
|
|
29779
|
-
session.windows.push(window);
|
|
29780
|
-
}
|
|
29781
|
-
const paneIndex = toInt(fields[FIELD2.paneIndex]);
|
|
29782
|
-
const pane = {
|
|
29783
|
-
id: paneId,
|
|
29784
|
-
label: `${sessionName}:${windowIndex}.${paneIndex}`,
|
|
29785
|
-
paneIndex,
|
|
29786
|
-
title: fields[FIELD2.paneTitle] ?? "",
|
|
29787
|
-
command: fields[FIELD2.paneCommand] ?? "",
|
|
29788
|
-
path: fields[FIELD2.panePath] ?? "",
|
|
29789
|
-
width: toInt(fields[FIELD2.paneWidth]),
|
|
29790
|
-
height: toInt(fields[FIELD2.paneHeight]),
|
|
29791
|
-
active: toFlag(fields[FIELD2.paneActive]),
|
|
29792
|
-
inRepo: worktrees.length === 0 || isPathInsideAny(fields[FIELD2.panePath] ?? "", worktrees)
|
|
29793
|
-
};
|
|
29794
|
-
window.panes.push(pane);
|
|
29795
|
-
}
|
|
29796
|
-
return sessions2;
|
|
29797
|
-
}
|
|
29798
|
-
async function listTmuxPanes(cwd2) {
|
|
29799
|
-
const [result, worktrees] = await Promise.all([
|
|
29800
|
-
runTmux(["list-panes", "-a", "-F", PANE_FORMAT], cwd2),
|
|
29801
|
-
worktreePathsAsync(cwd2)
|
|
29802
|
-
]);
|
|
29803
|
-
if (result.status === "missing") {
|
|
29804
|
-
return { available: false, running: false, sessions: [] };
|
|
29805
|
-
}
|
|
29806
|
-
if (result.status === "no-server" || result.status === "no-target") {
|
|
29807
|
-
return { available: true, running: false, sessions: [] };
|
|
29808
|
-
}
|
|
29809
|
-
if (result.status === "error") {
|
|
29810
|
-
throw errorWithCause("failed to list tmux panes", result.error);
|
|
30869
|
+
[writeError, closed.error]
|
|
30870
|
+
)
|
|
30871
|
+
};
|
|
30872
|
+
}
|
|
30873
|
+
return written;
|
|
29811
30874
|
}
|
|
29812
|
-
return {
|
|
29813
|
-
available: true,
|
|
29814
|
-
running: true,
|
|
29815
|
-
sessions: parseTmuxPanes(result.stdout, worktrees)
|
|
29816
|
-
};
|
|
30875
|
+
return { status: "ok", session: created.session, action: "attached" };
|
|
29817
30876
|
}
|
|
29818
|
-
var
|
|
29819
|
-
|
|
29820
|
-
"web-src/server/tmux/panes.ts"() {
|
|
30877
|
+
var init_open = __esm({
|
|
30878
|
+
"web-src/server/terminal/open.ts"() {
|
|
29821
30879
|
init_error_detail();
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
PANE_FIELDS = [
|
|
29826
|
-
"#{pane_id}",
|
|
29827
|
-
"#{session_name}",
|
|
29828
|
-
"#{session_attached}",
|
|
29829
|
-
"#{window_index}",
|
|
29830
|
-
"#{window_name}",
|
|
29831
|
-
"#{window_active}",
|
|
29832
|
-
"#{pane_index}",
|
|
29833
|
-
"#{pane_active}",
|
|
29834
|
-
"#{pane_width}",
|
|
29835
|
-
"#{pane_height}",
|
|
29836
|
-
"#{pane_current_command}",
|
|
29837
|
-
"#{pane_current_path}",
|
|
29838
|
-
// タイトルは自由文字列なので必ず最後に置く。
|
|
29839
|
-
"#{pane_title}"
|
|
29840
|
-
];
|
|
29841
|
-
PANE_FORMAT = PANE_FIELDS.join(TMUX_FIELD_SEP);
|
|
29842
|
-
FIELD2 = {
|
|
29843
|
-
paneId: 0,
|
|
29844
|
-
sessionName: 1,
|
|
29845
|
-
sessionAttached: 2,
|
|
29846
|
-
windowIndex: 3,
|
|
29847
|
-
windowName: 4,
|
|
29848
|
-
windowActive: 5,
|
|
29849
|
-
paneIndex: 6,
|
|
29850
|
-
paneActive: 7,
|
|
29851
|
-
paneWidth: 8,
|
|
29852
|
-
paneHeight: 9,
|
|
29853
|
-
paneCommand: 10,
|
|
29854
|
-
panePath: 11,
|
|
29855
|
-
paneTitle: 12
|
|
29856
|
-
};
|
|
30880
|
+
init_session();
|
|
30881
|
+
init_clients();
|
|
30882
|
+
init_focus();
|
|
29857
30883
|
}
|
|
29858
30884
|
});
|
|
29859
30885
|
|
|
@@ -30149,65 +31175,6 @@ var init_handle3 = __esm({
|
|
|
30149
31175
|
}
|
|
30150
31176
|
});
|
|
30151
31177
|
|
|
30152
|
-
// web-src/core/terminal-paste.ts
|
|
30153
|
-
function pasteImageExtension(mime) {
|
|
30154
|
-
if (typeof mime !== "string") return null;
|
|
30155
|
-
const base = mime.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
30156
|
-
return PASTE_IMAGE_TYPES[base] ?? null;
|
|
30157
|
-
}
|
|
30158
|
-
function looksLikeBase64(value) {
|
|
30159
|
-
return typeof value === "string" && value.length > 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
30160
|
-
}
|
|
30161
|
-
function base64ByteLength(value) {
|
|
30162
|
-
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
30163
|
-
return Math.floor(value.length * 3 / 4) - padding;
|
|
30164
|
-
}
|
|
30165
|
-
var PASTE_IMAGE_TYPES, MAX_PASTE_IMAGE_BYTES, MAX_PASTE_BODY_BYTES, SHIFT_ENTER_SEQUENCE;
|
|
30166
|
-
var init_terminal_paste = __esm({
|
|
30167
|
-
"web-src/core/terminal-paste.ts"() {
|
|
30168
|
-
PASTE_IMAGE_TYPES = {
|
|
30169
|
-
"image/png": "png",
|
|
30170
|
-
"image/jpeg": "jpg",
|
|
30171
|
-
"image/gif": "gif",
|
|
30172
|
-
"image/webp": "webp"
|
|
30173
|
-
};
|
|
30174
|
-
MAX_PASTE_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
30175
|
-
MAX_PASTE_BODY_BYTES = Math.ceil(MAX_PASTE_IMAGE_BYTES * 1.4);
|
|
30176
|
-
SHIFT_ENTER_SEQUENCE = `${String.fromCharCode(27)}[200~${String.fromCharCode(10)}${String.fromCharCode(27)}[201~`;
|
|
30177
|
-
}
|
|
30178
|
-
});
|
|
30179
|
-
|
|
30180
|
-
// web-src/core/terminal-images.ts
|
|
30181
|
-
function terminalImageExtension(path) {
|
|
30182
|
-
const dot = path.lastIndexOf(".");
|
|
30183
|
-
if (dot < 0) return null;
|
|
30184
|
-
const extension = path.slice(dot + 1).toLowerCase();
|
|
30185
|
-
return TERMINAL_IMAGE_EXTENSIONS.includes(extension) ? extension : null;
|
|
30186
|
-
}
|
|
30187
|
-
var TERMINAL_IMAGE_EXTENSIONS, MAX_TERMINAL_IMAGE_QUERY, ESC, BEL, ANSI_RE, PATH_CHAR, NAME_CHAR, IMAGE_PATH_RE;
|
|
30188
|
-
var init_terminal_images = __esm({
|
|
30189
|
-
"web-src/core/terminal-images.ts"() {
|
|
30190
|
-
init_terminal_paste();
|
|
30191
|
-
TERMINAL_IMAGE_EXTENSIONS = [
|
|
30192
|
-
...new Set(Object.values(PASTE_IMAGE_TYPES)),
|
|
30193
|
-
"jpeg"
|
|
30194
|
-
];
|
|
30195
|
-
MAX_TERMINAL_IMAGE_QUERY = 16;
|
|
30196
|
-
ESC = String.fromCharCode(27);
|
|
30197
|
-
BEL = String.fromCharCode(7);
|
|
30198
|
-
ANSI_RE = new RegExp(
|
|
30199
|
-
`${ESC}\\[[0-9;?]*[ -/]*[@-~]|${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)|${ESC}[@-Z\\\\-_]`,
|
|
30200
|
-
"g"
|
|
30201
|
-
);
|
|
30202
|
-
PATH_CHAR = "[\\p{L}\\p{N}._~+@%/-]";
|
|
30203
|
-
NAME_CHAR = "[\\p{L}\\p{N}_~+@%-]";
|
|
30204
|
-
IMAGE_PATH_RE = new RegExp(
|
|
30205
|
-
`${PATH_CHAR}*${NAME_CHAR}\\.(?:${TERMINAL_IMAGE_EXTENSIONS.join("|")})(?![\\p{L}\\p{N}])`,
|
|
30206
|
-
"giu"
|
|
30207
|
-
);
|
|
30208
|
-
}
|
|
30209
|
-
});
|
|
30210
|
-
|
|
30211
31178
|
// web-src/server/terminal/images.ts
|
|
30212
31179
|
import { realpathSync as realpathSync7, statSync as statSync7 } from "node:fs";
|
|
30213
31180
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -30259,7 +31226,7 @@ var init_images = __esm({
|
|
|
30259
31226
|
|
|
30260
31227
|
// web-src/server/terminal/paste.ts
|
|
30261
31228
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
30262
|
-
import { join as
|
|
31229
|
+
import { join as join24 } from "node:path";
|
|
30263
31230
|
async function savePastedImage(cwd2, mime, base64) {
|
|
30264
31231
|
const extension = pasteImageExtension(mime);
|
|
30265
31232
|
if (!extension) {
|
|
@@ -30284,8 +31251,8 @@ async function savePastedImage(cwd2, mime, base64) {
|
|
|
30284
31251
|
return { status: "invalid", message: "image too large" };
|
|
30285
31252
|
}
|
|
30286
31253
|
const name = `${makeTimedId("paste")}.${extension}`;
|
|
30287
|
-
const dir =
|
|
30288
|
-
const path =
|
|
31254
|
+
const dir = join24(cwd2, PASTE_DIR);
|
|
31255
|
+
const path = join24(dir, name);
|
|
30289
31256
|
try {
|
|
30290
31257
|
await mkdir2(dir, { recursive: true });
|
|
30291
31258
|
await writeFile2(path, bytes);
|
|
@@ -30302,7 +31269,7 @@ var init_paste = __esm({
|
|
|
30302
31269
|
"web-src/server/terminal/paste.ts"() {
|
|
30303
31270
|
init_id();
|
|
30304
31271
|
init_terminal_paste();
|
|
30305
|
-
PASTE_DIR =
|
|
31272
|
+
PASTE_DIR = join24(".code-viewer", "pasted");
|
|
30306
31273
|
}
|
|
30307
31274
|
});
|
|
30308
31275
|
|
|
@@ -30335,13 +31302,53 @@ async function handleStatePost(req) {
|
|
|
30335
31302
|
return json({ ok: true, state: record });
|
|
30336
31303
|
}
|
|
30337
31304
|
function handleStatesGet(url) {
|
|
31305
|
+
const errors = getAgentActivityErrors();
|
|
30338
31306
|
const target = url.searchParams.get("target");
|
|
30339
31307
|
if (target) {
|
|
30340
31308
|
const record = getAgentState(target);
|
|
30341
31309
|
if (!record) return textError("unknown target", 404);
|
|
30342
|
-
return json({ states: [record] });
|
|
31310
|
+
return json({ states: [record], errors });
|
|
31311
|
+
}
|
|
31312
|
+
return json({
|
|
31313
|
+
states: listAgentStates(),
|
|
31314
|
+
errors
|
|
31315
|
+
});
|
|
31316
|
+
}
|
|
31317
|
+
function ruleOperationError(code, error) {
|
|
31318
|
+
console.error(`[code-viewer] terminal rule ${code} failed`, error);
|
|
31319
|
+
const errors = [
|
|
31320
|
+
{
|
|
31321
|
+
path: "$",
|
|
31322
|
+
code,
|
|
31323
|
+
message: formatErrorDetail(error),
|
|
31324
|
+
...error instanceof Error && error.stack ? { stack: error.stack } : {}
|
|
31325
|
+
}
|
|
31326
|
+
];
|
|
31327
|
+
return json({ errors }, 500);
|
|
31328
|
+
}
|
|
31329
|
+
async function handleRulesGet(cwd2) {
|
|
31330
|
+
return json(await reloadAgentScreenRules(cwd2));
|
|
31331
|
+
}
|
|
31332
|
+
async function handleRulesPut(req, cwd2) {
|
|
31333
|
+
const body = await parseBoundedJsonBody(
|
|
31334
|
+
req,
|
|
31335
|
+
MAX_AGENT_SCREEN_RULES_BYTES,
|
|
31336
|
+
"terminal rules body too large"
|
|
31337
|
+
);
|
|
31338
|
+
if (body instanceof Response) return body;
|
|
31339
|
+
try {
|
|
31340
|
+
const result = await saveAgentScreenRules(cwd2, body);
|
|
31341
|
+
return json(result, "source" in result ? 200 : 400);
|
|
31342
|
+
} catch (error) {
|
|
31343
|
+
return ruleOperationError("save_failed", error);
|
|
31344
|
+
}
|
|
31345
|
+
}
|
|
31346
|
+
async function handleRulesDelete(cwd2) {
|
|
31347
|
+
try {
|
|
31348
|
+
return json(await resetAgentScreenRules(cwd2));
|
|
31349
|
+
} catch (error) {
|
|
31350
|
+
return ruleOperationError("reset_failed", error);
|
|
30343
31351
|
}
|
|
30344
|
-
return json({ states: listAgentStates() });
|
|
30345
31352
|
}
|
|
30346
31353
|
async function handleCaptureGet(url, cwd2) {
|
|
30347
31354
|
const target = url.searchParams.get("target");
|
|
@@ -30414,6 +31421,15 @@ function handleAgentRoute(req, url, cwd2, sideEffectAllowed) {
|
|
|
30414
31421
|
sideEffect: false,
|
|
30415
31422
|
handler: () => Promise.resolve(handleStatesGet(url))
|
|
30416
31423
|
},
|
|
31424
|
+
"/_agent/rules": {
|
|
31425
|
+
methods: ["GET", "PUT", "DELETE"],
|
|
31426
|
+
sideEffect: (method) => method !== "GET",
|
|
31427
|
+
handler: () => {
|
|
31428
|
+
if (req.method === "GET") return handleRulesGet(cwd2);
|
|
31429
|
+
if (req.method === "DELETE") return handleRulesDelete(cwd2);
|
|
31430
|
+
return handleRulesPut(req, cwd2);
|
|
31431
|
+
}
|
|
31432
|
+
},
|
|
30417
31433
|
"/_agent/capture": {
|
|
30418
31434
|
methods: ["GET"],
|
|
30419
31435
|
sideEffect: false,
|
|
@@ -30450,10 +31466,12 @@ var init_handle4 = __esm({
|
|
|
30450
31466
|
init_handle_shared();
|
|
30451
31467
|
init_raw_file_headers();
|
|
30452
31468
|
init_runtime();
|
|
31469
|
+
init_activity();
|
|
30453
31470
|
init_agent_state2();
|
|
30454
31471
|
init_capture2();
|
|
30455
31472
|
init_images();
|
|
30456
31473
|
init_paste();
|
|
31474
|
+
init_rules();
|
|
30457
31475
|
MAX_STATE_TEXT = 2e3;
|
|
30458
31476
|
}
|
|
30459
31477
|
});
|
|
@@ -30574,134 +31592,6 @@ var init_state_route = __esm({
|
|
|
30574
31592
|
}
|
|
30575
31593
|
});
|
|
30576
31594
|
|
|
30577
|
-
// web-src/server/terminal/activity.ts
|
|
30578
|
-
var activity_exports = {};
|
|
30579
|
-
__export(activity_exports, {
|
|
30580
|
-
ACTIVITY_IDLE_AFTER_MS: () => ACTIVITY_IDLE_AFTER_MS,
|
|
30581
|
-
ACTIVITY_POLL_INTERVAL_MS: () => ACTIVITY_POLL_INTERVAL_MS,
|
|
30582
|
-
MAX_PANES_PER_SWEEP: () => MAX_PANES_PER_SWEEP,
|
|
30583
|
-
OVERRIDE_CHANGE_STREAK: () => OVERRIDE_CHANGE_STREAK,
|
|
30584
|
-
nextActivityState: () => nextActivityState,
|
|
30585
|
-
rotateForSweep: () => rotateForSweep,
|
|
30586
|
-
startAgentActivityWatch: () => startAgentActivityWatch,
|
|
30587
|
-
stopAgentActivityWatch: () => stopAgentActivityWatch
|
|
30588
|
-
});
|
|
30589
|
-
function nextActivityState(previous, hash, now) {
|
|
30590
|
-
const changed = previous === void 0 || previous.hash !== hash;
|
|
30591
|
-
const changedAt = changed ? now : previous.changedAt;
|
|
30592
|
-
const changeStreak = changed ? previous === void 0 ? 0 : previous.changeStreak + 1 : 0;
|
|
30593
|
-
return {
|
|
30594
|
-
state: agentStateFromActivity(
|
|
30595
|
-
changed,
|
|
30596
|
-
now - changedAt,
|
|
30597
|
-
ACTIVITY_IDLE_AFTER_MS
|
|
30598
|
-
),
|
|
30599
|
-
seen: { hash, changedAt, changeStreak },
|
|
30600
|
-
override: changeStreak >= OVERRIDE_CHANGE_STREAK
|
|
30601
|
-
};
|
|
30602
|
-
}
|
|
30603
|
-
function observe(target, content, note) {
|
|
30604
|
-
const next = nextActivityState(
|
|
30605
|
-
seen.get(target),
|
|
30606
|
-
hashLine(content),
|
|
30607
|
-
Date.now()
|
|
30608
|
-
);
|
|
30609
|
-
seen.set(target, next.seen);
|
|
30610
|
-
recordAgentState({
|
|
30611
|
-
target,
|
|
30612
|
-
state: next.state,
|
|
30613
|
-
source: "activity",
|
|
30614
|
-
note,
|
|
30615
|
-
override: next.override
|
|
30616
|
-
});
|
|
30617
|
-
}
|
|
30618
|
-
function rotateForSweep(items, offset, limit) {
|
|
30619
|
-
if (items.length === 0) return { batch: [], nextOffset: 0 };
|
|
30620
|
-
const take = Math.min(limit, items.length);
|
|
30621
|
-
const start = (offset % items.length + items.length) % items.length;
|
|
30622
|
-
const batch = [];
|
|
30623
|
-
for (let i = 0; i < take; i += 1) {
|
|
30624
|
-
batch.push(items[(start + i) % items.length]);
|
|
30625
|
-
}
|
|
30626
|
-
return { batch, nextOffset: (start + take) % items.length };
|
|
30627
|
-
}
|
|
30628
|
-
async function sweep(cwd2) {
|
|
30629
|
-
if (inFlight) return;
|
|
30630
|
-
inFlight = true;
|
|
30631
|
-
try {
|
|
30632
|
-
const panes = await listTmuxPanes(cwd2);
|
|
30633
|
-
const shells = listShellSessions();
|
|
30634
|
-
const allPanes = panes.running ? flattenTmuxPanes(panes.sessions) : [];
|
|
30635
|
-
if (panes.running) {
|
|
30636
|
-
const known = /* @__PURE__ */ new Set([
|
|
30637
|
-
...allPanes.map((pane) => pane.id),
|
|
30638
|
-
...shells.map((session) => session.id)
|
|
30639
|
-
]);
|
|
30640
|
-
retainAgentStates(known);
|
|
30641
|
-
for (const target of [...seen.keys()]) {
|
|
30642
|
-
if (!known.has(target)) seen.delete(target);
|
|
30643
|
-
}
|
|
30644
|
-
}
|
|
30645
|
-
for (const session of shells) {
|
|
30646
|
-
const buffer = readShellBuffer(session.id);
|
|
30647
|
-
if (!buffer) continue;
|
|
30648
|
-
observe(session.id, buffer.replay, session.command);
|
|
30649
|
-
}
|
|
30650
|
-
const targets = allPanes;
|
|
30651
|
-
const { batch, nextOffset } = rotateForSweep(
|
|
30652
|
-
targets,
|
|
30653
|
-
sweepOffset,
|
|
30654
|
-
MAX_PANES_PER_SWEEP
|
|
30655
|
-
);
|
|
30656
|
-
sweepOffset = nextOffset;
|
|
30657
|
-
for (const pane of batch) {
|
|
30658
|
-
const result = await captureTmuxPane(pane.id, cwd2);
|
|
30659
|
-
if (result.status !== "ok") {
|
|
30660
|
-
seen.delete(pane.id);
|
|
30661
|
-
continue;
|
|
30662
|
-
}
|
|
30663
|
-
observe(pane.id, result.screen.content, pane.title);
|
|
30664
|
-
}
|
|
30665
|
-
} catch (error) {
|
|
30666
|
-
console.warn(
|
|
30667
|
-
`[code-viewer] agent activity sweep skipped: ${String(error)}`
|
|
30668
|
-
);
|
|
30669
|
-
} finally {
|
|
30670
|
-
inFlight = false;
|
|
30671
|
-
}
|
|
30672
|
-
}
|
|
30673
|
-
function startAgentActivityWatch(cwd2) {
|
|
30674
|
-
if (timer) return;
|
|
30675
|
-
timer = setInterval(() => void sweep(cwd2), ACTIVITY_POLL_INTERVAL_MS);
|
|
30676
|
-
timer.unref?.();
|
|
30677
|
-
}
|
|
30678
|
-
function stopAgentActivityWatch() {
|
|
30679
|
-
if (timer) clearInterval(timer);
|
|
30680
|
-
timer = null;
|
|
30681
|
-
seen.clear();
|
|
30682
|
-
sweepOffset = 0;
|
|
30683
|
-
}
|
|
30684
|
-
var ACTIVITY_POLL_INTERVAL_MS, ACTIVITY_IDLE_AFTER_MS, OVERRIDE_CHANGE_STREAK, MAX_PANES_PER_SWEEP, seen, timer, inFlight, sweepOffset;
|
|
30685
|
-
var init_activity = __esm({
|
|
30686
|
-
"web-src/server/terminal/activity.ts"() {
|
|
30687
|
-
init_agent_state();
|
|
30688
|
-
init_terminal_capture();
|
|
30689
|
-
init_tmux();
|
|
30690
|
-
init_session();
|
|
30691
|
-
init_capture();
|
|
30692
|
-
init_panes();
|
|
30693
|
-
init_agent_state2();
|
|
30694
|
-
ACTIVITY_POLL_INTERVAL_MS = 3e3;
|
|
30695
|
-
ACTIVITY_IDLE_AFTER_MS = 15e3;
|
|
30696
|
-
OVERRIDE_CHANGE_STREAK = 4;
|
|
30697
|
-
MAX_PANES_PER_SWEEP = 12;
|
|
30698
|
-
seen = /* @__PURE__ */ new Map();
|
|
30699
|
-
timer = null;
|
|
30700
|
-
inFlight = false;
|
|
30701
|
-
sweepOffset = 0;
|
|
30702
|
-
}
|
|
30703
|
-
});
|
|
30704
|
-
|
|
30705
31595
|
// web-src/server/preview.ts
|
|
30706
31596
|
var preview_exports = {};
|
|
30707
31597
|
import {
|
|
@@ -30712,7 +31602,7 @@ import {
|
|
|
30712
31602
|
statSync as statSync8,
|
|
30713
31603
|
watch
|
|
30714
31604
|
} from "node:fs";
|
|
30715
|
-
import { basename as basename5, dirname as dirname7, extname as extname2, join as
|
|
31605
|
+
import { basename as basename5, dirname as dirname7, extname as extname2, join as join25, relative as relative8 } from "node:path";
|
|
30716
31606
|
function parseCli() {
|
|
30717
31607
|
const rest = [];
|
|
30718
31608
|
for (let i = 2; i < process.argv.length; i++) {
|
|
@@ -30836,7 +31726,7 @@ Examples:
|
|
|
30836
31726
|
}
|
|
30837
31727
|
function warnIfLegacyConfigPresent() {
|
|
30838
31728
|
try {
|
|
30839
|
-
if (existsSync9(
|
|
31729
|
+
if (existsSync9(join25(cwd, ".code-viewer.json"))) {
|
|
30840
31730
|
console.warn(
|
|
30841
31731
|
"[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed."
|
|
30842
31732
|
);
|
|
@@ -30943,7 +31833,7 @@ function staticFile(pathname) {
|
|
|
30943
31833
|
}
|
|
30944
31834
|
const spec = map[pathname];
|
|
30945
31835
|
if (!spec) return null;
|
|
30946
|
-
const full =
|
|
31836
|
+
const full = join25(WEB_ROOT, spec[0]);
|
|
30947
31837
|
if (!existsSync9(full)) return text("not found", 404);
|
|
30948
31838
|
return new Response(readFileSync8(full), {
|
|
30949
31839
|
headers: { "Content-Type": spec[1], "Cache-Control": "no-store" }
|
|
@@ -31202,7 +32092,7 @@ function safeWorktreePath2(path) {
|
|
|
31202
32092
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
31203
32093
|
}
|
|
31204
32094
|
function worktreePath(path) {
|
|
31205
|
-
return
|
|
32095
|
+
return join25(cwd, path);
|
|
31206
32096
|
}
|
|
31207
32097
|
function safeOpenWorktreePath(path) {
|
|
31208
32098
|
if (path === "") {
|
|
@@ -31539,7 +32429,7 @@ async function handleLog(url) {
|
|
|
31539
32429
|
}
|
|
31540
32430
|
function blamePathKey(p) {
|
|
31541
32431
|
try {
|
|
31542
|
-
const st = statSync8(
|
|
32432
|
+
const st = statSync8(join25(cwd, p));
|
|
31543
32433
|
return `${st.mtimeMs}:${st.size}`;
|
|
31544
32434
|
} catch {
|
|
31545
32435
|
return "missing";
|
|
@@ -32070,7 +32960,7 @@ async function handleUploadFiles(req) {
|
|
|
32070
32960
|
if (file.size > MAX_UPLOAD_FILE_BYTES) return text("file too large", 413);
|
|
32071
32961
|
total += file.size;
|
|
32072
32962
|
if (total > MAX_UPLOAD_TOTAL_BYTES) return text("upload too large", 413);
|
|
32073
|
-
const target =
|
|
32963
|
+
const target = join25(realDir, safeName);
|
|
32074
32964
|
if (relative8(realDir, dirname7(target)) !== "")
|
|
32075
32965
|
return text("invalid filename", 400);
|
|
32076
32966
|
if (existsSync9(target)) return text("file exists", 409);
|
|
@@ -32221,7 +33111,7 @@ async function handleCreateDirectory(req) {
|
|
|
32221
33111
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
32222
33112
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
32223
33113
|
return text("invalid target", 400);
|
|
32224
|
-
const target =
|
|
33114
|
+
const target = join25(parent, name);
|
|
32225
33115
|
if (existsSync9(target)) return text("already exists", 409);
|
|
32226
33116
|
try {
|
|
32227
33117
|
mkdirSync5(target, { recursive: false });
|
|
@@ -32873,8 +33763,8 @@ var init_preview = __esm({
|
|
|
32873
33763
|
init_state_store();
|
|
32874
33764
|
init_watch_supervisor();
|
|
32875
33765
|
init_worktree_watcher();
|
|
32876
|
-
WEB_ROOT =
|
|
32877
|
-
VERSION = JSON.parse(readFileSync8(
|
|
33766
|
+
WEB_ROOT = join25(ROOT, "web");
|
|
33767
|
+
VERSION = JSON.parse(readFileSync8(join25(ROOT, "package.json"), "utf8")).version;
|
|
32878
33768
|
DEFAULT_ARGS = ["HEAD"];
|
|
32879
33769
|
PREVIEW_HUNKS_DEFAULT = 3;
|
|
32880
33770
|
PREVIEW_LINES_DEFAULT = 1200;
|