@yaklabs-ai/plugin-sdk 0.53.0 → 0.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -22
- package/THIRD-PARTY-INVENTORY.json +1 -1
- package/dist/canonical-transcript.d.ts +66 -0
- package/dist/canonical-transcript.js +567 -3
- package/dist/disclosure.d.ts +333 -110
- package/dist/disclosure.js +26 -0
- package/dist/endpoints.d.ts +29 -0
- package/dist/endpoints.js +28 -0
- package/dist/fs.d.ts +26 -0
- package/dist/fs.js +49 -2
- package/dist/kay-agent-testing.d.ts +358 -111
- package/dist/kay-agent-testing.js +1 -0
- package/dist/kay-agent.d.ts +432 -87
- package/dist/kay-agent.js +64 -17
- package/dist/kay-app-session-summary.d.ts +288 -128
- package/dist/kay-app.d.ts +1760 -212
- package/dist/kay-app.js +107 -52
- package/dist/text-diff.d.ts +106 -0
- package/dist/text-diff.js +223 -0
- package/dist/transcript.d.ts +118 -0
- package/dist/transcript.js +266 -0
- package/dist/workers.d.ts +27 -0
- package/dist/workers.js +20 -0
- package/package.json +9 -1
package/dist/kay-app.js
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
// packages/plugin-sdk/dist/workers.js
|
|
2
|
+
var PluginWorkerError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message = `Plugin worker request failed: ${code}`) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "PluginWorkerError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function createUnavailablePluginWorkers() {
|
|
11
|
+
return {
|
|
12
|
+
async run() {
|
|
13
|
+
throw new PluginWorkerError("spawn_failed", "Plugin workers are not available on this host.");
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
// packages/herd-uri/dist/errors.js
|
|
2
19
|
var UriError = class extends Error {
|
|
3
20
|
};
|
|
@@ -308,6 +325,31 @@ function createUnavailableUriCaller(message) {
|
|
|
308
325
|
};
|
|
309
326
|
}
|
|
310
327
|
|
|
328
|
+
// packages/plugin-protocol/dist/project-directories.js
|
|
329
|
+
import { z as z5 } from "zod";
|
|
330
|
+
var PluginProjectDirectories = z5.strictObject({
|
|
331
|
+
/** An endpoint this plugin's app target declares; called with a {@link ProjectDirectoryRequest}. */
|
|
332
|
+
endpoint: z5.string().min(1),
|
|
333
|
+
/** A scheme this plugin's URI modules serve; a project is addressed as `<scheme>://<id>`. */
|
|
334
|
+
scheme: z5.string().min(1)
|
|
335
|
+
});
|
|
336
|
+
var ProjectDirectoryRequest = z5.strictObject({
|
|
337
|
+
/** The project's stable id. */
|
|
338
|
+
id: z5.string().min(1)
|
|
339
|
+
});
|
|
340
|
+
var ProjectDirectory = z5.strictObject({
|
|
341
|
+
id: z5.string().min(1),
|
|
342
|
+
/** Absolute path of the project's folder. */
|
|
343
|
+
path: z5.string().min(1),
|
|
344
|
+
/** Absolute path a new session in this project starts in. */
|
|
345
|
+
defaultCwd: z5.string().min(1),
|
|
346
|
+
/** Ancestor project ids, root first. */
|
|
347
|
+
ancestorIds: z5.array(z5.string().min(1))
|
|
348
|
+
});
|
|
349
|
+
var ProjectDirectoryResponse = z5.strictObject({
|
|
350
|
+
project: ProjectDirectory.nullable()
|
|
351
|
+
});
|
|
352
|
+
|
|
311
353
|
// packages/plugin-sdk/dist/endpoints.js
|
|
312
354
|
function createUnavailablePluginEndpointCaller(message) {
|
|
313
355
|
return {
|
|
@@ -374,41 +416,48 @@ function isCredentialShapedText(value) {
|
|
|
374
416
|
return CREDENTIAL_VALUE_PATTERNS.some((pattern) => pattern.test(value));
|
|
375
417
|
}
|
|
376
418
|
|
|
419
|
+
// packages/plugin-protocol/dist/resource-invocation-cursor.js
|
|
420
|
+
import { z as z6 } from "zod";
|
|
421
|
+
var CorpusResourceInvocationCursor = z6.strictObject({
|
|
422
|
+
at: z6.iso.datetime({ offset: true }),
|
|
423
|
+
eventId: z6.string().min(1)
|
|
424
|
+
});
|
|
425
|
+
|
|
377
426
|
// packages/plugin-protocol/dist/json.js
|
|
378
|
-
import { z as
|
|
379
|
-
var JsonValue =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
427
|
+
import { z as z7 } from "zod";
|
|
428
|
+
var JsonValue = z7.lazy(() => z7.union([
|
|
429
|
+
z7.string(),
|
|
430
|
+
z7.number(),
|
|
431
|
+
z7.boolean(),
|
|
432
|
+
z7.null(),
|
|
433
|
+
z7.array(JsonValue),
|
|
434
|
+
z7.record(z7.string(), JsonValue)
|
|
386
435
|
]));
|
|
387
|
-
var JsonObject =
|
|
388
|
-
var JsonSchema =
|
|
436
|
+
var JsonObject = z7.record(z7.string(), JsonValue);
|
|
437
|
+
var JsonSchema = z7.record(z7.string(), JsonValue);
|
|
389
438
|
|
|
390
439
|
// packages/plugin-protocol/dist/shell-commands.js
|
|
391
|
-
import { z as
|
|
392
|
-
var PluginShellCommand =
|
|
393
|
-
name:
|
|
394
|
-
aliases:
|
|
395
|
-
endpoint:
|
|
396
|
-
usage:
|
|
440
|
+
import { z as z8 } from "zod";
|
|
441
|
+
var PluginShellCommand = z8.strictObject({
|
|
442
|
+
name: z8.string().regex(/^[a-z][a-z0-9-]*$/),
|
|
443
|
+
aliases: z8.array(z8.string().regex(/^[a-z][a-z0-9-]*$/)).optional(),
|
|
444
|
+
endpoint: z8.string().min(1),
|
|
445
|
+
usage: z8.string().min(1)
|
|
397
446
|
});
|
|
398
|
-
var ShellCommandOutcome =
|
|
399
|
-
exitCode:
|
|
447
|
+
var ShellCommandOutcome = z8.strictObject({
|
|
448
|
+
exitCode: z8.number().int(),
|
|
400
449
|
envelope: JsonValue,
|
|
401
450
|
result: JsonValue.optional()
|
|
402
451
|
});
|
|
403
|
-
var ShellCommandRequest =
|
|
404
|
-
argv:
|
|
405
|
-
cwd:
|
|
406
|
-
outcomes:
|
|
452
|
+
var ShellCommandRequest = z8.strictObject({
|
|
453
|
+
argv: z8.array(z8.string()),
|
|
454
|
+
cwd: z8.string(),
|
|
455
|
+
outcomes: z8.array(ShellCommandOutcome).max(3)
|
|
407
456
|
});
|
|
408
|
-
var ShellCommandStep =
|
|
409
|
-
|
|
410
|
-
kind:
|
|
411
|
-
toolName:
|
|
457
|
+
var ShellCommandStep = z8.discriminatedUnion("kind", [
|
|
458
|
+
z8.strictObject({
|
|
459
|
+
kind: z8.literal("invoke"),
|
|
460
|
+
toolName: z8.enum([
|
|
412
461
|
"read",
|
|
413
462
|
"list",
|
|
414
463
|
"search",
|
|
@@ -417,13 +466,13 @@ var ShellCommandStep = z6.discriminatedUnion("kind", [
|
|
|
417
466
|
"delete",
|
|
418
467
|
"help"
|
|
419
468
|
]),
|
|
420
|
-
input:
|
|
421
|
-
flags:
|
|
469
|
+
input: z8.record(z8.string(), JsonValue),
|
|
470
|
+
flags: z8.record(z8.string(), z8.string())
|
|
422
471
|
}),
|
|
423
|
-
|
|
424
|
-
kind:
|
|
425
|
-
exitCode:
|
|
426
|
-
stdout:
|
|
472
|
+
z8.strictObject({
|
|
473
|
+
kind: z8.literal("finish"),
|
|
474
|
+
exitCode: z8.number().int(),
|
|
475
|
+
stdout: z8.string()
|
|
427
476
|
})
|
|
428
477
|
]);
|
|
429
478
|
|
|
@@ -487,7 +536,7 @@ function submit(label) {
|
|
|
487
536
|
}
|
|
488
537
|
|
|
489
538
|
// packages/plugin-sdk/dist/signals.js
|
|
490
|
-
import { z as
|
|
539
|
+
import { z as z9 } from "zod";
|
|
491
540
|
function createFallbackOnlySignals() {
|
|
492
541
|
const noStore = { status: "unavailable", reason: "no-store" };
|
|
493
542
|
return {
|
|
@@ -517,23 +566,23 @@ var POINTER = /^[^\s\p{C}/?#]+$/u;
|
|
|
517
566
|
var LABEL = /^[^\s,@\p{C}]+(?: [^\s,@\p{C}]+)*$/u;
|
|
518
567
|
var RUN_ID = /^\P{Cc}+$/u;
|
|
519
568
|
var POINTER_RULE = `1 to ${MAX_POINTER} characters with no whitespace, \`/\`, \`?\`, \`#\`, or control or invisible character`;
|
|
520
|
-
var ObserveShape =
|
|
521
|
-
detector:
|
|
522
|
-
subject:
|
|
523
|
-
after:
|
|
524
|
-
reuse:
|
|
525
|
-
source:
|
|
526
|
-
sessionId:
|
|
527
|
-
entryId:
|
|
528
|
-
openerEntryId:
|
|
529
|
-
runId:
|
|
530
|
-
occurredAtMs:
|
|
569
|
+
var ObserveShape = z9.looseObject({
|
|
570
|
+
detector: z9.looseObject({ id: z9.string(), version: z9.string() }),
|
|
571
|
+
subject: z9.looseObject({ kind: z9.string(), key: z9.string().optional() }).optional(),
|
|
572
|
+
after: z9.looseObject({ observationId: z9.unknown() }).optional(),
|
|
573
|
+
reuse: z9.looseObject({ maxAgeMs: z9.unknown() }).optional(),
|
|
574
|
+
source: z9.looseObject({
|
|
575
|
+
sessionId: z9.string(),
|
|
576
|
+
entryId: z9.string(),
|
|
577
|
+
openerEntryId: z9.string().optional(),
|
|
578
|
+
runId: z9.string().optional(),
|
|
579
|
+
occurredAtMs: z9.unknown().optional()
|
|
531
580
|
}),
|
|
532
|
-
questions:
|
|
581
|
+
questions: z9.record(z9.string(), z9.unknown())
|
|
533
582
|
});
|
|
534
|
-
var ChoiceQuestion =
|
|
535
|
-
type:
|
|
536
|
-
criteria:
|
|
583
|
+
var ChoiceQuestion = z9.looseObject({
|
|
584
|
+
type: z9.literal("choice"),
|
|
585
|
+
criteria: z9.record(z9.string(), z9.unknown())
|
|
537
586
|
});
|
|
538
587
|
var fits = (value, grammar) => value.length <= MAX_NAME && grammar.test(value);
|
|
539
588
|
var isWhole = (value, min) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= SIGNAL_TIME_MAX_MS;
|
|
@@ -613,10 +662,10 @@ function grammarProblem(input) {
|
|
|
613
662
|
}
|
|
614
663
|
return void 0;
|
|
615
664
|
}
|
|
616
|
-
var EchoText =
|
|
617
|
-
var Echo =
|
|
618
|
-
detector:
|
|
619
|
-
source:
|
|
665
|
+
var EchoText = z9.string().catch("");
|
|
666
|
+
var Echo = z9.object({
|
|
667
|
+
detector: z9.object({ id: EchoText, version: EchoText }).catch({ id: "", version: "" }),
|
|
668
|
+
source: z9.object({ sessionId: EchoText, entryId: EchoText }).catch({ sessionId: "", entryId: "" })
|
|
620
669
|
}).catch({
|
|
621
670
|
detector: { id: "", version: "" },
|
|
622
671
|
source: { sessionId: "", entryId: "" }
|
|
@@ -1121,6 +1170,7 @@ var KAY_APP_DATA_CHANGE_MAX_BYTES = 256 * 1024;
|
|
|
1121
1170
|
var KAY_APP_DATA_READ_REQUEST_MAX_BYTES = 64 * 1024;
|
|
1122
1171
|
var KAY_APP_DATA_READ_RESULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
1123
1172
|
export {
|
|
1173
|
+
CorpusResourceInvocationCursor,
|
|
1124
1174
|
JsonSchema,
|
|
1125
1175
|
JsonValue,
|
|
1126
1176
|
KAY_APP_AUTOMATION_CONDITION_KINDS,
|
|
@@ -1140,6 +1190,10 @@ export {
|
|
|
1140
1190
|
PLUGIN_SIGNAL_RELATED_KEYS,
|
|
1141
1191
|
PLUGIN_SIGNAL_SNIPPET_MAX,
|
|
1142
1192
|
PLUGIN_SIGNAL_TITLE_MAX,
|
|
1193
|
+
PluginWorkerError,
|
|
1194
|
+
ProjectDirectory,
|
|
1195
|
+
ProjectDirectoryRequest,
|
|
1196
|
+
ProjectDirectoryResponse,
|
|
1143
1197
|
SIGNAL_TIME_MAX_MS,
|
|
1144
1198
|
STORED_TRANSCRIPT_MESSAGE_FILTER,
|
|
1145
1199
|
STORED_TRANSCRIPT_MESSAGE_KINDS,
|
|
@@ -1157,6 +1211,7 @@ export {
|
|
|
1157
1211
|
createFallbackOnlySignals,
|
|
1158
1212
|
createKayPluginLog,
|
|
1159
1213
|
createUnavailablePluginEndpointCaller,
|
|
1214
|
+
createUnavailablePluginWorkers,
|
|
1160
1215
|
createUnavailableSealing,
|
|
1161
1216
|
createUnavailableSkills,
|
|
1162
1217
|
createUnavailableTools,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** What changed between the texts the card showed and the texts the person sent. */
|
|
2
|
+
export declare function diffDraft(before: DraftTexts, after: DraftTexts): DraftChange;
|
|
3
|
+
|
|
4
|
+
declare interface DiffSegment {
|
|
5
|
+
readonly kind: DiffSegmentKind;
|
|
6
|
+
readonly text: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare type DiffSegmentKind = "same" | "removed" | "added";
|
|
10
|
+
|
|
11
|
+
/** Compare two texts word by word. Callers normalize first; this compares what it is given. */
|
|
12
|
+
export declare function diffWords(before: string, after: string): WordDiff;
|
|
13
|
+
|
|
14
|
+
export declare interface DraftChange {
|
|
15
|
+
/** The normalized subject or body differ. A change in whitespace alone counts. */
|
|
16
|
+
readonly edited: boolean;
|
|
17
|
+
readonly subjectChanged: boolean;
|
|
18
|
+
readonly bodyChanged: boolean;
|
|
19
|
+
/** Words across subject and body; zero for an edit that only moved whitespace. */
|
|
20
|
+
readonly wordsAdded: number;
|
|
21
|
+
readonly wordsRemoved: number;
|
|
22
|
+
/**
|
|
23
|
+
* Words across subject and body of the draft as proposed and of the message as sent, counted the
|
|
24
|
+
* way `wordsRemoved` and `wordsAdded` are. `(beforeWords - wordsRemoved) / beforeWords` is the
|
|
25
|
+
* share of the proposed draft that was kept.
|
|
26
|
+
*/
|
|
27
|
+
readonly beforeWords: number;
|
|
28
|
+
readonly afterWords: number;
|
|
29
|
+
readonly beforeChars: number;
|
|
30
|
+
readonly afterChars: number;
|
|
31
|
+
/**
|
|
32
|
+
* Either part's changed span was too long to align, so it counts as one removed block and one
|
|
33
|
+
* added block: `wordsRemoved` is then an upper bound and the share kept a lower bound.
|
|
34
|
+
*/
|
|
35
|
+
readonly coarse: boolean;
|
|
36
|
+
readonly subjectDiff: WordDiff | undefined;
|
|
37
|
+
readonly bodyDiff: WordDiff | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export declare interface DraftTexts {
|
|
41
|
+
readonly subject: string | undefined;
|
|
42
|
+
readonly body: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Both ends of a text too long to show whole, and how much lay between them. */
|
|
46
|
+
export declare interface HeadAndTail {
|
|
47
|
+
readonly head: string;
|
|
48
|
+
readonly tail: string;
|
|
49
|
+
/** UTF-16 units left out between `head` and `tail`; `0` when the text fit and `head` is all of it. */
|
|
50
|
+
readonly omitted: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The first `headMax` and the last `tailMax` UTF-16 units of `text`, each cut on a character
|
|
55
|
+
* boundary. Text that fits in `headMax + tailMax` comes back whole as `head`, with nothing omitted.
|
|
56
|
+
*
|
|
57
|
+
* For text whose ASK sits at the end (a reply that closes on a question) as well as its framing at
|
|
58
|
+
* the start: a head-only cut keeps the framing and loses the ask.
|
|
59
|
+
*/
|
|
60
|
+
export declare function headAndTailChars(text: string, headMax: number, tailMax: number): HeadAndTail;
|
|
61
|
+
|
|
62
|
+
/** At most `max` UTF-16 units from the start of `text`, never ending inside a surrogate pair. */
|
|
63
|
+
export declare function headChars(text: string, max: number): string;
|
|
64
|
+
|
|
65
|
+
/** The most cells the alignment table may hold (about 2,000 tokens a side, 16 MB of Uint32). */
|
|
66
|
+
export declare const MAX_LCS_CELLS = 4000000;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Line endings, trailing whitespace on each line and trailing blank lines are not edits: a text
|
|
70
|
+
* area and a saved file disagree on them without the person touching a word.
|
|
71
|
+
*/
|
|
72
|
+
export declare function normalizeDraftBody(text: string): string;
|
|
73
|
+
|
|
74
|
+
/** A subject is one line: any whitespace run is one space and the ends are trimmed. */
|
|
75
|
+
export declare function normalizeDraftSubject(subject: string | undefined): string | undefined;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The diff as one marked text: `[-removed-]` and `{+added+}` inside the unchanged words around
|
|
79
|
+
* them, with a long unchanged stretch collapsed to its edges and an ellipsis. The unmarked parts
|
|
80
|
+
* are the original wording, so the result reads as the final text with the edits shown.
|
|
81
|
+
*/
|
|
82
|
+
export declare function renderWordDiff(diff: WordDiff, options?: {
|
|
83
|
+
readonly contextWords?: number;
|
|
84
|
+
readonly maxChars?: number;
|
|
85
|
+
}): {
|
|
86
|
+
readonly text: string;
|
|
87
|
+
readonly truncated: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** The end index of a head that does not end inside a surrogate pair. */
|
|
91
|
+
export declare function safeHeadEnd(text: string, end: number): number;
|
|
92
|
+
|
|
93
|
+
/** The start index of a tail that does not begin inside a surrogate pair. */
|
|
94
|
+
export declare function safeTailStart(text: string, start: number): number;
|
|
95
|
+
|
|
96
|
+
export declare interface WordDiff {
|
|
97
|
+
/** Non-whitespace words present before and gone after. */
|
|
98
|
+
readonly wordsRemoved: number;
|
|
99
|
+
/** Non-whitespace words present after and not before. */
|
|
100
|
+
readonly wordsAdded: number;
|
|
101
|
+
readonly segments: readonly DiffSegment[];
|
|
102
|
+
/** The middle was too large to align, so it is one removed block and one added block. */
|
|
103
|
+
readonly coarse: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { }
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// packages/plugin-sdk/dist/text-clip.js
|
|
2
|
+
function safeTailStart(text, start) {
|
|
3
|
+
const code = text.charCodeAt(start);
|
|
4
|
+
return code >= 56320 && code <= 57343 ? start + 1 : start;
|
|
5
|
+
}
|
|
6
|
+
function safeHeadEnd(text, end) {
|
|
7
|
+
const code = text.charCodeAt(end - 1);
|
|
8
|
+
return end > 0 && code >= 55296 && code <= 56319 ? end - 1 : end;
|
|
9
|
+
}
|
|
10
|
+
function headChars(text, max) {
|
|
11
|
+
return text.length <= max ? text : text.slice(0, safeHeadEnd(text, max));
|
|
12
|
+
}
|
|
13
|
+
function headAndTailChars(text, headMax, tailMax) {
|
|
14
|
+
if (text.length <= headMax + tailMax) {
|
|
15
|
+
return { head: text, tail: "", omitted: 0 };
|
|
16
|
+
}
|
|
17
|
+
const headEnd = safeHeadEnd(text, headMax);
|
|
18
|
+
const tailStart = safeTailStart(text, text.length - tailMax);
|
|
19
|
+
return {
|
|
20
|
+
head: text.slice(0, headEnd),
|
|
21
|
+
tail: text.slice(tailStart),
|
|
22
|
+
omitted: tailStart - headEnd
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// packages/plugin-sdk/dist/text-diff.js
|
|
27
|
+
var MAX_LCS_CELLS = 4e6;
|
|
28
|
+
var isSpaceOrTab = (code) => code === 32 || code === 9;
|
|
29
|
+
var isNewline = (code) => code === 10;
|
|
30
|
+
function trimEndWhile(text, drop) {
|
|
31
|
+
let end = text.length;
|
|
32
|
+
while (end > 0 && drop(text.charCodeAt(end - 1)))
|
|
33
|
+
end -= 1;
|
|
34
|
+
return text.slice(0, end);
|
|
35
|
+
}
|
|
36
|
+
function normalizeDraftBody(text) {
|
|
37
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n").map((line) => trimEndWhile(line, isSpaceOrTab));
|
|
38
|
+
return trimEndWhile(lines.join("\n"), isNewline);
|
|
39
|
+
}
|
|
40
|
+
function normalizeDraftSubject(subject) {
|
|
41
|
+
return subject === void 0 ? void 0 : subject.replace(/\s+/g, " ").trim();
|
|
42
|
+
}
|
|
43
|
+
function tokensOf(text) {
|
|
44
|
+
return text.match(/\s+|\S+/g) ?? [];
|
|
45
|
+
}
|
|
46
|
+
function wordCount(text) {
|
|
47
|
+
return text.split(/\s+/).filter((word) => word !== "").length;
|
|
48
|
+
}
|
|
49
|
+
function alignTokens(a, b) {
|
|
50
|
+
const rows = a.length;
|
|
51
|
+
const cols = b.length;
|
|
52
|
+
if (rows === 0 || cols === 0)
|
|
53
|
+
return [];
|
|
54
|
+
if ((rows + 1) * (cols + 1) > MAX_LCS_CELLS)
|
|
55
|
+
return void 0;
|
|
56
|
+
const width = cols + 1;
|
|
57
|
+
const table = new Uint32Array((rows + 1) * width);
|
|
58
|
+
for (let row2 = rows - 1; row2 >= 0; row2 -= 1) {
|
|
59
|
+
for (let col2 = cols - 1; col2 >= 0; col2 -= 1) {
|
|
60
|
+
table[row2 * width + col2] = a[row2] === b[col2] ? table[(row2 + 1) * width + col2 + 1] + 1 : Math.max(table[(row2 + 1) * width + col2], table[row2 * width + col2 + 1]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const pairs = [];
|
|
64
|
+
let row = 0;
|
|
65
|
+
let col = 0;
|
|
66
|
+
while (row < rows && col < cols) {
|
|
67
|
+
if (a[row] === b[col]) {
|
|
68
|
+
pairs.push([row, col]);
|
|
69
|
+
row += 1;
|
|
70
|
+
col += 1;
|
|
71
|
+
} else if (table[(row + 1) * width + col] >= table[row * width + col + 1]) {
|
|
72
|
+
row += 1;
|
|
73
|
+
} else {
|
|
74
|
+
col += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return pairs;
|
|
78
|
+
}
|
|
79
|
+
function pushSegment(segments, kind, text) {
|
|
80
|
+
if (text === "")
|
|
81
|
+
return;
|
|
82
|
+
const last = segments.at(-1);
|
|
83
|
+
if (last?.kind === kind) {
|
|
84
|
+
segments[segments.length - 1] = { kind, text: last.text + text };
|
|
85
|
+
} else {
|
|
86
|
+
segments.push({ kind, text });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function diffWords(before, after) {
|
|
90
|
+
const a = tokensOf(before);
|
|
91
|
+
const b = tokensOf(after);
|
|
92
|
+
let start = 0;
|
|
93
|
+
while (start < a.length && start < b.length && a[start] === b[start]) {
|
|
94
|
+
start += 1;
|
|
95
|
+
}
|
|
96
|
+
let endA = a.length;
|
|
97
|
+
let endB = b.length;
|
|
98
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
99
|
+
endA -= 1;
|
|
100
|
+
endB -= 1;
|
|
101
|
+
}
|
|
102
|
+
const middleA = a.slice(start, endA);
|
|
103
|
+
const middleB = b.slice(start, endB);
|
|
104
|
+
const pairs = alignTokens(middleA, middleB);
|
|
105
|
+
const segments = [];
|
|
106
|
+
pushSegment(segments, "same", a.slice(0, start).join(""));
|
|
107
|
+
if (pairs === void 0) {
|
|
108
|
+
pushSegment(segments, "removed", middleA.join(""));
|
|
109
|
+
pushSegment(segments, "added", middleB.join(""));
|
|
110
|
+
} else {
|
|
111
|
+
let row = 0;
|
|
112
|
+
let col = 0;
|
|
113
|
+
for (const [matchedRow, matchedCol] of pairs) {
|
|
114
|
+
pushSegment(segments, "removed", middleA.slice(row, matchedRow).join(""));
|
|
115
|
+
pushSegment(segments, "added", middleB.slice(col, matchedCol).join(""));
|
|
116
|
+
pushSegment(segments, "same", middleA[matchedRow]);
|
|
117
|
+
row = matchedRow + 1;
|
|
118
|
+
col = matchedCol + 1;
|
|
119
|
+
}
|
|
120
|
+
pushSegment(segments, "removed", middleA.slice(row).join(""));
|
|
121
|
+
pushSegment(segments, "added", middleB.slice(col).join(""));
|
|
122
|
+
}
|
|
123
|
+
pushSegment(segments, "same", a.slice(endA).join(""));
|
|
124
|
+
let wordsRemoved = 0;
|
|
125
|
+
let wordsAdded = 0;
|
|
126
|
+
for (const segment of segments) {
|
|
127
|
+
if (segment.kind === "removed")
|
|
128
|
+
wordsRemoved += wordCount(segment.text);
|
|
129
|
+
if (segment.kind === "added")
|
|
130
|
+
wordsAdded += wordCount(segment.text);
|
|
131
|
+
}
|
|
132
|
+
return { wordsRemoved, wordsAdded, segments, coarse: pairs === void 0 };
|
|
133
|
+
}
|
|
134
|
+
function diffDraft(before, after) {
|
|
135
|
+
const beforeBody = normalizeDraftBody(before.body);
|
|
136
|
+
const afterBody = normalizeDraftBody(after.body);
|
|
137
|
+
const beforeSubject = normalizeDraftSubject(before.subject);
|
|
138
|
+
const afterSubject = normalizeDraftSubject(after.subject);
|
|
139
|
+
const subjectChanged = (beforeSubject ?? "") !== (afterSubject ?? "");
|
|
140
|
+
const bodyChanged = beforeBody !== afterBody;
|
|
141
|
+
const subjectDiff = subjectChanged ? diffWords(beforeSubject ?? "", afterSubject ?? "") : void 0;
|
|
142
|
+
const bodyDiff = bodyChanged ? diffWords(beforeBody, afterBody) : void 0;
|
|
143
|
+
return {
|
|
144
|
+
edited: subjectChanged || bodyChanged,
|
|
145
|
+
subjectChanged,
|
|
146
|
+
bodyChanged,
|
|
147
|
+
wordsAdded: (subjectDiff?.wordsAdded ?? 0) + (bodyDiff?.wordsAdded ?? 0),
|
|
148
|
+
wordsRemoved: (subjectDiff?.wordsRemoved ?? 0) + (bodyDiff?.wordsRemoved ?? 0),
|
|
149
|
+
beforeWords: wordCount(beforeSubject ?? "") + wordCount(beforeBody),
|
|
150
|
+
afterWords: wordCount(afterSubject ?? "") + wordCount(afterBody),
|
|
151
|
+
beforeChars: (beforeSubject ?? "").length + beforeBody.length,
|
|
152
|
+
afterChars: (afterSubject ?? "").length + afterBody.length,
|
|
153
|
+
coarse: subjectDiff?.coarse === true || bodyDiff?.coarse === true,
|
|
154
|
+
subjectDiff,
|
|
155
|
+
bodyDiff
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function visible(text) {
|
|
159
|
+
if (text.trim() === "") {
|
|
160
|
+
return text.replace(/\n/g, "\u23CE").replace(/\t/g, "\u21E5").replace(/ /g, "\u2423");
|
|
161
|
+
}
|
|
162
|
+
return text.replace(/\n/g, "\u23CE\n");
|
|
163
|
+
}
|
|
164
|
+
function wordSpans(text) {
|
|
165
|
+
return [...text.matchAll(/\S+/g)].map((match) => ({
|
|
166
|
+
start: match.index ?? 0,
|
|
167
|
+
end: (match.index ?? 0) + match[0].length
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function contextOf(text, words, where) {
|
|
171
|
+
const spans = wordSpans(text);
|
|
172
|
+
if (spans.length <= words)
|
|
173
|
+
return { text, cut: false };
|
|
174
|
+
return where === "head" ? { text: text.slice(0, spans[words - 1].end), cut: true } : { text: text.slice(spans[spans.length - words].start), cut: true };
|
|
175
|
+
}
|
|
176
|
+
function renderWordDiff(diff, options = {}) {
|
|
177
|
+
const contextWords = options.contextWords ?? 8;
|
|
178
|
+
const maxChars = options.maxChars ?? 3e3;
|
|
179
|
+
if (diff.segments.every((segment) => segment.kind === "same")) {
|
|
180
|
+
return {
|
|
181
|
+
text: "(no word changed; only whitespace differs)",
|
|
182
|
+
truncated: false
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const lastIndex = diff.segments.length - 1;
|
|
186
|
+
const parts = diff.segments.map((segment, index) => {
|
|
187
|
+
if (segment.kind === "removed")
|
|
188
|
+
return `[-${visible(segment.text)}-]`;
|
|
189
|
+
if (segment.kind === "added")
|
|
190
|
+
return `{+${visible(segment.text)}+}`;
|
|
191
|
+
const words = wordSpans(segment.text).length;
|
|
192
|
+
if (index === 0) {
|
|
193
|
+
const tail2 = contextOf(segment.text, contextWords, "tail");
|
|
194
|
+
return tail2.cut ? `\u2026 ${tail2.text}` : tail2.text;
|
|
195
|
+
}
|
|
196
|
+
if (index === lastIndex) {
|
|
197
|
+
const head2 = contextOf(segment.text, contextWords, "head");
|
|
198
|
+
return head2.cut ? `${head2.text} \u2026` : head2.text;
|
|
199
|
+
}
|
|
200
|
+
if (words <= contextWords * 2)
|
|
201
|
+
return segment.text;
|
|
202
|
+
const head = contextOf(segment.text, contextWords, "head").text;
|
|
203
|
+
const tail = contextOf(segment.text, contextWords, "tail").text;
|
|
204
|
+
return `${head} \u2026 ${tail}`;
|
|
205
|
+
});
|
|
206
|
+
const rendered = parts.join("");
|
|
207
|
+
return rendered.length <= maxChars ? { text: rendered, truncated: false } : {
|
|
208
|
+
text: `${headChars(rendered, maxChars)}\u2026`,
|
|
209
|
+
truncated: true
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
MAX_LCS_CELLS,
|
|
214
|
+
diffDraft,
|
|
215
|
+
diffWords,
|
|
216
|
+
headAndTailChars,
|
|
217
|
+
headChars,
|
|
218
|
+
normalizeDraftBody,
|
|
219
|
+
normalizeDraftSubject,
|
|
220
|
+
renderWordDiff,
|
|
221
|
+
safeHeadEnd,
|
|
222
|
+
safeTailStart
|
|
223
|
+
};
|