@getpaseo/protocol 0.3.0-beta.4 → 0.3.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/dist/generated/validation/ws-outbound.aot.js +24521 -24362
- package/dist/messages.d.ts +134 -0
- package/dist/messages.js +26 -0
- package/dist/search/text-match.d.ts +55 -0
- package/dist/search/text-match.js +262 -0
- package/dist/validation/ws-outbound-schema-metadata.d.ts +27 -0
- package/package.json +1 -1
package/dist/messages.d.ts
CHANGED
|
@@ -979,6 +979,7 @@ export declare const FetchAgentHistoryRequestMessageSchema: z.ZodObject<{
|
|
|
979
979
|
requiresAttention: z.ZodOptional<z.ZodBoolean>;
|
|
980
980
|
thinkingOptionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
981
981
|
}, z.core.$strip>>;
|
|
982
|
+
search: z.ZodOptional<z.ZodString>;
|
|
982
983
|
sort: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
983
984
|
key: z.ZodEnum<{
|
|
984
985
|
title: "title";
|
|
@@ -3286,6 +3287,7 @@ export declare const SessionInboundMessageSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
3286
3287
|
requiresAttention: z.ZodOptional<z.ZodBoolean>;
|
|
3287
3288
|
thinkingOptionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
3288
3289
|
}, z.core.$strip>>;
|
|
3290
|
+
search: z.ZodOptional<z.ZodString>;
|
|
3289
3291
|
sort: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
3290
3292
|
key: z.ZodEnum<{
|
|
3291
3293
|
title: "title";
|
|
@@ -5070,6 +5072,7 @@ export declare const ServerInfoStatusPayloadSchema: z.ZodPipe<z.ZodObject<{
|
|
|
5070
5072
|
"terminal-size-ownership": z.ZodOptional<z.ZodBoolean>;
|
|
5071
5073
|
rewind: z.ZodOptional<z.ZodBoolean>;
|
|
5072
5074
|
agentTimelinePromptIndex: z.ZodOptional<z.ZodBoolean>;
|
|
5075
|
+
agentHistorySearch: z.ZodOptional<z.ZodBoolean>;
|
|
5073
5076
|
checkoutRefresh: z.ZodOptional<z.ZodBoolean>;
|
|
5074
5077
|
workspaceMultiplicity: z.ZodOptional<z.ZodBoolean>;
|
|
5075
5078
|
projectRemove: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -5136,6 +5139,7 @@ export declare const ServerInfoStatusPayloadSchema: z.ZodPipe<z.ZodObject<{
|
|
|
5136
5139
|
"terminal-size-ownership"?: boolean | undefined;
|
|
5137
5140
|
rewind?: boolean | undefined;
|
|
5138
5141
|
agentTimelinePromptIndex?: boolean | undefined;
|
|
5142
|
+
agentHistorySearch?: boolean | undefined;
|
|
5139
5143
|
checkoutRefresh?: boolean | undefined;
|
|
5140
5144
|
workspaceMultiplicity?: boolean | undefined;
|
|
5141
5145
|
projectRemove?: boolean | undefined;
|
|
@@ -5203,6 +5207,7 @@ export declare const ServerInfoStatusPayloadSchema: z.ZodPipe<z.ZodObject<{
|
|
|
5203
5207
|
"terminal-size-ownership"?: boolean | undefined;
|
|
5204
5208
|
rewind?: boolean | undefined;
|
|
5205
5209
|
agentTimelinePromptIndex?: boolean | undefined;
|
|
5210
|
+
agentHistorySearch?: boolean | undefined;
|
|
5206
5211
|
checkoutRefresh?: boolean | undefined;
|
|
5207
5212
|
workspaceMultiplicity?: boolean | undefined;
|
|
5208
5213
|
projectRemove?: boolean | undefined;
|
|
@@ -6724,6 +6729,25 @@ export declare const AgentListMessageSchema: z.ZodObject<{
|
|
|
6724
6729
|
}, z.core.$strip>>;
|
|
6725
6730
|
}, z.core.$strip>;
|
|
6726
6731
|
}, z.core.$strip>;
|
|
6732
|
+
export declare const AgentSearchMatchFieldSchema: z.ZodEnum<{
|
|
6733
|
+
title: "title";
|
|
6734
|
+
branch: "branch";
|
|
6735
|
+
workspace: "workspace";
|
|
6736
|
+
project: "project";
|
|
6737
|
+
}>;
|
|
6738
|
+
export declare const AgentSearchMatchSchema: z.ZodObject<{
|
|
6739
|
+
field: z.ZodEnum<{
|
|
6740
|
+
title: "title";
|
|
6741
|
+
branch: "branch";
|
|
6742
|
+
workspace: "workspace";
|
|
6743
|
+
project: "project";
|
|
6744
|
+
}>;
|
|
6745
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
6746
|
+
start: z.ZodNumber;
|
|
6747
|
+
length: z.ZodNumber;
|
|
6748
|
+
}, z.core.$strip>>;
|
|
6749
|
+
}, z.core.$strip>;
|
|
6750
|
+
export type AgentSearchMatch = z.infer<typeof AgentSearchMatchSchema>;
|
|
6727
6751
|
export declare const FetchAgentsResponseMessageSchema: z.ZodObject<{
|
|
6728
6752
|
type: z.ZodLiteral<"fetch_agents_response">;
|
|
6729
6753
|
payload: z.ZodObject<{
|
|
@@ -6874,6 +6898,19 @@ export declare const FetchAgentsResponseMessageSchema: z.ZodObject<{
|
|
|
6874
6898
|
worktreeRoot?: string | undefined;
|
|
6875
6899
|
}>>]>;
|
|
6876
6900
|
}, z.core.$strip>;
|
|
6901
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
6902
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
6903
|
+
field: z.ZodEnum<{
|
|
6904
|
+
title: "title";
|
|
6905
|
+
branch: "branch";
|
|
6906
|
+
workspace: "workspace";
|
|
6907
|
+
project: "project";
|
|
6908
|
+
}>;
|
|
6909
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
6910
|
+
start: z.ZodNumber;
|
|
6911
|
+
length: z.ZodNumber;
|
|
6912
|
+
}, z.core.$strip>>;
|
|
6913
|
+
}, z.core.$strip>>>;
|
|
6877
6914
|
}, z.core.$strip>>;
|
|
6878
6915
|
pageInfo: z.ZodObject<{
|
|
6879
6916
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -7031,12 +7068,26 @@ export declare const FetchAgentHistoryResponseMessageSchema: z.ZodObject<{
|
|
|
7031
7068
|
worktreeRoot?: string | undefined;
|
|
7032
7069
|
}>>]>;
|
|
7033
7070
|
}, z.core.$strip>;
|
|
7071
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
7072
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
7073
|
+
field: z.ZodEnum<{
|
|
7074
|
+
title: "title";
|
|
7075
|
+
branch: "branch";
|
|
7076
|
+
workspace: "workspace";
|
|
7077
|
+
project: "project";
|
|
7078
|
+
}>;
|
|
7079
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
7080
|
+
start: z.ZodNumber;
|
|
7081
|
+
length: z.ZodNumber;
|
|
7082
|
+
}, z.core.$strip>>;
|
|
7083
|
+
}, z.core.$strip>>>;
|
|
7034
7084
|
}, z.core.$strip>>;
|
|
7035
7085
|
pageInfo: z.ZodObject<{
|
|
7036
7086
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
7037
7087
|
prevCursor: z.ZodNullable<z.ZodString>;
|
|
7038
7088
|
hasMore: z.ZodBoolean;
|
|
7039
7089
|
}, z.core.$strip>;
|
|
7090
|
+
searchTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
7040
7091
|
}, z.core.$strip>;
|
|
7041
7092
|
}, z.core.$strip>;
|
|
7042
7093
|
export declare const FetchRecentProviderSessionsResponseMessageSchema: z.ZodObject<{
|
|
@@ -15043,6 +15094,19 @@ export declare const SessionOutboundMessageSchema: z.ZodDiscriminatedUnion<[z.Zo
|
|
|
15043
15094
|
worktreeRoot?: string | undefined;
|
|
15044
15095
|
}>>]>;
|
|
15045
15096
|
}, z.core.$strip>;
|
|
15097
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
15098
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
15099
|
+
field: z.ZodEnum<{
|
|
15100
|
+
title: "title";
|
|
15101
|
+
branch: "branch";
|
|
15102
|
+
workspace: "workspace";
|
|
15103
|
+
project: "project";
|
|
15104
|
+
}>;
|
|
15105
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
15106
|
+
start: z.ZodNumber;
|
|
15107
|
+
length: z.ZodNumber;
|
|
15108
|
+
}, z.core.$strip>>;
|
|
15109
|
+
}, z.core.$strip>>>;
|
|
15046
15110
|
}, z.core.$strip>>;
|
|
15047
15111
|
pageInfo: z.ZodObject<{
|
|
15048
15112
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -15199,12 +15263,26 @@ export declare const SessionOutboundMessageSchema: z.ZodDiscriminatedUnion<[z.Zo
|
|
|
15199
15263
|
worktreeRoot?: string | undefined;
|
|
15200
15264
|
}>>]>;
|
|
15201
15265
|
}, z.core.$strip>;
|
|
15266
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
15267
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
15268
|
+
field: z.ZodEnum<{
|
|
15269
|
+
title: "title";
|
|
15270
|
+
branch: "branch";
|
|
15271
|
+
workspace: "workspace";
|
|
15272
|
+
project: "project";
|
|
15273
|
+
}>;
|
|
15274
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
15275
|
+
start: z.ZodNumber;
|
|
15276
|
+
length: z.ZodNumber;
|
|
15277
|
+
}, z.core.$strip>>;
|
|
15278
|
+
}, z.core.$strip>>>;
|
|
15202
15279
|
}, z.core.$strip>>;
|
|
15203
15280
|
pageInfo: z.ZodObject<{
|
|
15204
15281
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
15205
15282
|
prevCursor: z.ZodNullable<z.ZodString>;
|
|
15206
15283
|
hasMore: z.ZodBoolean;
|
|
15207
15284
|
}, z.core.$strip>;
|
|
15285
|
+
searchTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
15208
15286
|
}, z.core.$strip>;
|
|
15209
15287
|
}, z.core.$strip>, z.ZodObject<{
|
|
15210
15288
|
type: z.ZodLiteral<"fetch_recent_provider_sessions_response">;
|
|
@@ -21816,6 +21894,7 @@ export declare const WSSessionInboundSchema: z.ZodObject<{
|
|
|
21816
21894
|
requiresAttention: z.ZodOptional<z.ZodBoolean>;
|
|
21817
21895
|
thinkingOptionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21818
21896
|
}, z.core.$strip>>;
|
|
21897
|
+
search: z.ZodOptional<z.ZodString>;
|
|
21819
21898
|
sort: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21820
21899
|
key: z.ZodEnum<{
|
|
21821
21900
|
title: "title";
|
|
@@ -24950,6 +25029,19 @@ export declare const WSSessionOutboundSchema: z.ZodObject<{
|
|
|
24950
25029
|
worktreeRoot?: string | undefined;
|
|
24951
25030
|
}>>]>;
|
|
24952
25031
|
}, z.core.$strip>;
|
|
25032
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
25033
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
25034
|
+
field: z.ZodEnum<{
|
|
25035
|
+
title: "title";
|
|
25036
|
+
branch: "branch";
|
|
25037
|
+
workspace: "workspace";
|
|
25038
|
+
project: "project";
|
|
25039
|
+
}>;
|
|
25040
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
25041
|
+
start: z.ZodNumber;
|
|
25042
|
+
length: z.ZodNumber;
|
|
25043
|
+
}, z.core.$strip>>;
|
|
25044
|
+
}, z.core.$strip>>>;
|
|
24953
25045
|
}, z.core.$strip>>;
|
|
24954
25046
|
pageInfo: z.ZodObject<{
|
|
24955
25047
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -25106,12 +25198,26 @@ export declare const WSSessionOutboundSchema: z.ZodObject<{
|
|
|
25106
25198
|
worktreeRoot?: string | undefined;
|
|
25107
25199
|
}>>]>;
|
|
25108
25200
|
}, z.core.$strip>;
|
|
25201
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
25202
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
25203
|
+
field: z.ZodEnum<{
|
|
25204
|
+
title: "title";
|
|
25205
|
+
branch: "branch";
|
|
25206
|
+
workspace: "workspace";
|
|
25207
|
+
project: "project";
|
|
25208
|
+
}>;
|
|
25209
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
25210
|
+
start: z.ZodNumber;
|
|
25211
|
+
length: z.ZodNumber;
|
|
25212
|
+
}, z.core.$strip>>;
|
|
25213
|
+
}, z.core.$strip>>>;
|
|
25109
25214
|
}, z.core.$strip>>;
|
|
25110
25215
|
pageInfo: z.ZodObject<{
|
|
25111
25216
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
25112
25217
|
prevCursor: z.ZodNullable<z.ZodString>;
|
|
25113
25218
|
hasMore: z.ZodBoolean;
|
|
25114
25219
|
}, z.core.$strip>;
|
|
25220
|
+
searchTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
25115
25221
|
}, z.core.$strip>;
|
|
25116
25222
|
}, z.core.$strip>, z.ZodObject<{
|
|
25117
25223
|
type: z.ZodLiteral<"fetch_recent_provider_sessions_response">;
|
|
@@ -31385,6 +31491,7 @@ export declare const WSInboundMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
|
|
|
31385
31491
|
requiresAttention: z.ZodOptional<z.ZodBoolean>;
|
|
31386
31492
|
thinkingOptionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31387
31493
|
}, z.core.$strip>>;
|
|
31494
|
+
search: z.ZodOptional<z.ZodString>;
|
|
31388
31495
|
sort: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
31389
31496
|
key: z.ZodEnum<{
|
|
31390
31497
|
title: "title";
|
|
@@ -34521,6 +34628,19 @@ export declare const WSOutboundMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
34521
34628
|
worktreeRoot?: string | undefined;
|
|
34522
34629
|
}>>]>;
|
|
34523
34630
|
}, z.core.$strip>;
|
|
34631
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
34632
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34633
|
+
field: z.ZodEnum<{
|
|
34634
|
+
title: "title";
|
|
34635
|
+
branch: "branch";
|
|
34636
|
+
workspace: "workspace";
|
|
34637
|
+
project: "project";
|
|
34638
|
+
}>;
|
|
34639
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
34640
|
+
start: z.ZodNumber;
|
|
34641
|
+
length: z.ZodNumber;
|
|
34642
|
+
}, z.core.$strip>>;
|
|
34643
|
+
}, z.core.$strip>>>;
|
|
34524
34644
|
}, z.core.$strip>>;
|
|
34525
34645
|
pageInfo: z.ZodObject<{
|
|
34526
34646
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -34677,12 +34797,26 @@ export declare const WSOutboundMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
34677
34797
|
worktreeRoot?: string | undefined;
|
|
34678
34798
|
}>>]>;
|
|
34679
34799
|
}, z.core.$strip>;
|
|
34800
|
+
searchScore: z.ZodOptional<z.ZodNumber>;
|
|
34801
|
+
searchMatches: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34802
|
+
field: z.ZodEnum<{
|
|
34803
|
+
title: "title";
|
|
34804
|
+
branch: "branch";
|
|
34805
|
+
workspace: "workspace";
|
|
34806
|
+
project: "project";
|
|
34807
|
+
}>;
|
|
34808
|
+
ranges: z.ZodArray<z.ZodObject<{
|
|
34809
|
+
start: z.ZodNumber;
|
|
34810
|
+
length: z.ZodNumber;
|
|
34811
|
+
}, z.core.$strip>>;
|
|
34812
|
+
}, z.core.$strip>>>;
|
|
34680
34813
|
}, z.core.$strip>>;
|
|
34681
34814
|
pageInfo: z.ZodObject<{
|
|
34682
34815
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
34683
34816
|
prevCursor: z.ZodNullable<z.ZodString>;
|
|
34684
34817
|
hasMore: z.ZodBoolean;
|
|
34685
34818
|
}, z.core.$strip>;
|
|
34819
|
+
searchTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
34686
34820
|
}, z.core.$strip>;
|
|
34687
34821
|
}, z.core.$strip>, z.ZodObject<{
|
|
34688
34822
|
type: z.ZodLiteral<"fetch_recent_provider_sessions_response">;
|
package/dist/messages.js
CHANGED
|
@@ -927,6 +927,10 @@ export const FetchAgentHistoryRequestMessageSchema = z.object({
|
|
|
927
927
|
type: z.literal("fetch_agent_history_request"),
|
|
928
928
|
requestId: z.string(),
|
|
929
929
|
filter: AgentDirectoryFilterSchema.optional(),
|
|
930
|
+
// A ranked free-text query over agent title, workspace name, branch, and
|
|
931
|
+
// project name. Present only on history: agent subscriptions filter on
|
|
932
|
+
// structure, not on relevance. Ranking replaces `sort` when it is set.
|
|
933
|
+
search: z.string().optional(),
|
|
930
934
|
sort: z
|
|
931
935
|
.array(z.object({
|
|
932
936
|
key: z.enum(["status_priority", "created_at", "updated_at", "title"]),
|
|
@@ -2464,6 +2468,8 @@ export const ServerInfoStatusPayloadSchema = z
|
|
|
2464
2468
|
rewind: z.boolean().optional(),
|
|
2465
2469
|
// COMPAT(agentTimelinePromptIndex): added in v0.2.X, drop the gate when floor >= v0.2.X.
|
|
2466
2470
|
agentTimelinePromptIndex: z.boolean().optional(),
|
|
2471
|
+
// COMPAT(agentHistorySearch): added in v0.3.0, remove gate after 2027-02-07.
|
|
2472
|
+
agentHistorySearch: z.boolean().optional(),
|
|
2467
2473
|
// COMPAT(checkoutRefresh): added in v0.1.86, remove gate after 2026-11-29.
|
|
2468
2474
|
checkoutRefresh: z.boolean().optional(),
|
|
2469
2475
|
// COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
|
|
@@ -2858,9 +2864,25 @@ export const AgentListMessageSchema = z.object({
|
|
|
2858
2864
|
agents: z.array(AgentSnapshotPayloadSchema),
|
|
2859
2865
|
}),
|
|
2860
2866
|
});
|
|
2867
|
+
export const AgentSearchMatchFieldSchema = z.enum(["workspace", "title", "branch", "project"]);
|
|
2868
|
+
export const AgentSearchMatchSchema = z.object({
|
|
2869
|
+
field: AgentSearchMatchFieldSchema,
|
|
2870
|
+
ranges: z.array(z.object({
|
|
2871
|
+
start: z.number().int().nonnegative(),
|
|
2872
|
+
length: z.number().int().positive(),
|
|
2873
|
+
})),
|
|
2874
|
+
});
|
|
2861
2875
|
const AgentDirectoryResponseEntrySchema = z.object({
|
|
2862
2876
|
agent: AgentSnapshotPayloadSchema,
|
|
2863
2877
|
project: ProjectPlacementPayloadSchema,
|
|
2878
|
+
// Relevance of this entry to the request's `search`, lower being better.
|
|
2879
|
+
// Set only when the request carried a query; a client merging results from
|
|
2880
|
+
// several hosts needs it to interleave their separately ranked pages.
|
|
2881
|
+
searchScore: z.number().optional(),
|
|
2882
|
+
// Where the query matched, so the row can mark it. The ranker computes this
|
|
2883
|
+
// anyway; sending it keeps the client from re-deriving a second opinion that
|
|
2884
|
+
// could disagree with the ranking it is explaining.
|
|
2885
|
+
searchMatches: z.array(AgentSearchMatchSchema).optional(),
|
|
2864
2886
|
});
|
|
2865
2887
|
const AgentDirectoryPageInfoSchema = z.object({
|
|
2866
2888
|
nextCursor: z.string().nullable(),
|
|
@@ -2882,6 +2904,10 @@ export const FetchAgentHistoryResponseMessageSchema = z.object({
|
|
|
2882
2904
|
requestId: z.string(),
|
|
2883
2905
|
entries: z.array(AgentDirectoryResponseEntrySchema),
|
|
2884
2906
|
pageInfo: AgentDirectoryPageInfoSchema,
|
|
2907
|
+
// More sessions matched the request's `search` than the page could hold.
|
|
2908
|
+
// Distinct from `pageInfo.hasMore`, which promises a fetchable next page —
|
|
2909
|
+
// a ranked result set has none, and the way on is a narrower query.
|
|
2910
|
+
searchTruncated: z.boolean().optional(),
|
|
2885
2911
|
}),
|
|
2886
2912
|
});
|
|
2887
2913
|
export const FetchRecentProviderSessionsResponseMessageSchema = z.object({
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranked text matching shared by the app's pickers and the daemon's history
|
|
3
|
+
* search. A match is a tier plus the offset it was found at; lower is better on
|
|
4
|
+
* both, so callers sort ascending and never have to invent a scale.
|
|
5
|
+
*
|
|
6
|
+
* Typo tolerance is opt-in via `fuzzy`. The pickers leave it off — a combobox
|
|
7
|
+
* over a known list wants exact narrowing — while history search turns it on
|
|
8
|
+
* because the user is recalling a title from memory.
|
|
9
|
+
*/
|
|
10
|
+
export interface MatchScore {
|
|
11
|
+
tier: number;
|
|
12
|
+
offset: number;
|
|
13
|
+
spread?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface MatchOptions {
|
|
16
|
+
/** Omit or pass null to match exactly. `fuzzyPolicyForToken` picks a policy. */
|
|
17
|
+
fuzzy?: FuzzyPolicy | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* How much a typo in one query token is forgiven. Short tokens get
|
|
21
|
+
* transpositions and nothing else: at four characters a free substitution turns
|
|
22
|
+
* "main" into "mail", "maid", and "rain", while a swap can only ever reach the
|
|
23
|
+
* word the user meant. Null means the token is matched exactly.
|
|
24
|
+
*/
|
|
25
|
+
export interface FuzzyPolicy {
|
|
26
|
+
maxEdits: number;
|
|
27
|
+
transpositionsOnly: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function fuzzyPolicyForToken(token: string): FuzzyPolicy | null;
|
|
30
|
+
export declare function scoreMatch(query: string, text: string, options?: MatchOptions): MatchScore | null;
|
|
31
|
+
export interface MatchRange {
|
|
32
|
+
start: number;
|
|
33
|
+
length: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Where a score's match actually landed, so a caller can mark it. Derived from
|
|
37
|
+
* a score rather than produced alongside one: ranking touches every candidate
|
|
38
|
+
* and needs no ranges, while only the handful of rows that get rendered do.
|
|
39
|
+
*
|
|
40
|
+
* The tier decides the shape. A substring hit is one span; a subsequence hit is
|
|
41
|
+
* the scattered characters it walked; a typo hit marks the whole word, because
|
|
42
|
+
* the characters the user got wrong are not in the text to point at.
|
|
43
|
+
*/
|
|
44
|
+
export declare function matchRanges(query: string, text: string, score: MatchScore): MatchRange[];
|
|
45
|
+
export declare function compareMatchScores(a: MatchScore, b: MatchScore): number;
|
|
46
|
+
export declare function tokenizeQuery(query: string): string[];
|
|
47
|
+
export interface TextFieldsOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Forgive typos. The budget is per token rather than per query, because a
|
|
50
|
+
* query mixes long words that can absorb an edit with short ones that cannot.
|
|
51
|
+
*/
|
|
52
|
+
typoTolerant?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export declare function scoreTextFields(query: string, fields: string[], options?: TextFieldsOptions): MatchScore | null;
|
|
55
|
+
//# sourceMappingURL=text-match.d.ts.map
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranked text matching shared by the app's pickers and the daemon's history
|
|
3
|
+
* search. A match is a tier plus the offset it was found at; lower is better on
|
|
4
|
+
* both, so callers sort ascending and never have to invent a scale.
|
|
5
|
+
*
|
|
6
|
+
* Typo tolerance is opt-in via `fuzzy`. The pickers leave it off — a combobox
|
|
7
|
+
* over a known list wants exact narrowing — while history search turns it on
|
|
8
|
+
* because the user is recalling a title from memory.
|
|
9
|
+
*/
|
|
10
|
+
/** Exact tiers, best to worst. The fuzzy tier always sorts after all of them. */
|
|
11
|
+
const TIER_EXACT = 0;
|
|
12
|
+
const TIER_WHOLE_WORD = 1;
|
|
13
|
+
const TIER_PREFIX = 2;
|
|
14
|
+
const TIER_WORD_START = 3;
|
|
15
|
+
const TIER_SUBSTRING = 4;
|
|
16
|
+
const TIER_SUBSEQUENCE = 5;
|
|
17
|
+
const TIER_FUZZY = 6;
|
|
18
|
+
function isWordBoundaryChar(ch) {
|
|
19
|
+
if (ch === undefined)
|
|
20
|
+
return true;
|
|
21
|
+
return !/[a-z0-9]/.test(ch);
|
|
22
|
+
}
|
|
23
|
+
function scoreSubstringMatch(query, text) {
|
|
24
|
+
let best = null;
|
|
25
|
+
let pos = 0;
|
|
26
|
+
while (pos <= text.length - query.length) {
|
|
27
|
+
const found = text.indexOf(query, pos);
|
|
28
|
+
if (found === -1)
|
|
29
|
+
break;
|
|
30
|
+
const before = found > 0 ? text[found - 1] : undefined;
|
|
31
|
+
const after = text[found + query.length];
|
|
32
|
+
const startsAtBoundary = found === 0 || isWordBoundaryChar(before);
|
|
33
|
+
const endsAtBoundary = after === undefined || isWordBoundaryChar(after);
|
|
34
|
+
let tier;
|
|
35
|
+
if (startsAtBoundary && endsAtBoundary) {
|
|
36
|
+
tier = TIER_WHOLE_WORD;
|
|
37
|
+
}
|
|
38
|
+
else if (found === 0) {
|
|
39
|
+
tier = TIER_PREFIX;
|
|
40
|
+
}
|
|
41
|
+
else if (startsAtBoundary) {
|
|
42
|
+
tier = TIER_WORD_START;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
tier = TIER_SUBSTRING;
|
|
46
|
+
}
|
|
47
|
+
if (!best || tier < best.tier || (tier === best.tier && found < best.offset)) {
|
|
48
|
+
best = { tier, offset: found };
|
|
49
|
+
}
|
|
50
|
+
pos = found + 1;
|
|
51
|
+
}
|
|
52
|
+
return best;
|
|
53
|
+
}
|
|
54
|
+
function scoreSubsequenceMatch(query, text) {
|
|
55
|
+
let queryIndex = 0;
|
|
56
|
+
let firstIndex = -1;
|
|
57
|
+
let lastIndex = -1;
|
|
58
|
+
for (let textIndex = 0; textIndex < text.length && queryIndex < query.length; textIndex += 1) {
|
|
59
|
+
if (text[textIndex] !== query[queryIndex])
|
|
60
|
+
continue;
|
|
61
|
+
if (firstIndex === -1)
|
|
62
|
+
firstIndex = textIndex;
|
|
63
|
+
lastIndex = textIndex;
|
|
64
|
+
queryIndex += 1;
|
|
65
|
+
}
|
|
66
|
+
if (queryIndex !== query.length || firstIndex === -1)
|
|
67
|
+
return null;
|
|
68
|
+
return { tier: TIER_SUBSEQUENCE, offset: firstIndex, spread: lastIndex - firstIndex + 1 };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Damerau-Levenshtein distance, abandoned as soon as every cell in a row is
|
|
72
|
+
* over budget. Bounding it is what keeps the fuzzy tier affordable to run
|
|
73
|
+
* against every word of every candidate.
|
|
74
|
+
*/
|
|
75
|
+
function boundedEditDistance(query, word, budget) {
|
|
76
|
+
if (Math.abs(query.length - word.length) > budget)
|
|
77
|
+
return null;
|
|
78
|
+
let twoRowsBack = [];
|
|
79
|
+
let previousRow = Array.from({ length: word.length + 1 }, (_, index) => index);
|
|
80
|
+
for (let queryIndex = 1; queryIndex <= query.length; queryIndex += 1) {
|
|
81
|
+
const currentRow = [queryIndex];
|
|
82
|
+
let rowBest = queryIndex;
|
|
83
|
+
for (let wordIndex = 1; wordIndex <= word.length; wordIndex += 1) {
|
|
84
|
+
const substitutionCost = query[queryIndex - 1] === word[wordIndex - 1] ? 0 : 1;
|
|
85
|
+
let cost = Math.min(currentRow[wordIndex - 1] + 1, previousRow[wordIndex] + 1, previousRow[wordIndex - 1] + substitutionCost);
|
|
86
|
+
const isTransposition = queryIndex > 1 &&
|
|
87
|
+
wordIndex > 1 &&
|
|
88
|
+
query[queryIndex - 1] === word[wordIndex - 2] &&
|
|
89
|
+
query[queryIndex - 2] === word[wordIndex - 1];
|
|
90
|
+
if (isTransposition) {
|
|
91
|
+
cost = Math.min(cost, twoRowsBack[wordIndex - 2] + 1);
|
|
92
|
+
}
|
|
93
|
+
currentRow.push(cost);
|
|
94
|
+
rowBest = Math.min(rowBest, cost);
|
|
95
|
+
}
|
|
96
|
+
if (rowBest > budget)
|
|
97
|
+
return null;
|
|
98
|
+
twoRowsBack = previousRow;
|
|
99
|
+
previousRow = currentRow;
|
|
100
|
+
}
|
|
101
|
+
const distance = previousRow[word.length];
|
|
102
|
+
return distance <= budget ? distance : null;
|
|
103
|
+
}
|
|
104
|
+
function transpositionDistance(query, word) {
|
|
105
|
+
return isAdjacentTransposition(query, word) ? 1 : null;
|
|
106
|
+
}
|
|
107
|
+
/** True when the two differ only by one swap of neighbouring characters. */
|
|
108
|
+
function isAdjacentTransposition(query, word) {
|
|
109
|
+
if (query.length !== word.length)
|
|
110
|
+
return false;
|
|
111
|
+
let index = 0;
|
|
112
|
+
while (index < query.length && query[index] === word[index])
|
|
113
|
+
index += 1;
|
|
114
|
+
if (index >= query.length - 1)
|
|
115
|
+
return false;
|
|
116
|
+
if (query[index] !== word[index + 1] || query[index + 1] !== word[index])
|
|
117
|
+
return false;
|
|
118
|
+
return query.slice(index + 2) === word.slice(index + 2);
|
|
119
|
+
}
|
|
120
|
+
export function fuzzyPolicyForToken(token) {
|
|
121
|
+
if (token.length <= 3)
|
|
122
|
+
return null;
|
|
123
|
+
if (token.length === 4)
|
|
124
|
+
return { maxEdits: 1, transpositionsOnly: true };
|
|
125
|
+
if (token.length <= 7)
|
|
126
|
+
return { maxEdits: 1, transpositionsOnly: false };
|
|
127
|
+
return { maxEdits: 2, transpositionsOnly: false };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Words are what people mistype, so the fuzzy tier compares the query against
|
|
131
|
+
* each word rather than against the whole string — otherwise a long title's
|
|
132
|
+
* length difference alone would blow the budget.
|
|
133
|
+
*/
|
|
134
|
+
function scoreFuzzyMatch(query, text, policy) {
|
|
135
|
+
if (policy.maxEdits <= 0 || query.length <= policy.maxEdits)
|
|
136
|
+
return null;
|
|
137
|
+
let best = null;
|
|
138
|
+
const wordPattern = /[a-z0-9]+/g;
|
|
139
|
+
let word = wordPattern.exec(text);
|
|
140
|
+
while (word !== null) {
|
|
141
|
+
// Compare against the whole word and against its leading slices, so a typo
|
|
142
|
+
// in a prefix ("confug" for "configuration") still lands — the length gap
|
|
143
|
+
// to the full word would otherwise blow the budget on its own.
|
|
144
|
+
const candidates = new Set([
|
|
145
|
+
word[0],
|
|
146
|
+
word[0].slice(0, query.length),
|
|
147
|
+
word[0].slice(0, query.length + policy.maxEdits),
|
|
148
|
+
]);
|
|
149
|
+
for (const candidate of candidates) {
|
|
150
|
+
const distance = policy.transpositionsOnly
|
|
151
|
+
? transpositionDistance(query, candidate)
|
|
152
|
+
: boundedEditDistance(query, candidate, policy.maxEdits);
|
|
153
|
+
if (distance === null)
|
|
154
|
+
continue;
|
|
155
|
+
const score = { tier: TIER_FUZZY, offset: word.index, spread: distance };
|
|
156
|
+
if (!best || compareMatchScores(score, best) < 0) {
|
|
157
|
+
best = score;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
word = wordPattern.exec(text);
|
|
161
|
+
}
|
|
162
|
+
return best;
|
|
163
|
+
}
|
|
164
|
+
export function scoreMatch(query, text, options = {}) {
|
|
165
|
+
if (!query)
|
|
166
|
+
return { tier: TIER_EXACT, offset: 0 };
|
|
167
|
+
const q = query.toLowerCase();
|
|
168
|
+
const t = text.toLowerCase();
|
|
169
|
+
if (t === q)
|
|
170
|
+
return { tier: TIER_EXACT, offset: 0 };
|
|
171
|
+
const exact = scoreSubstringMatch(q, t) ?? scoreSubsequenceMatch(q, t);
|
|
172
|
+
if (exact)
|
|
173
|
+
return exact;
|
|
174
|
+
const fuzzy = options.fuzzy;
|
|
175
|
+
return fuzzy ? scoreFuzzyMatch(q, t, fuzzy) : null;
|
|
176
|
+
}
|
|
177
|
+
function mergeAdjacentRanges(indices) {
|
|
178
|
+
const ranges = [];
|
|
179
|
+
for (const index of indices) {
|
|
180
|
+
const last = ranges.at(-1);
|
|
181
|
+
if (last && last.start + last.length === index) {
|
|
182
|
+
last.length += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
ranges.push({ start: index, length: 1 });
|
|
186
|
+
}
|
|
187
|
+
return ranges;
|
|
188
|
+
}
|
|
189
|
+
function wordRangeAt(text, offset) {
|
|
190
|
+
let end = offset;
|
|
191
|
+
while (end < text.length && /[a-z0-9]/.test(text[end]))
|
|
192
|
+
end += 1;
|
|
193
|
+
return { start: offset, length: Math.max(end - offset, 1) };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Where a score's match actually landed, so a caller can mark it. Derived from
|
|
197
|
+
* a score rather than produced alongside one: ranking touches every candidate
|
|
198
|
+
* and needs no ranges, while only the handful of rows that get rendered do.
|
|
199
|
+
*
|
|
200
|
+
* The tier decides the shape. A substring hit is one span; a subsequence hit is
|
|
201
|
+
* the scattered characters it walked; a typo hit marks the whole word, because
|
|
202
|
+
* the characters the user got wrong are not in the text to point at.
|
|
203
|
+
*/
|
|
204
|
+
export function matchRanges(query, text, score) {
|
|
205
|
+
if (!query)
|
|
206
|
+
return [];
|
|
207
|
+
const q = query.toLowerCase();
|
|
208
|
+
const t = text.toLowerCase();
|
|
209
|
+
if (score.tier === TIER_EXACT)
|
|
210
|
+
return [{ start: 0, length: text.length }];
|
|
211
|
+
if (score.tier === TIER_FUZZY)
|
|
212
|
+
return [wordRangeAt(t, score.offset)];
|
|
213
|
+
if (score.tier === TIER_SUBSEQUENCE) {
|
|
214
|
+
const indices = [];
|
|
215
|
+
let queryIndex = 0;
|
|
216
|
+
for (let textIndex = 0; textIndex < t.length && queryIndex < q.length; textIndex += 1) {
|
|
217
|
+
if (t[textIndex] !== q[queryIndex])
|
|
218
|
+
continue;
|
|
219
|
+
indices.push(textIndex);
|
|
220
|
+
queryIndex += 1;
|
|
221
|
+
}
|
|
222
|
+
return mergeAdjacentRanges(indices);
|
|
223
|
+
}
|
|
224
|
+
return [{ start: score.offset, length: q.length }];
|
|
225
|
+
}
|
|
226
|
+
export function compareMatchScores(a, b) {
|
|
227
|
+
if (a.tier !== b.tier)
|
|
228
|
+
return a.tier - b.tier;
|
|
229
|
+
if (a.offset !== b.offset)
|
|
230
|
+
return a.offset - b.offset;
|
|
231
|
+
return (a.spread ?? 0) - (b.spread ?? 0);
|
|
232
|
+
}
|
|
233
|
+
export function tokenizeQuery(query) {
|
|
234
|
+
return query
|
|
235
|
+
.trim()
|
|
236
|
+
.toLowerCase()
|
|
237
|
+
.split(/\s+/)
|
|
238
|
+
.filter((token) => token.length > 0);
|
|
239
|
+
}
|
|
240
|
+
export function scoreTextFields(query, fields, options = {}) {
|
|
241
|
+
const tokens = tokenizeQuery(query);
|
|
242
|
+
if (tokens.length === 0)
|
|
243
|
+
return { tier: TIER_EXACT, offset: 0, spread: 0 };
|
|
244
|
+
const aggregate = { tier: TIER_EXACT, offset: 0, spread: 0 };
|
|
245
|
+
for (const token of tokens) {
|
|
246
|
+
const fuzzy = options.typoTolerant ? fuzzyPolicyForToken(token) : null;
|
|
247
|
+
let best = null;
|
|
248
|
+
for (const field of fields) {
|
|
249
|
+
const score = scoreMatch(token, field, { fuzzy });
|
|
250
|
+
if (score && (!best || compareMatchScores(score, best) < 0)) {
|
|
251
|
+
best = score;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!best)
|
|
255
|
+
return null;
|
|
256
|
+
aggregate.tier += best.tier;
|
|
257
|
+
aggregate.offset += best.offset;
|
|
258
|
+
aggregate.spread = (aggregate.spread ?? 0) + (best.spread ?? token.length);
|
|
259
|
+
}
|
|
260
|
+
return aggregate;
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=text-match.js.map
|