@opengeni/db 3.5.2-canary.0 → 3.6.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/canonical-human-identities.js +3 -3
- package/dist/{chunk-NRGUX3M5.js → chunk-2NT476HD.js} +2 -2
- package/dist/{chunk-PERPE2UY.js → chunk-744DFC65.js} +31 -1
- package/dist/chunk-744DFC65.js.map +1 -0
- package/dist/{chunk-FJHEWYV5.js → chunk-CDMMSUWJ.js} +3 -3
- package/dist/{chunk-TRCYF26N.js → chunk-G3732M3T.js} +2 -2
- package/dist/{chunk-ORKVXHL2.js → chunk-IW6O6G7R.js} +2 -2
- package/dist/{chunk-Y44FKTYU.js → chunk-Q5YUGQVK.js} +3 -3
- package/dist/{chunk-U7NKCX5E.js → chunk-UGDVSQ6U.js} +2 -2
- package/dist/{chunk-6NYKC5DD.js → chunk-UJ2EW3QI.js} +4729 -4583
- package/dist/chunk-UJ2EW3QI.js.map +1 -0
- package/dist/{chunk-X6T2P3L3.js → chunk-WW7AJ5IC.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +2112 -1436
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +5 -4
- package/dist/schema.d.ts +7 -6
- package/dist/schema.js +7 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/work-claims-schema.d.ts +975 -0
- package/dist/work-claims.d.ts +83 -0
- package/drizzle/0365_permission_scoped_work_claims.sql +1306 -0
- package/drizzle/0366_session_discovery_title_search_index.sql +5 -0
- package/drizzle/0367_session_discovery_goal_search_index.sql +5 -0
- package/drizzle/0368_session_discovery_claim_search_index.sql +6 -0
- package/package.json +6 -6
- package/src/index.ts +1051 -330
- package/src/provision-roles.ts +18 -0
- package/src/runtime-posture.ts +12 -0
- package/src/schema.ts +1 -0
- package/src/work-claims-schema.ts +148 -0
- package/src/work-claims.ts +200 -0
- package/dist/chunk-6NYKC5DD.js.map +0 -1
- package/dist/chunk-PERPE2UY.js.map +0 -1
- /package/dist/{chunk-NRGUX3M5.js.map → chunk-2NT476HD.js.map} +0 -0
- /package/dist/{chunk-FJHEWYV5.js.map → chunk-CDMMSUWJ.js.map} +0 -0
- /package/dist/{chunk-TRCYF26N.js.map → chunk-G3732M3T.js.map} +0 -0
- /package/dist/{chunk-ORKVXHL2.js.map → chunk-IW6O6G7R.js.map} +0 -0
- /package/dist/{chunk-Y44FKTYU.js.map → chunk-Q5YUGQVK.js.map} +0 -0
- /package/dist/{chunk-U7NKCX5E.js.map → chunk-UGDVSQ6U.js.map} +0 -0
- /package/dist/{chunk-X6T2P3L3.js.map → chunk-WW7AJ5IC.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -20,9 +20,21 @@ import {
|
|
|
20
20
|
SessionGoalSnapshot,
|
|
21
21
|
AUTOMATIC_SESSION_TITLE_FALLBACK,
|
|
22
22
|
ScheduledTaskRunAcceptedExecution,
|
|
23
|
+
WORK_CLAIM_DISCOVERY_DEFAULT_LIMIT,
|
|
24
|
+
WORK_CLAIM_DISCOVERY_LIMIT,
|
|
25
|
+
WORK_DISCOVERY_QUERY_MAX_CHARS,
|
|
26
|
+
WORK_DISCOVERY_RECENT_HOURS_MAX,
|
|
27
|
+
WorkClaimDiscoverySummary as WorkClaimDiscoverySummarySchema,
|
|
28
|
+
WorkDiscoveryMatch as WorkDiscoveryMatchSchema,
|
|
29
|
+
WorkClaimNamespace,
|
|
30
|
+
WorkClaimSubjectType,
|
|
31
|
+
WorkClaimCanonicalKey,
|
|
32
|
+
SessionStatus as SessionStatusSchema,
|
|
23
33
|
SessionGoalRootConstraintsWrite,
|
|
24
34
|
normalizeSessionGoalRootConstraints,
|
|
25
35
|
normalizeAutomaticSessionTitle,
|
|
36
|
+
normalizeWorkClaimCanonicalKey,
|
|
37
|
+
normalizeWorkClaimNamespace,
|
|
26
38
|
sessionVisibilityToPublic,
|
|
27
39
|
sessionGoalUtf8Bytes,
|
|
28
40
|
} from "@opengeni/contracts";
|
|
@@ -154,6 +166,9 @@ import type {
|
|
|
154
166
|
WorkspaceArchiveDescriptor,
|
|
155
167
|
ModalCheckpointProviderBinding,
|
|
156
168
|
SandboxProviderContinuityRecovery,
|
|
169
|
+
WorkClaimDiscoverySummary,
|
|
170
|
+
WorkClaimSubjectFilter,
|
|
171
|
+
WorkDiscoveryProjection,
|
|
157
172
|
} from "@opengeni/contracts";
|
|
158
173
|
import {
|
|
159
174
|
closePrivateSessionCreateCapability,
|
|
@@ -273,6 +288,7 @@ import {
|
|
|
273
288
|
sql,
|
|
274
289
|
type SQL,
|
|
275
290
|
} from "drizzle-orm";
|
|
291
|
+
import type { PgTransactionConfig } from "drizzle-orm/pg-core";
|
|
276
292
|
import {
|
|
277
293
|
creatorColumns,
|
|
278
294
|
frozenInitiatorForCommandActor,
|
|
@@ -426,6 +442,7 @@ export * from "./company-brain-governed-writes";
|
|
|
426
442
|
export * from "./company-brain-context-selection";
|
|
427
443
|
export * from "./knowledge-source-sync";
|
|
428
444
|
export * from "./task-notes";
|
|
445
|
+
export * from "./work-claims";
|
|
429
446
|
export * from "./managed-human-provisioning";
|
|
430
447
|
export * from "./managed-user-setup";
|
|
431
448
|
export * from "./organization-membership-backfill";
|
|
@@ -30823,9 +30840,11 @@ export async function getLatestSessionModelForSubject(
|
|
|
30823
30840
|
});
|
|
30824
30841
|
}
|
|
30825
30842
|
|
|
30826
|
-
export type SessionDiscoveryOrderBy = "createdAt" | "updatedAt";
|
|
30843
|
+
export type SessionDiscoveryOrderBy = "createdAt" | "updatedAt" | "relevance";
|
|
30827
30844
|
export type SessionDiscoveryCursor = {
|
|
30828
30845
|
orderBy: SessionDiscoveryOrderBy;
|
|
30846
|
+
/** Stable rank class for relevance order; null for chronological order. */
|
|
30847
|
+
sortRank: number | null;
|
|
30829
30848
|
/** Decimal database activity revision; zero is the legacy/created-order bucket. */
|
|
30830
30849
|
sortRevision: string;
|
|
30831
30850
|
/** Exact PostgreSQL timestamp text (including microseconds), not a JS Date. */
|
|
@@ -30837,6 +30856,8 @@ export type SessionDiscoveryCursor = {
|
|
|
30837
30856
|
snapshotRevision: string;
|
|
30838
30857
|
/** Incremental revision scope is cursor-bound and cannot change on continuation. */
|
|
30839
30858
|
updatedAfter: string | null;
|
|
30859
|
+
/** SHA-256 of normalized relevance filters; null for chronological order. */
|
|
30860
|
+
filterHash: string | null;
|
|
30840
30861
|
};
|
|
30841
30862
|
export const SESSION_DISCOVERY_GOAL_MAX_CHARS = 600;
|
|
30842
30863
|
export const SESSION_DISCOVERY_MESSAGE_MAX_CHARS = 600;
|
|
@@ -30861,12 +30882,15 @@ export type SessionDiscoverySummary = {
|
|
|
30861
30882
|
preview: string | null;
|
|
30862
30883
|
previewOriginalChars: number | null;
|
|
30863
30884
|
} | null;
|
|
30885
|
+
workDiscovery: WorkDiscoveryProjection;
|
|
30864
30886
|
createdAt: string;
|
|
30865
30887
|
updatedAt: string;
|
|
30866
30888
|
/** Internal exact revision key; omitted from the MCP row projection. */
|
|
30867
30889
|
sortRevision: string;
|
|
30868
30890
|
/** Internal exact keyset timestamp; omitted from the MCP row projection. */
|
|
30869
30891
|
sortAt: string;
|
|
30892
|
+
/** Internal stable relevance class; omitted from public row projections. */
|
|
30893
|
+
sortRank: number | null;
|
|
30870
30894
|
};
|
|
30871
30895
|
|
|
30872
30896
|
function projectSessionDiscoveryControlForRelatedAccess(
|
|
@@ -30925,6 +30949,700 @@ async function readWorkspaceSessionActivityRevision(
|
|
|
30925
30949
|
return normalizeSessionActivityRevision(revision, "snapshot revision");
|
|
30926
30950
|
}
|
|
30927
30951
|
|
|
30952
|
+
const SESSION_DISCOVERY_QUERY_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
30953
|
+
|
|
30954
|
+
type NormalizedSessionDiscoveryFilters = {
|
|
30955
|
+
query: string | null;
|
|
30956
|
+
statuses: SessionStatus[];
|
|
30957
|
+
activeOnly: boolean;
|
|
30958
|
+
recentHours: number | null;
|
|
30959
|
+
rootSessionId: string | null;
|
|
30960
|
+
parentSessionId: string | null | undefined;
|
|
30961
|
+
subject: WorkClaimSubjectFilter | null;
|
|
30962
|
+
};
|
|
30963
|
+
|
|
30964
|
+
type SessionDiscoveryPageRow = {
|
|
30965
|
+
id: string | null;
|
|
30966
|
+
title: string | null;
|
|
30967
|
+
titleOriginalChars: number | string | null;
|
|
30968
|
+
parentSessionId: string | null;
|
|
30969
|
+
rootSessionId: string | null;
|
|
30970
|
+
nestedAgentDepth: number | string | null;
|
|
30971
|
+
status: string | null;
|
|
30972
|
+
createdAt: Date | string | null;
|
|
30973
|
+
updatedAt: Date | string | null;
|
|
30974
|
+
sortRevision: string | null;
|
|
30975
|
+
sortAt: string | null;
|
|
30976
|
+
sortRank: number | string | null;
|
|
30977
|
+
matchClass: string | null;
|
|
30978
|
+
matchedField: string | null;
|
|
30979
|
+
scoreBand: string | null;
|
|
30980
|
+
matchedClaimId: string | null;
|
|
30981
|
+
total: number | string;
|
|
30982
|
+
};
|
|
30983
|
+
|
|
30984
|
+
type SessionDiscoveryClaimRow = {
|
|
30985
|
+
sessionId: string;
|
|
30986
|
+
id: string;
|
|
30987
|
+
subjectNamespace: string;
|
|
30988
|
+
subjectType: string;
|
|
30989
|
+
canonicalKey: string;
|
|
30990
|
+
displayLabel: string | null;
|
|
30991
|
+
role: string;
|
|
30992
|
+
state: string;
|
|
30993
|
+
revision: number | string;
|
|
30994
|
+
provenance: string;
|
|
30995
|
+
versionKind: string | null;
|
|
30996
|
+
versionValue: string | null;
|
|
30997
|
+
observedAt: Date | string;
|
|
30998
|
+
updatedAt: Date | string;
|
|
30999
|
+
settledAt: Date | string | null;
|
|
31000
|
+
availableCount: number | string;
|
|
31001
|
+
};
|
|
31002
|
+
|
|
31003
|
+
function normalizeSessionDiscoveryQuery(value: string | undefined): string | null {
|
|
31004
|
+
if (value === undefined) return null;
|
|
31005
|
+
const canonical = value.normalize("NFKC");
|
|
31006
|
+
if (SESSION_DISCOVERY_QUERY_CONTROL_CHARACTERS.test(canonical)) {
|
|
31007
|
+
throw new Error("sessions_list query must not contain control characters");
|
|
31008
|
+
}
|
|
31009
|
+
const normalized = canonical.trim().replace(/\s+/gu, " ").toLowerCase();
|
|
31010
|
+
if (!normalized) return null;
|
|
31011
|
+
if (Array.from(normalized).length > WORK_DISCOVERY_QUERY_MAX_CHARS) {
|
|
31012
|
+
throw new Error(
|
|
31013
|
+
`sessions_list query must be at most ${WORK_DISCOVERY_QUERY_MAX_CHARS} characters`,
|
|
31014
|
+
);
|
|
31015
|
+
}
|
|
31016
|
+
return normalized;
|
|
31017
|
+
}
|
|
31018
|
+
|
|
31019
|
+
function normalizeSessionDiscoverySubject(
|
|
31020
|
+
value: WorkClaimSubjectFilter | undefined,
|
|
31021
|
+
): WorkClaimSubjectFilter | null {
|
|
31022
|
+
if (!value) return null;
|
|
31023
|
+
return {
|
|
31024
|
+
namespace: WorkClaimNamespace.parse(normalizeWorkClaimNamespace(value.namespace)),
|
|
31025
|
+
type: WorkClaimSubjectType.parse(value.type),
|
|
31026
|
+
canonicalKey: WorkClaimCanonicalKey.parse(normalizeWorkClaimCanonicalKey(value.canonicalKey)),
|
|
31027
|
+
};
|
|
31028
|
+
}
|
|
31029
|
+
|
|
31030
|
+
function normalizeSessionDiscoveryFilters(options: {
|
|
31031
|
+
query?: string;
|
|
31032
|
+
search?: string;
|
|
31033
|
+
statuses?: SessionStatus[];
|
|
31034
|
+
activeOnly?: boolean;
|
|
31035
|
+
recentHours?: number;
|
|
31036
|
+
rootSessionId?: string;
|
|
31037
|
+
parentSessionId?: string | null;
|
|
31038
|
+
subject?: WorkClaimSubjectFilter;
|
|
31039
|
+
}): NormalizedSessionDiscoveryFilters {
|
|
31040
|
+
const query = normalizeSessionDiscoveryQuery(options.query ?? options.search);
|
|
31041
|
+
if (
|
|
31042
|
+
options.query !== undefined &&
|
|
31043
|
+
options.search !== undefined &&
|
|
31044
|
+
normalizeSessionDiscoveryQuery(options.query) !== normalizeSessionDiscoveryQuery(options.search)
|
|
31045
|
+
) {
|
|
31046
|
+
throw new Error("sessions_list query and legacy search filter do not match");
|
|
31047
|
+
}
|
|
31048
|
+
const statuses = [...new Set(options.statuses ?? [])]
|
|
31049
|
+
.map((status) => SessionStatusSchema.parse(status))
|
|
31050
|
+
.sort();
|
|
31051
|
+
const recentHours = options.recentHours ?? null;
|
|
31052
|
+
if (
|
|
31053
|
+
recentHours !== null &&
|
|
31054
|
+
(!Number.isSafeInteger(recentHours) ||
|
|
31055
|
+
recentHours < 1 ||
|
|
31056
|
+
recentHours > WORK_DISCOVERY_RECENT_HOURS_MAX)
|
|
31057
|
+
) {
|
|
31058
|
+
throw new Error(
|
|
31059
|
+
`sessions_list recentHours must be an integer between 1 and ${WORK_DISCOVERY_RECENT_HOURS_MAX}`,
|
|
31060
|
+
);
|
|
31061
|
+
}
|
|
31062
|
+
const subject = normalizeSessionDiscoverySubject(options.subject);
|
|
31063
|
+
if (query && subject) {
|
|
31064
|
+
throw new Error("sessions_list query cannot be combined with an exact subject filter");
|
|
31065
|
+
}
|
|
31066
|
+
return {
|
|
31067
|
+
query,
|
|
31068
|
+
statuses,
|
|
31069
|
+
activeOnly: options.activeOnly === true,
|
|
31070
|
+
recentHours,
|
|
31071
|
+
rootSessionId: options.rootSessionId ?? null,
|
|
31072
|
+
parentSessionId: Object.prototype.hasOwnProperty.call(options, "parentSessionId")
|
|
31073
|
+
? (options.parentSessionId ?? null)
|
|
31074
|
+
: undefined,
|
|
31075
|
+
subject,
|
|
31076
|
+
};
|
|
31077
|
+
}
|
|
31078
|
+
|
|
31079
|
+
function sessionDiscoveryFilterHash(filters: NormalizedSessionDiscoveryFilters): string {
|
|
31080
|
+
return createHash("sha256")
|
|
31081
|
+
.update(
|
|
31082
|
+
JSON.stringify({
|
|
31083
|
+
query: filters.query,
|
|
31084
|
+
statuses: filters.statuses,
|
|
31085
|
+
activeOnly: filters.activeOnly,
|
|
31086
|
+
recentHours: filters.recentHours,
|
|
31087
|
+
rootSessionId: filters.rootSessionId,
|
|
31088
|
+
parentSessionId:
|
|
31089
|
+
filters.parentSessionId === undefined ? { any: true } : filters.parentSessionId,
|
|
31090
|
+
subject: filters.subject,
|
|
31091
|
+
}),
|
|
31092
|
+
)
|
|
31093
|
+
.digest("hex");
|
|
31094
|
+
}
|
|
31095
|
+
|
|
31096
|
+
function sessionDiscoverySubjectDigest(subject: WorkClaimSubjectFilter): string {
|
|
31097
|
+
return createHash("sha256")
|
|
31098
|
+
.update(`${subject.namespace}\u001f${subject.type}\u001f${subject.canonicalKey}`, "utf8")
|
|
31099
|
+
.digest("hex");
|
|
31100
|
+
}
|
|
31101
|
+
|
|
31102
|
+
function sessionDiscoveryIso(value: Date | string): string {
|
|
31103
|
+
return (value instanceof Date ? value : new Date(value)).toISOString();
|
|
31104
|
+
}
|
|
31105
|
+
|
|
31106
|
+
async function selectSessionDiscoveryPageRows(
|
|
31107
|
+
db: Database,
|
|
31108
|
+
workspaceId: string,
|
|
31109
|
+
options: {
|
|
31110
|
+
limit: number;
|
|
31111
|
+
orderBy: SessionDiscoveryOrderBy;
|
|
31112
|
+
cursor?: SessionDiscoveryCursor;
|
|
31113
|
+
snapshotAt: string;
|
|
31114
|
+
snapshotRevision: string;
|
|
31115
|
+
updatedAfter: string | null;
|
|
31116
|
+
subjectId?: string;
|
|
31117
|
+
authorizationScope?: SessionAuthorizationListScope;
|
|
31118
|
+
filters: NormalizedSessionDiscoveryFilters;
|
|
31119
|
+
},
|
|
31120
|
+
): Promise<{ rows: SessionDiscoveryPageRow[]; total: number }> {
|
|
31121
|
+
const authorizationFilters: SQL[] = [eq(schema.sessions.workspaceId, workspaceId)];
|
|
31122
|
+
if (options.subjectId) {
|
|
31123
|
+
authorizationFilters.push(sql`not exists (
|
|
31124
|
+
select 1
|
|
31125
|
+
from ${schema.slackInteractions} private_slack_interaction
|
|
31126
|
+
where private_slack_interaction.workspace_id = ${schema.sessions.workspaceId}
|
|
31127
|
+
and private_slack_interaction.session_reservation_id = ${schema.sessions.rootSessionId}
|
|
31128
|
+
and private_slack_interaction.visibility = 'private'
|
|
31129
|
+
and private_slack_interaction.owning_subject_id <> ${options.subjectId}
|
|
31130
|
+
)`);
|
|
31131
|
+
} else {
|
|
31132
|
+
// Missing subject context must fail closed for Slack-private roots. Public
|
|
31133
|
+
// and ordinary workspace-shared discovery remains available to internal
|
|
31134
|
+
// callers that do not carry a human subject.
|
|
31135
|
+
authorizationFilters.push(sql`not exists (
|
|
31136
|
+
select 1
|
|
31137
|
+
from ${schema.slackInteractions} private_slack_interaction
|
|
31138
|
+
where private_slack_interaction.workspace_id = ${schema.sessions.workspaceId}
|
|
31139
|
+
and private_slack_interaction.session_reservation_id = ${schema.sessions.rootSessionId}
|
|
31140
|
+
and private_slack_interaction.visibility = 'private'
|
|
31141
|
+
)`);
|
|
31142
|
+
}
|
|
31143
|
+
if (options.authorizationScope) {
|
|
31144
|
+
authorizationFilters.push(sessionAuthorizationScopeFilter(options.authorizationScope));
|
|
31145
|
+
}
|
|
31146
|
+
if (options.filters.statuses.length > 0) {
|
|
31147
|
+
authorizationFilters.push(inArray(schema.sessions.status, options.filters.statuses));
|
|
31148
|
+
}
|
|
31149
|
+
if (options.filters.activeOnly) {
|
|
31150
|
+
authorizationFilters.push(notInArray(schema.sessions.status, ["failed", "cancelled"]));
|
|
31151
|
+
}
|
|
31152
|
+
if (options.filters.recentHours !== null) {
|
|
31153
|
+
authorizationFilters.push(
|
|
31154
|
+
sql`${schema.sessions.updatedAt} >= ${options.snapshotAt}::text::timestamptz
|
|
31155
|
+
- (${options.filters.recentHours}::integer * interval '1 hour')`,
|
|
31156
|
+
);
|
|
31157
|
+
}
|
|
31158
|
+
if (options.filters.rootSessionId) {
|
|
31159
|
+
authorizationFilters.push(eq(schema.sessions.rootSessionId, options.filters.rootSessionId));
|
|
31160
|
+
}
|
|
31161
|
+
if (options.filters.parentSessionId !== undefined) {
|
|
31162
|
+
authorizationFilters.push(
|
|
31163
|
+
options.filters.parentSessionId === null
|
|
31164
|
+
? isNull(schema.sessions.parentSessionId)
|
|
31165
|
+
: eq(schema.sessions.parentSessionId, options.filters.parentSessionId),
|
|
31166
|
+
);
|
|
31167
|
+
}
|
|
31168
|
+
authorizationFilters.push(
|
|
31169
|
+
options.orderBy === "createdAt"
|
|
31170
|
+
? sql`${schema.sessions.createdAt} <= ${options.snapshotAt}::text::timestamptz`
|
|
31171
|
+
: sql`${schema.sessions.activityRevision} <= ${options.snapshotRevision}::text::bigint`,
|
|
31172
|
+
);
|
|
31173
|
+
if (options.updatedAfter !== null) {
|
|
31174
|
+
authorizationFilters.push(
|
|
31175
|
+
sql`${schema.sessions.activityRevision} > ${options.updatedAfter}::text::bigint`,
|
|
31176
|
+
);
|
|
31177
|
+
}
|
|
31178
|
+
const authorizedPredicate = and(...authorizationFilters) ?? sql`false`;
|
|
31179
|
+
const claimStatePredicate =
|
|
31180
|
+
options.filters.activeOnly || options.filters.recentHours === null
|
|
31181
|
+
? sql`${schema.sessionWorkClaims.state} = 'active'`
|
|
31182
|
+
: sql`(${schema.sessionWorkClaims.state} = 'active' or
|
|
31183
|
+
${schema.sessionWorkClaims.updatedAt} >= ${options.snapshotAt}::text::timestamptz
|
|
31184
|
+
- (${options.filters.recentHours}::integer * interval '1 hour'))`;
|
|
31185
|
+
const subjectDigest = options.filters.subject
|
|
31186
|
+
? sessionDiscoverySubjectDigest(options.filters.subject)
|
|
31187
|
+
: null;
|
|
31188
|
+
const subjectScopePredicate = options.filters.subject
|
|
31189
|
+
? sql`exists (
|
|
31190
|
+
select 1
|
|
31191
|
+
from ${schema.sessionWorkClaims}
|
|
31192
|
+
where ${schema.sessionWorkClaims.workspaceId} = ${workspaceId}
|
|
31193
|
+
and ${schema.sessionWorkClaims.sessionId} = authorized_sessions.id
|
|
31194
|
+
and ${schema.sessionWorkClaims.subjectNamespace} = ${options.filters.subject.namespace}
|
|
31195
|
+
and ${schema.sessionWorkClaims.subjectType} = ${options.filters.subject.type}
|
|
31196
|
+
and ${schema.sessionWorkClaims.subjectDigest} = ${subjectDigest}
|
|
31197
|
+
and ${claimStatePredicate}
|
|
31198
|
+
)`
|
|
31199
|
+
: sql`true`;
|
|
31200
|
+
|
|
31201
|
+
const rankingCtes = options.filters.subject
|
|
31202
|
+
? sql`,
|
|
31203
|
+
match_candidates as materialized (
|
|
31204
|
+
select
|
|
31205
|
+
subject_scoped_sessions.id as session_id,
|
|
31206
|
+
0::integer as match_rank,
|
|
31207
|
+
'exact_subject'::text as match_class,
|
|
31208
|
+
'subject'::text as matched_field,
|
|
31209
|
+
'exact'::text as score_band,
|
|
31210
|
+
${schema.sessionWorkClaims.id} as matched_claim_id,
|
|
31211
|
+
${schema.sessionWorkClaims.updatedAt} as matched_claim_updated_at
|
|
31212
|
+
from subject_scoped_sessions
|
|
31213
|
+
join ${schema.sessionWorkClaims}
|
|
31214
|
+
on ${schema.sessionWorkClaims.workspaceId} = ${workspaceId}
|
|
31215
|
+
and ${schema.sessionWorkClaims.sessionId} = subject_scoped_sessions.id
|
|
31216
|
+
and ${schema.sessionWorkClaims.subjectNamespace} = ${options.filters.subject.namespace}
|
|
31217
|
+
and ${schema.sessionWorkClaims.subjectType} = ${options.filters.subject.type}
|
|
31218
|
+
and ${schema.sessionWorkClaims.subjectDigest} = ${subjectDigest}
|
|
31219
|
+
and ${claimStatePredicate}
|
|
31220
|
+
),
|
|
31221
|
+
best_matches as materialized (
|
|
31222
|
+
select distinct on (session_id)
|
|
31223
|
+
session_id, match_rank, match_class, matched_field, score_band, matched_claim_id
|
|
31224
|
+
from match_candidates
|
|
31225
|
+
order by session_id, match_rank, matched_claim_updated_at desc, matched_claim_id desc
|
|
31226
|
+
)`
|
|
31227
|
+
: options.filters.query
|
|
31228
|
+
? sql`,
|
|
31229
|
+
search_input as materialized (
|
|
31230
|
+
select
|
|
31231
|
+
lower(${options.filters.query}::text) as normalized_query,
|
|
31232
|
+
websearch_to_tsquery('simple', ${options.filters.query}::text) as ts_query
|
|
31233
|
+
),
|
|
31234
|
+
match_candidates as materialized (
|
|
31235
|
+
select
|
|
31236
|
+
subject_scoped_sessions.id as session_id,
|
|
31237
|
+
case
|
|
31238
|
+
when lower(search_session.title) = search_input.normalized_query then 10
|
|
31239
|
+
when starts_with(lower(search_session.title), search_input.normalized_query) then 11
|
|
31240
|
+
else 12
|
|
31241
|
+
end::integer as match_rank,
|
|
31242
|
+
'title'::text as match_class,
|
|
31243
|
+
'title'::text as matched_field,
|
|
31244
|
+
case
|
|
31245
|
+
when lower(search_session.title) = search_input.normalized_query then 'exact'
|
|
31246
|
+
else 'strong'
|
|
31247
|
+
end::text as score_band,
|
|
31248
|
+
null::uuid as matched_claim_id,
|
|
31249
|
+
null::timestamptz as matched_claim_updated_at
|
|
31250
|
+
from subject_scoped_sessions
|
|
31251
|
+
join ${schema.sessions} search_session
|
|
31252
|
+
on search_session.workspace_id = ${workspaceId}
|
|
31253
|
+
and search_session.id = subject_scoped_sessions.id
|
|
31254
|
+
cross join search_input
|
|
31255
|
+
where search_session.title is not null
|
|
31256
|
+
and (
|
|
31257
|
+
lower(search_session.title) = search_input.normalized_query
|
|
31258
|
+
or starts_with(lower(search_session.title), search_input.normalized_query)
|
|
31259
|
+
or to_tsvector('simple', search_session.title) @@ search_input.ts_query
|
|
31260
|
+
)
|
|
31261
|
+
|
|
31262
|
+
union all
|
|
31263
|
+
|
|
31264
|
+
select
|
|
31265
|
+
subject_scoped_sessions.id as session_id,
|
|
31266
|
+
case
|
|
31267
|
+
when lower(${schema.sessionGoals.text}) = search_input.normalized_query then 20
|
|
31268
|
+
when starts_with(
|
|
31269
|
+
lower(${schema.sessionGoals.text}), search_input.normalized_query
|
|
31270
|
+
) then 21
|
|
31271
|
+
else 22
|
|
31272
|
+
end::integer as match_rank,
|
|
31273
|
+
'goal'::text as match_class,
|
|
31274
|
+
'goal'::text as matched_field,
|
|
31275
|
+
case
|
|
31276
|
+
when lower(${schema.sessionGoals.text}) = search_input.normalized_query then 'exact'
|
|
31277
|
+
else 'strong'
|
|
31278
|
+
end::text as score_band,
|
|
31279
|
+
null::uuid as matched_claim_id,
|
|
31280
|
+
null::timestamptz as matched_claim_updated_at
|
|
31281
|
+
from subject_scoped_sessions
|
|
31282
|
+
join ${schema.sessionGoals}
|
|
31283
|
+
on ${schema.sessionGoals.workspaceId} = ${workspaceId}
|
|
31284
|
+
and ${schema.sessionGoals.sessionId} = subject_scoped_sessions.id
|
|
31285
|
+
and ${schema.sessionGoals.status} = 'active'
|
|
31286
|
+
cross join search_input
|
|
31287
|
+
where lower(${schema.sessionGoals.text}) = search_input.normalized_query
|
|
31288
|
+
or starts_with(lower(${schema.sessionGoals.text}), search_input.normalized_query)
|
|
31289
|
+
or to_tsvector('simple', ${schema.sessionGoals.text}) @@ search_input.ts_query
|
|
31290
|
+
|
|
31291
|
+
union all
|
|
31292
|
+
|
|
31293
|
+
select
|
|
31294
|
+
subject_scoped_sessions.id as session_id,
|
|
31295
|
+
(
|
|
31296
|
+
case when ${schema.sessionWorkClaims.state} = 'active' then 30 else 40 end
|
|
31297
|
+
+ case
|
|
31298
|
+
when lower(${schema.sessionWorkClaims.canonicalKey}) = search_input.normalized_query
|
|
31299
|
+
or lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')) = search_input.normalized_query then 0
|
|
31300
|
+
when starts_with(
|
|
31301
|
+
lower(${schema.sessionWorkClaims.canonicalKey}),
|
|
31302
|
+
search_input.normalized_query
|
|
31303
|
+
) or starts_with(
|
|
31304
|
+
lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')),
|
|
31305
|
+
search_input.normalized_query
|
|
31306
|
+
) then 1
|
|
31307
|
+
else 2
|
|
31308
|
+
end
|
|
31309
|
+
)::integer as match_rank,
|
|
31310
|
+
'fuzzy'::text as match_class,
|
|
31311
|
+
case
|
|
31312
|
+
when lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')) = search_input.normalized_query
|
|
31313
|
+
or starts_with(
|
|
31314
|
+
lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')),
|
|
31315
|
+
search_input.normalized_query
|
|
31316
|
+
)
|
|
31317
|
+
or to_tsvector('simple', coalesce(${schema.sessionWorkClaims.displayLabel}, '')) @@ search_input.ts_query
|
|
31318
|
+
then 'claim_label'
|
|
31319
|
+
else 'claim_key'
|
|
31320
|
+
end::text as matched_field,
|
|
31321
|
+
case
|
|
31322
|
+
when lower(${schema.sessionWorkClaims.canonicalKey}) = search_input.normalized_query
|
|
31323
|
+
or lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')) = search_input.normalized_query then 'exact'
|
|
31324
|
+
when starts_with(
|
|
31325
|
+
lower(${schema.sessionWorkClaims.canonicalKey}), search_input.normalized_query
|
|
31326
|
+
) or starts_with(
|
|
31327
|
+
lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')),
|
|
31328
|
+
search_input.normalized_query
|
|
31329
|
+
) then 'strong'
|
|
31330
|
+
else 'related'
|
|
31331
|
+
end::text as score_band,
|
|
31332
|
+
${schema.sessionWorkClaims.id} as matched_claim_id,
|
|
31333
|
+
${schema.sessionWorkClaims.updatedAt} as matched_claim_updated_at
|
|
31334
|
+
from subject_scoped_sessions
|
|
31335
|
+
join ${schema.sessionWorkClaims}
|
|
31336
|
+
on ${schema.sessionWorkClaims.workspaceId} = ${workspaceId}
|
|
31337
|
+
and ${schema.sessionWorkClaims.sessionId} = subject_scoped_sessions.id
|
|
31338
|
+
and ${claimStatePredicate}
|
|
31339
|
+
cross join search_input
|
|
31340
|
+
where lower(${schema.sessionWorkClaims.canonicalKey}) = search_input.normalized_query
|
|
31341
|
+
or starts_with(
|
|
31342
|
+
lower(${schema.sessionWorkClaims.canonicalKey}),
|
|
31343
|
+
search_input.normalized_query
|
|
31344
|
+
)
|
|
31345
|
+
or to_tsvector(
|
|
31346
|
+
'simple',
|
|
31347
|
+
${schema.sessionWorkClaims.canonicalKey} || ' '
|
|
31348
|
+
|| coalesce(${schema.sessionWorkClaims.displayLabel}, '')
|
|
31349
|
+
) @@ search_input.ts_query
|
|
31350
|
+
or lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')) = search_input.normalized_query
|
|
31351
|
+
or starts_with(
|
|
31352
|
+
lower(coalesce(${schema.sessionWorkClaims.displayLabel}, '')),
|
|
31353
|
+
search_input.normalized_query
|
|
31354
|
+
)
|
|
31355
|
+
),
|
|
31356
|
+
best_matches as materialized (
|
|
31357
|
+
select distinct on (session_id)
|
|
31358
|
+
session_id, match_rank, match_class, matched_field, score_band, matched_claim_id
|
|
31359
|
+
from match_candidates
|
|
31360
|
+
order by session_id, match_rank, matched_claim_updated_at desc nulls last,
|
|
31361
|
+
matched_claim_id desc nulls last
|
|
31362
|
+
)`
|
|
31363
|
+
: sql``;
|
|
31364
|
+
const rankedCte =
|
|
31365
|
+
options.orderBy === "relevance"
|
|
31366
|
+
? sql`,
|
|
31367
|
+
ranked_sessions as materialized (
|
|
31368
|
+
select
|
|
31369
|
+
subject_scoped_sessions.*,
|
|
31370
|
+
best_matches.match_rank,
|
|
31371
|
+
best_matches.match_class,
|
|
31372
|
+
best_matches.matched_field,
|
|
31373
|
+
best_matches.score_band,
|
|
31374
|
+
best_matches.matched_claim_id
|
|
31375
|
+
from subject_scoped_sessions
|
|
31376
|
+
join best_matches on best_matches.session_id = subject_scoped_sessions.id
|
|
31377
|
+
)`
|
|
31378
|
+
: sql`,
|
|
31379
|
+
ranked_sessions as materialized (
|
|
31380
|
+
select
|
|
31381
|
+
subject_scoped_sessions.*,
|
|
31382
|
+
null::integer as match_rank,
|
|
31383
|
+
null::text as match_class,
|
|
31384
|
+
null::text as matched_field,
|
|
31385
|
+
null::text as score_band,
|
|
31386
|
+
null::uuid as matched_claim_id
|
|
31387
|
+
from subject_scoped_sessions
|
|
31388
|
+
)`;
|
|
31389
|
+
const cursorPredicate = !options.cursor
|
|
31390
|
+
? sql`true`
|
|
31391
|
+
: options.orderBy === "relevance"
|
|
31392
|
+
? sql`(
|
|
31393
|
+
ranked_sessions.match_rank > ${options.cursor.sortRank}::integer
|
|
31394
|
+
or (
|
|
31395
|
+
ranked_sessions.match_rank = ${options.cursor.sortRank}::integer
|
|
31396
|
+
and (
|
|
31397
|
+
ranked_sessions.activity_revision < ${options.cursor.sortRevision}::text::bigint
|
|
31398
|
+
or (
|
|
31399
|
+
ranked_sessions.activity_revision = ${options.cursor.sortRevision}::text::bigint
|
|
31400
|
+
and (
|
|
31401
|
+
ranked_sessions.updated_at < ${options.cursor.sortAt}::text::timestamptz
|
|
31402
|
+
or (
|
|
31403
|
+
ranked_sessions.updated_at = ${options.cursor.sortAt}::text::timestamptz
|
|
31404
|
+
and ranked_sessions.id < ${options.cursor.id}::uuid
|
|
31405
|
+
)
|
|
31406
|
+
)
|
|
31407
|
+
)
|
|
31408
|
+
)
|
|
31409
|
+
)
|
|
31410
|
+
)`
|
|
31411
|
+
: options.orderBy === "updatedAt"
|
|
31412
|
+
? sql`(
|
|
31413
|
+
ranked_sessions.activity_revision < ${options.cursor.sortRevision}::text::bigint
|
|
31414
|
+
or (
|
|
31415
|
+
ranked_sessions.activity_revision = ${options.cursor.sortRevision}::text::bigint
|
|
31416
|
+
and (
|
|
31417
|
+
ranked_sessions.updated_at < ${options.cursor.sortAt}::text::timestamptz
|
|
31418
|
+
or (
|
|
31419
|
+
ranked_sessions.updated_at = ${options.cursor.sortAt}::text::timestamptz
|
|
31420
|
+
and ranked_sessions.id < ${options.cursor.id}::uuid
|
|
31421
|
+
)
|
|
31422
|
+
)
|
|
31423
|
+
)
|
|
31424
|
+
)`
|
|
31425
|
+
: sql`(
|
|
31426
|
+
ranked_sessions.created_at < ${options.cursor.sortAt}::text::timestamptz
|
|
31427
|
+
or (
|
|
31428
|
+
ranked_sessions.created_at = ${options.cursor.sortAt}::text::timestamptz
|
|
31429
|
+
and ranked_sessions.id < ${options.cursor.id}::uuid
|
|
31430
|
+
)
|
|
31431
|
+
)`;
|
|
31432
|
+
const pageOrder =
|
|
31433
|
+
options.orderBy === "relevance"
|
|
31434
|
+
? sql`ranked_sessions.match_rank asc, ranked_sessions.activity_revision desc,
|
|
31435
|
+
ranked_sessions.updated_at desc, ranked_sessions.id desc`
|
|
31436
|
+
: options.orderBy === "updatedAt"
|
|
31437
|
+
? sql`ranked_sessions.activity_revision desc, ranked_sessions.updated_at desc,
|
|
31438
|
+
ranked_sessions.id desc`
|
|
31439
|
+
: sql`ranked_sessions.created_at desc, ranked_sessions.id desc`;
|
|
31440
|
+
const outerOrder =
|
|
31441
|
+
options.orderBy === "relevance"
|
|
31442
|
+
? sql`page."sortRank" asc nulls last, page."sortRevision"::bigint desc nulls last,
|
|
31443
|
+
page."sortAt"::timestamptz desc nulls last, page.id desc nulls last`
|
|
31444
|
+
: options.orderBy === "updatedAt"
|
|
31445
|
+
? sql`page."sortRevision"::bigint desc nulls last,
|
|
31446
|
+
page."sortAt"::timestamptz desc nulls last, page.id desc nulls last`
|
|
31447
|
+
: sql`page."sortAt"::timestamptz desc nulls last, page.id desc nulls last`;
|
|
31448
|
+
const sortRevision =
|
|
31449
|
+
options.orderBy === "createdAt" ? sql`'0'::text` : sql`ranked_sessions.activity_revision::text`;
|
|
31450
|
+
const sortAt =
|
|
31451
|
+
options.orderBy === "createdAt"
|
|
31452
|
+
? sql`to_char(ranked_sessions.created_at at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`
|
|
31453
|
+
: sql`to_char(ranked_sessions.updated_at at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
|
|
31454
|
+
|
|
31455
|
+
const result = await rawRows<SessionDiscoveryPageRow>(
|
|
31456
|
+
db,
|
|
31457
|
+
sql`
|
|
31458
|
+
with authorized_sessions as materialized (
|
|
31459
|
+
select
|
|
31460
|
+
${schema.sessions.id} as id,
|
|
31461
|
+
${schema.sessions.title} as title,
|
|
31462
|
+
${schema.sessions.parentSessionId} as parent_session_id,
|
|
31463
|
+
${schema.sessions.rootSessionId} as root_session_id,
|
|
31464
|
+
${schema.sessions.nestedAgentDepth} as nested_agent_depth,
|
|
31465
|
+
${schema.sessions.status} as status,
|
|
31466
|
+
${schema.sessions.createdAt} as created_at,
|
|
31467
|
+
${schema.sessions.updatedAt} as updated_at,
|
|
31468
|
+
${schema.sessions.activityRevision} as activity_revision
|
|
31469
|
+
from ${schema.sessions}
|
|
31470
|
+
where ${authorizedPredicate}
|
|
31471
|
+
),
|
|
31472
|
+
subject_scoped_sessions as materialized (
|
|
31473
|
+
select * from authorized_sessions where ${subjectScopePredicate}
|
|
31474
|
+
)
|
|
31475
|
+
${rankingCtes}
|
|
31476
|
+
${rankedCte}
|
|
31477
|
+
select
|
|
31478
|
+
page.id,
|
|
31479
|
+
page.title,
|
|
31480
|
+
page."titleOriginalChars",
|
|
31481
|
+
page."parentSessionId",
|
|
31482
|
+
page."rootSessionId",
|
|
31483
|
+
page."nestedAgentDepth",
|
|
31484
|
+
page.status,
|
|
31485
|
+
page."createdAt",
|
|
31486
|
+
page."updatedAt",
|
|
31487
|
+
page."sortRevision",
|
|
31488
|
+
page."sortAt",
|
|
31489
|
+
page."sortRank",
|
|
31490
|
+
page."matchClass",
|
|
31491
|
+
page."matchedField",
|
|
31492
|
+
page."scoreBand",
|
|
31493
|
+
page."matchedClaimId",
|
|
31494
|
+
totals.total
|
|
31495
|
+
from (select count(*)::integer as total from ranked_sessions) totals
|
|
31496
|
+
left join lateral (
|
|
31497
|
+
select
|
|
31498
|
+
ranked_sessions.id,
|
|
31499
|
+
left(ranked_sessions.title, ${SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS}) as title,
|
|
31500
|
+
char_length(ranked_sessions.title)::integer as "titleOriginalChars",
|
|
31501
|
+
ranked_sessions.parent_session_id as "parentSessionId",
|
|
31502
|
+
ranked_sessions.root_session_id as "rootSessionId",
|
|
31503
|
+
ranked_sessions.nested_agent_depth as "nestedAgentDepth",
|
|
31504
|
+
ranked_sessions.status,
|
|
31505
|
+
ranked_sessions.created_at as "createdAt",
|
|
31506
|
+
ranked_sessions.updated_at as "updatedAt",
|
|
31507
|
+
${sortRevision} as "sortRevision",
|
|
31508
|
+
${sortAt} as "sortAt",
|
|
31509
|
+
ranked_sessions.match_rank as "sortRank",
|
|
31510
|
+
ranked_sessions.match_class as "matchClass",
|
|
31511
|
+
ranked_sessions.matched_field as "matchedField",
|
|
31512
|
+
ranked_sessions.score_band as "scoreBand",
|
|
31513
|
+
ranked_sessions.matched_claim_id as "matchedClaimId"
|
|
31514
|
+
from ranked_sessions
|
|
31515
|
+
where ${cursorPredicate}
|
|
31516
|
+
order by ${pageOrder}
|
|
31517
|
+
limit ${options.limit + 1}
|
|
31518
|
+
) page on true
|
|
31519
|
+
order by ${outerOrder}
|
|
31520
|
+
`,
|
|
31521
|
+
);
|
|
31522
|
+
const total = Number(result[0]?.total ?? 0);
|
|
31523
|
+
return { rows: result.filter((row) => row.id !== null), total };
|
|
31524
|
+
}
|
|
31525
|
+
|
|
31526
|
+
async function sessionDiscoveryClaimsForSessions(
|
|
31527
|
+
db: Database,
|
|
31528
|
+
workspaceId: string,
|
|
31529
|
+
sessionIds: string[],
|
|
31530
|
+
options: {
|
|
31531
|
+
snapshotAt: string;
|
|
31532
|
+
activeOnly: boolean;
|
|
31533
|
+
recentHours: number | null;
|
|
31534
|
+
limit: number;
|
|
31535
|
+
matchedClaimIds: ReadonlyMap<string, string | null>;
|
|
31536
|
+
},
|
|
31537
|
+
): Promise<Map<string, { claims: WorkClaimDiscoverySummary[]; truncated: boolean }>> {
|
|
31538
|
+
if (sessionIds.length === 0 || options.limit < 1) return new Map();
|
|
31539
|
+
const claimStatePredicate =
|
|
31540
|
+
options.activeOnly || options.recentHours === null
|
|
31541
|
+
? sql`${schema.sessionWorkClaims.state} = 'active'`
|
|
31542
|
+
: sql`(${schema.sessionWorkClaims.state} = 'active' or
|
|
31543
|
+
${schema.sessionWorkClaims.updatedAt} >= ${options.snapshotAt}::text::timestamptz
|
|
31544
|
+
- (${options.recentHours}::integer * interval '1 hour'))`;
|
|
31545
|
+
const rows = await rawRows<SessionDiscoveryClaimRow>(
|
|
31546
|
+
db,
|
|
31547
|
+
sql`
|
|
31548
|
+
with requested_sessions(session_id, matched_claim_id) as (
|
|
31549
|
+
values ${sql.join(
|
|
31550
|
+
sessionIds.map(
|
|
31551
|
+
(sessionId) =>
|
|
31552
|
+
sql`(${sessionId}::uuid, ${options.matchedClaimIds.get(sessionId) ?? null}::uuid)`,
|
|
31553
|
+
),
|
|
31554
|
+
sql`, `,
|
|
31555
|
+
)}
|
|
31556
|
+
)
|
|
31557
|
+
select
|
|
31558
|
+
requested_sessions.session_id as "sessionId",
|
|
31559
|
+
claim.id,
|
|
31560
|
+
claim.subject_namespace as "subjectNamespace",
|
|
31561
|
+
claim.subject_type as "subjectType",
|
|
31562
|
+
claim.canonical_key as "canonicalKey",
|
|
31563
|
+
claim.display_label as "displayLabel",
|
|
31564
|
+
claim.role,
|
|
31565
|
+
claim.state,
|
|
31566
|
+
claim.revision,
|
|
31567
|
+
claim.provenance,
|
|
31568
|
+
claim.version_kind as "versionKind",
|
|
31569
|
+
claim.version_value as "versionValue",
|
|
31570
|
+
claim.observed_at as "observedAt",
|
|
31571
|
+
claim.updated_at as "updatedAt",
|
|
31572
|
+
claim.settled_at as "settledAt",
|
|
31573
|
+
claim.available_count as "availableCount"
|
|
31574
|
+
from requested_sessions
|
|
31575
|
+
cross join lateral (
|
|
31576
|
+
select
|
|
31577
|
+
${schema.sessionWorkClaims.id} as id,
|
|
31578
|
+
${schema.sessionWorkClaims.subjectNamespace} as subject_namespace,
|
|
31579
|
+
${schema.sessionWorkClaims.subjectType} as subject_type,
|
|
31580
|
+
${schema.sessionWorkClaims.canonicalKey} as canonical_key,
|
|
31581
|
+
${schema.sessionWorkClaims.displayLabel} as display_label,
|
|
31582
|
+
${schema.sessionWorkClaims.role} as role,
|
|
31583
|
+
${schema.sessionWorkClaims.state} as state,
|
|
31584
|
+
${schema.sessionWorkClaims.revision} as revision,
|
|
31585
|
+
${schema.sessionWorkClaims.provenance} as provenance,
|
|
31586
|
+
${schema.sessionWorkClaims.versionKind} as version_kind,
|
|
31587
|
+
${schema.sessionWorkClaims.versionValue} as version_value,
|
|
31588
|
+
${schema.sessionWorkClaims.observedAt} as observed_at,
|
|
31589
|
+
${schema.sessionWorkClaims.updatedAt} as updated_at,
|
|
31590
|
+
${schema.sessionWorkClaims.settledAt} as settled_at,
|
|
31591
|
+
count(*) over()::integer as available_count
|
|
31592
|
+
from ${schema.sessionWorkClaims}
|
|
31593
|
+
where ${schema.sessionWorkClaims.workspaceId} = ${workspaceId}
|
|
31594
|
+
and ${schema.sessionWorkClaims.sessionId} = requested_sessions.session_id
|
|
31595
|
+
and ${claimStatePredicate}
|
|
31596
|
+
order by
|
|
31597
|
+
(
|
|
31598
|
+
requested_sessions.matched_claim_id is not null
|
|
31599
|
+
and ${schema.sessionWorkClaims.id} = requested_sessions.matched_claim_id
|
|
31600
|
+
) desc,
|
|
31601
|
+
(${schema.sessionWorkClaims.state} = 'active') desc,
|
|
31602
|
+
${schema.sessionWorkClaims.updatedAt} desc,
|
|
31603
|
+
${schema.sessionWorkClaims.id} desc
|
|
31604
|
+
limit ${options.limit}
|
|
31605
|
+
) claim
|
|
31606
|
+
order by requested_sessions.session_id,
|
|
31607
|
+
(
|
|
31608
|
+
requested_sessions.matched_claim_id is not null
|
|
31609
|
+
and claim.id = requested_sessions.matched_claim_id
|
|
31610
|
+
) desc,
|
|
31611
|
+
(claim.state = 'active') desc, claim.updated_at desc, claim.id desc
|
|
31612
|
+
`,
|
|
31613
|
+
);
|
|
31614
|
+
const grouped = new Map<string, { claims: WorkClaimDiscoverySummary[]; truncated: boolean }>();
|
|
31615
|
+
for (const row of rows) {
|
|
31616
|
+
const entry = grouped.get(row.sessionId) ?? { claims: [], truncated: false };
|
|
31617
|
+
entry.claims.push(
|
|
31618
|
+
WorkClaimDiscoverySummarySchema.parse({
|
|
31619
|
+
id: row.id,
|
|
31620
|
+
sessionId: row.sessionId,
|
|
31621
|
+
subject: {
|
|
31622
|
+
namespace: row.subjectNamespace,
|
|
31623
|
+
type: row.subjectType,
|
|
31624
|
+
canonicalKey: row.canonicalKey,
|
|
31625
|
+
displayLabel: row.displayLabel,
|
|
31626
|
+
},
|
|
31627
|
+
role: row.role,
|
|
31628
|
+
state: row.state,
|
|
31629
|
+
revision: Number(row.revision),
|
|
31630
|
+
provenance: row.provenance,
|
|
31631
|
+
version:
|
|
31632
|
+
row.versionKind === null || row.versionValue === null
|
|
31633
|
+
? null
|
|
31634
|
+
: { kind: row.versionKind, value: row.versionValue },
|
|
31635
|
+
observedAt: sessionDiscoveryIso(row.observedAt),
|
|
31636
|
+
updatedAt: sessionDiscoveryIso(row.updatedAt),
|
|
31637
|
+
settledAt: row.settledAt === null ? null : sessionDiscoveryIso(row.settledAt),
|
|
31638
|
+
}),
|
|
31639
|
+
);
|
|
31640
|
+
entry.truncated = Number(row.availableCount) > options.limit;
|
|
31641
|
+
grouped.set(row.sessionId, entry);
|
|
31642
|
+
}
|
|
31643
|
+
return grouped;
|
|
31644
|
+
}
|
|
31645
|
+
|
|
30928
31646
|
/**
|
|
30929
31647
|
* Excludes a human/API `user.message` whose accepted turn was never claimed.
|
|
30930
31648
|
* Such a prompt was never model input: it is waiting work (represented by
|
|
@@ -30988,7 +31706,17 @@ export async function listSessionDiscoverySummaries(
|
|
|
30988
31706
|
orderBy?: SessionDiscoveryOrderBy;
|
|
30989
31707
|
updatedAfter?: string;
|
|
30990
31708
|
parentSessionId?: string | null;
|
|
31709
|
+
rootSessionId?: string;
|
|
31710
|
+
query?: string;
|
|
31711
|
+
/** @deprecated use query; retained for the existing topology route. */
|
|
30991
31712
|
search?: string;
|
|
31713
|
+
statuses?: SessionStatus[];
|
|
31714
|
+
activeOnly?: boolean;
|
|
31715
|
+
recentHours?: number;
|
|
31716
|
+
subject?: WorkClaimSubjectFilter;
|
|
31717
|
+
claimLimit?: number;
|
|
31718
|
+
/** Skip advisory claim reads for an operator-disabled discovery rollout. */
|
|
31719
|
+
includeWorkDiscovery?: boolean;
|
|
30992
31720
|
subjectId?: string;
|
|
30993
31721
|
authorizationScope?: SessionAuthorizationListScope;
|
|
30994
31722
|
},
|
|
@@ -31002,330 +31730,306 @@ export async function listSessionDiscoverySummaries(
|
|
|
31002
31730
|
snapshotRevision: string;
|
|
31003
31731
|
updatedThrough: string | null;
|
|
31004
31732
|
updatedAfter: string | null;
|
|
31733
|
+
/** Internal relevance cursor binding; omitted from row projections. */
|
|
31734
|
+
filterHash: string | null;
|
|
31005
31735
|
}> {
|
|
31006
31736
|
const limit = Math.max(1, Math.min(100, Math.floor(options.limit)));
|
|
31007
|
-
const
|
|
31008
|
-
|
|
31009
|
-
|
|
31010
|
-
|
|
31011
|
-
|
|
31012
|
-
|
|
31013
|
-
|
|
31014
|
-
|
|
31015
|
-
|
|
31016
|
-
|
|
31017
|
-
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
|
|
31026
|
-
|
|
31027
|
-
|
|
31028
|
-
|
|
31029
|
-
|
|
31030
|
-
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
|
|
31037
|
-
|
|
31038
|
-
|
|
31039
|
-
|
|
31040
|
-
|
|
31041
|
-
options.cursor.snapshotRevision,
|
|
31042
|
-
"cursor snapshot revision",
|
|
31043
|
-
)
|
|
31044
|
-
: await readWorkspaceSessionActivityRevision(scopedDb, workspaceId)
|
|
31045
|
-
: "0";
|
|
31046
|
-
const cursorSortRevision = options.cursor
|
|
31047
|
-
? normalizeSessionActivityRevision(options.cursor.sortRevision, "cursor sort revision")
|
|
31048
|
-
: null;
|
|
31737
|
+
const claimLimit = Math.max(
|
|
31738
|
+
1,
|
|
31739
|
+
Math.min(
|
|
31740
|
+
WORK_CLAIM_DISCOVERY_LIMIT,
|
|
31741
|
+
Math.floor(options.claimLimit ?? WORK_CLAIM_DISCOVERY_DEFAULT_LIMIT),
|
|
31742
|
+
),
|
|
31743
|
+
);
|
|
31744
|
+
const filters = normalizeSessionDiscoveryFilters(options);
|
|
31745
|
+
const relevanceRequested = filters.query !== null || filters.subject !== null;
|
|
31746
|
+
const orderBy =
|
|
31747
|
+
options.orderBy ?? options.cursor?.orderBy ?? (relevanceRequested ? "relevance" : "createdAt");
|
|
31748
|
+
if (relevanceRequested && orderBy !== "relevance") {
|
|
31749
|
+
throw new Error("sessions_list query and exact subject filters require relevance order");
|
|
31750
|
+
}
|
|
31751
|
+
if (!relevanceRequested && orderBy === "relevance") {
|
|
31752
|
+
throw new Error("sessions_list relevance order requires query or an exact subject filter");
|
|
31753
|
+
}
|
|
31754
|
+
const filterHash = relevanceRequested ? sessionDiscoveryFilterHash(filters) : null;
|
|
31755
|
+
const readPage = async (scopedDb: Database) => {
|
|
31756
|
+
const requestedUpdatedAfter = options.updatedAfter ?? options.cursor?.updatedAfter ?? null;
|
|
31757
|
+
const updatedAfter =
|
|
31758
|
+
requestedUpdatedAfter === null
|
|
31759
|
+
? null
|
|
31760
|
+
: normalizeSessionActivityRevision(requestedUpdatedAfter, "updatedAfter");
|
|
31761
|
+
if (options.cursor?.orderBy !== undefined && options.cursor.orderBy !== orderBy) {
|
|
31762
|
+
throw new Error("sessions_list cursor order does not match the request");
|
|
31763
|
+
}
|
|
31764
|
+
if (options.cursor && options.cursor.updatedAfter !== updatedAfter) {
|
|
31765
|
+
throw new Error("sessions_list cursor incremental filter does not match the request");
|
|
31766
|
+
}
|
|
31767
|
+
if (updatedAfter !== null && orderBy !== "updatedAt") {
|
|
31768
|
+
throw new Error("sessions_list updatedAfter requires orderBy=updatedAt");
|
|
31769
|
+
}
|
|
31770
|
+
if (options.cursor && orderBy === "relevance") {
|
|
31049
31771
|
if (
|
|
31050
|
-
|
|
31051
|
-
options.cursor
|
|
31052
|
-
|
|
31772
|
+
!Number.isSafeInteger(options.cursor.sortRank) ||
|
|
31773
|
+
options.cursor.sortRank === null ||
|
|
31774
|
+
options.cursor.sortRank < 0
|
|
31053
31775
|
) {
|
|
31054
|
-
throw new Error("sessions_list
|
|
31776
|
+
throw new Error("sessions_list relevance cursor rank is invalid");
|
|
31055
31777
|
}
|
|
31056
|
-
|
|
31057
|
-
|
|
31058
|
-
? or(
|
|
31059
|
-
sql`${schema.sessions.activityRevision} < ${cursorSortRevision!}::text::bigint`,
|
|
31060
|
-
and(
|
|
31061
|
-
sql`${schema.sessions.activityRevision} = ${cursorSortRevision!}::text::bigint`,
|
|
31062
|
-
or(
|
|
31063
|
-
sql`${schema.sessions.updatedAt} < ${options.cursor.sortAt}::text::timestamptz`,
|
|
31064
|
-
and(
|
|
31065
|
-
sql`${schema.sessions.updatedAt} = ${options.cursor.sortAt}::text::timestamptz`,
|
|
31066
|
-
lt(schema.sessions.id, options.cursor.id),
|
|
31067
|
-
),
|
|
31068
|
-
),
|
|
31069
|
-
),
|
|
31070
|
-
)
|
|
31071
|
-
: or(
|
|
31072
|
-
// Cast through text deliberately. postgres.js otherwise infers a
|
|
31073
|
-
// timestamptz parameter and serializes this exact cursor string via
|
|
31074
|
-
// JS Date, which discards PostgreSQL's sub-millisecond precision.
|
|
31075
|
-
sql`${schema.sessions.createdAt} < ${options.cursor.sortAt}::text::timestamptz`,
|
|
31076
|
-
and(
|
|
31077
|
-
sql`${schema.sessions.createdAt} = ${options.cursor.sortAt}::text::timestamptz`,
|
|
31078
|
-
lt(schema.sessions.id, options.cursor.id),
|
|
31079
|
-
),
|
|
31080
|
-
)
|
|
31081
|
-
: undefined;
|
|
31082
|
-
const snapshotFilters: SQL[] = [eq(schema.sessions.workspaceId, workspaceId)];
|
|
31083
|
-
if (options.subjectId) {
|
|
31084
|
-
snapshotFilters.push(sql`not exists (
|
|
31085
|
-
select 1
|
|
31086
|
-
from ${schema.slackInteractions} private_slack_interaction
|
|
31087
|
-
where private_slack_interaction.workspace_id = ${schema.sessions.workspaceId}
|
|
31088
|
-
and private_slack_interaction.session_reservation_id = ${schema.sessions.rootSessionId}
|
|
31089
|
-
and private_slack_interaction.visibility = 'private'
|
|
31090
|
-
and private_slack_interaction.owning_subject_id <> ${options.subjectId}
|
|
31091
|
-
)`);
|
|
31778
|
+
if (options.cursor.filterHash !== filterHash) {
|
|
31779
|
+
throw new Error("sessions_list cursor relevance filters do not match the request");
|
|
31092
31780
|
}
|
|
31093
|
-
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
|
|
31097
|
-
|
|
31098
|
-
|
|
31099
|
-
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
.
|
|
31107
|
-
|
|
31108
|
-
.replaceAll("_", "\\_")}%`;
|
|
31109
|
-
snapshotFilters.push(
|
|
31110
|
-
or(
|
|
31111
|
-
ilike(schema.sessions.title, pattern),
|
|
31112
|
-
ilike(schema.sessions.initialMessage, pattern),
|
|
31113
|
-
)!,
|
|
31114
|
-
);
|
|
31115
|
-
}
|
|
31116
|
-
snapshotFilters.push(
|
|
31117
|
-
orderBy === "updatedAt"
|
|
31118
|
-
? sql`${schema.sessions.activityRevision} <= ${snapshotRevision}::text::bigint`
|
|
31119
|
-
: sql`${schema.sessions.createdAt} <= ${snapshotAt}::text::timestamptz`,
|
|
31120
|
-
);
|
|
31121
|
-
if (updatedAfter !== null) {
|
|
31122
|
-
snapshotFilters.push(
|
|
31123
|
-
sql`${schema.sessions.activityRevision} > ${updatedAfter}::text::bigint`,
|
|
31124
|
-
);
|
|
31125
|
-
}
|
|
31126
|
-
const rows = await scopedDb
|
|
31127
|
-
.select({
|
|
31128
|
-
id: schema.sessions.id,
|
|
31129
|
-
title: sql<
|
|
31130
|
-
string | null
|
|
31131
|
-
>`left(${schema.sessions.title}, ${SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS})`,
|
|
31132
|
-
titleOriginalChars: sql<number | null>`char_length(${schema.sessions.title})::integer`,
|
|
31133
|
-
parentSessionId: schema.sessions.parentSessionId,
|
|
31134
|
-
rootSessionId: schema.sessions.rootSessionId,
|
|
31135
|
-
nestedAgentDepth: schema.sessions.nestedAgentDepth,
|
|
31136
|
-
status: schema.sessions.status,
|
|
31137
|
-
createdAt: schema.sessions.createdAt,
|
|
31138
|
-
updatedAt: schema.sessions.updatedAt,
|
|
31139
|
-
sortRevision:
|
|
31140
|
-
orderBy === "updatedAt"
|
|
31141
|
-
? sql<string>`${schema.sessions.activityRevision}::text`
|
|
31142
|
-
: sql<string>`'0'`,
|
|
31143
|
-
sortAt:
|
|
31144
|
-
orderBy === "updatedAt"
|
|
31145
|
-
? sql<string>`to_char(${schema.sessions.updatedAt} at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`
|
|
31146
|
-
: sql<string>`to_char(${schema.sessions.createdAt} at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,
|
|
31147
|
-
})
|
|
31148
|
-
.from(schema.sessions)
|
|
31149
|
-
.where(and(...snapshotFilters, cursorPredicate))
|
|
31150
|
-
.orderBy(
|
|
31151
|
-
...(orderBy === "updatedAt"
|
|
31152
|
-
? [
|
|
31153
|
-
desc(schema.sessions.activityRevision),
|
|
31154
|
-
desc(schema.sessions.updatedAt),
|
|
31155
|
-
desc(schema.sessions.id),
|
|
31156
|
-
]
|
|
31157
|
-
: [desc(schema.sessions.createdAt), desc(schema.sessions.id)]),
|
|
31781
|
+
} else if (
|
|
31782
|
+
options.cursor &&
|
|
31783
|
+
(options.cursor.sortRank !== null || options.cursor.filterHash !== null)
|
|
31784
|
+
) {
|
|
31785
|
+
throw new Error("sessions_list chronological cursor cannot carry relevance fields");
|
|
31786
|
+
}
|
|
31787
|
+
const snapshotAt =
|
|
31788
|
+
options.cursor?.snapshotAt ??
|
|
31789
|
+
(
|
|
31790
|
+
await rawRows<{ value: string }>(
|
|
31791
|
+
scopedDb,
|
|
31792
|
+
// The transaction-start cutoff is no later than any statement or
|
|
31793
|
+
// repeatable-read snapshot used below. Later rows therefore belong
|
|
31794
|
+
// to the next traversal instead of entering this cursor mid-scan.
|
|
31795
|
+
sql`select to_char(transaction_timestamp() at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') as value`,
|
|
31158
31796
|
)
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31170
|
-
|
|
31171
|
-
|
|
31172
|
-
|
|
31173
|
-
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31797
|
+
)[0]!.value;
|
|
31798
|
+
const snapshotRevision =
|
|
31799
|
+
orderBy !== "createdAt"
|
|
31800
|
+
? options.cursor?.snapshotRevision !== undefined
|
|
31801
|
+
? normalizeSessionActivityRevision(
|
|
31802
|
+
options.cursor.snapshotRevision,
|
|
31803
|
+
"cursor snapshot revision",
|
|
31804
|
+
)
|
|
31805
|
+
: await readWorkspaceSessionActivityRevision(scopedDb, workspaceId)
|
|
31806
|
+
: "0";
|
|
31807
|
+
const cursorSortRevision = options.cursor
|
|
31808
|
+
? normalizeSessionActivityRevision(options.cursor.sortRevision, "cursor sort revision")
|
|
31809
|
+
: null;
|
|
31810
|
+
if (
|
|
31811
|
+
orderBy === "createdAt" &&
|
|
31812
|
+
options.cursor &&
|
|
31813
|
+
(cursorSortRevision !== "0" ||
|
|
31814
|
+
options.cursor.snapshotRevision !== "0" ||
|
|
31815
|
+
options.cursor.sortRank !== null ||
|
|
31816
|
+
options.cursor.filterHash !== null)
|
|
31817
|
+
) {
|
|
31818
|
+
throw new Error("sessions_list created-order cursor cannot carry activity revisions");
|
|
31819
|
+
}
|
|
31820
|
+
const selected = await selectSessionDiscoveryPageRows(scopedDb, workspaceId, {
|
|
31821
|
+
limit,
|
|
31822
|
+
orderBy,
|
|
31823
|
+
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
31824
|
+
snapshotAt,
|
|
31825
|
+
snapshotRevision,
|
|
31826
|
+
updatedAfter,
|
|
31827
|
+
...(options.subjectId ? { subjectId: options.subjectId } : {}),
|
|
31828
|
+
...(options.authorizationScope ? { authorizationScope: options.authorizationScope } : {}),
|
|
31829
|
+
filters,
|
|
31830
|
+
});
|
|
31831
|
+
const hasMore = selected.rows.length > limit;
|
|
31832
|
+
const page = selected.rows.slice(0, limit);
|
|
31833
|
+
const ids = page.map((row) => row.id!);
|
|
31834
|
+
if (ids.length === 0) {
|
|
31835
|
+
return {
|
|
31836
|
+
sessions: [],
|
|
31837
|
+
hasMore: false,
|
|
31838
|
+
nextCursor: null,
|
|
31839
|
+
total: selected.total,
|
|
31840
|
+
orderBy,
|
|
31841
|
+
snapshotAt,
|
|
31842
|
+
snapshotRevision,
|
|
31843
|
+
updatedThrough: orderBy === "updatedAt" ? snapshotRevision : null,
|
|
31844
|
+
updatedAfter,
|
|
31845
|
+
filterHash,
|
|
31846
|
+
};
|
|
31847
|
+
}
|
|
31180
31848
|
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
|
|
31193
|
-
|
|
31194
|
-
|
|
31195
|
-
|
|
31196
|
-
|
|
31197
|
-
})
|
|
31198
|
-
.from(schema.sessionGoals)
|
|
31199
|
-
.where(
|
|
31200
|
-
and(
|
|
31201
|
-
eq(schema.sessionGoals.workspaceId, workspaceId),
|
|
31202
|
-
inArray(schema.sessionGoals.sessionId, ids),
|
|
31203
|
-
),
|
|
31204
|
-
);
|
|
31205
|
-
const goalsBySession = new Map(goals.map((goal) => [goal.sessionId, goal]));
|
|
31206
|
-
const queueCounts = await scopedDb
|
|
31207
|
-
.select({
|
|
31208
|
-
sessionId: schema.sessionTurns.sessionId,
|
|
31209
|
-
count: sql<number>`count(*)::int`,
|
|
31849
|
+
const rootRelatedIds = await sessionIdsCoveredByAuthorizationRoots(
|
|
31850
|
+
scopedDb,
|
|
31851
|
+
workspaceId,
|
|
31852
|
+
ids,
|
|
31853
|
+
options.authorizationScope,
|
|
31854
|
+
);
|
|
31855
|
+
const controls = await evaluateSessionDiscoveryControls(scopedDb, workspaceId, ids);
|
|
31856
|
+
const treeStats = await sessionTreeStatsForSessions(scopedDb, workspaceId, [...rootRelatedIds]);
|
|
31857
|
+
const includeWorkDiscovery = options.includeWorkDiscovery !== false;
|
|
31858
|
+
const workClaims = includeWorkDiscovery
|
|
31859
|
+
? await sessionDiscoveryClaimsForSessions(scopedDb, workspaceId, ids, {
|
|
31860
|
+
snapshotAt,
|
|
31861
|
+
activeOnly: filters.activeOnly,
|
|
31862
|
+
recentHours: filters.recentHours,
|
|
31863
|
+
limit: claimLimit,
|
|
31864
|
+
matchedClaimIds: new Map(page.map((row) => [row.id!, row.matchedClaimId] as const)),
|
|
31210
31865
|
})
|
|
31211
|
-
|
|
31212
|
-
|
|
31213
|
-
|
|
31214
|
-
|
|
31215
|
-
|
|
31216
|
-
|
|
31217
|
-
|
|
31218
|
-
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31866
|
+
: new Map<string, { claims: WorkClaimDiscoverySummary[]; truncated: boolean }>();
|
|
31867
|
+
const goals = await scopedDb
|
|
31868
|
+
.select({
|
|
31869
|
+
sessionId: schema.sessionGoals.sessionId,
|
|
31870
|
+
status: schema.sessionGoals.status,
|
|
31871
|
+
text: sql<string>`left(${schema.sessionGoals.text}, ${SESSION_DISCOVERY_GOAL_MAX_CHARS})`,
|
|
31872
|
+
textOriginalChars: sql<number>`char_length(${schema.sessionGoals.text})::integer`,
|
|
31873
|
+
})
|
|
31874
|
+
.from(schema.sessionGoals)
|
|
31875
|
+
.where(
|
|
31876
|
+
and(
|
|
31877
|
+
eq(schema.sessionGoals.workspaceId, workspaceId),
|
|
31878
|
+
inArray(schema.sessionGoals.sessionId, ids),
|
|
31879
|
+
),
|
|
31223
31880
|
);
|
|
31224
|
-
|
|
31225
|
-
|
|
31226
|
-
|
|
31227
|
-
|
|
31228
|
-
|
|
31229
|
-
|
|
31230
|
-
|
|
31231
|
-
|
|
31232
|
-
|
|
31881
|
+
const goalsBySession = new Map(goals.map((goal) => [goal.sessionId, goal]));
|
|
31882
|
+
const queueCounts = await scopedDb
|
|
31883
|
+
.select({
|
|
31884
|
+
sessionId: schema.sessionTurns.sessionId,
|
|
31885
|
+
count: sql<number>`count(*)::int`,
|
|
31886
|
+
})
|
|
31887
|
+
.from(schema.sessionTurns)
|
|
31888
|
+
.where(
|
|
31889
|
+
and(
|
|
31890
|
+
eq(schema.sessionTurns.workspaceId, workspaceId),
|
|
31891
|
+
inArray(schema.sessionTurns.sessionId, ids),
|
|
31892
|
+
eq(schema.sessionTurns.status, "queued"),
|
|
31893
|
+
inArray(schema.sessionTurns.source, ["user", "api"]),
|
|
31894
|
+
),
|
|
31895
|
+
)
|
|
31896
|
+
.groupBy(schema.sessionTurns.sessionId);
|
|
31897
|
+
const queueBySession = new Map(
|
|
31898
|
+
queueCounts.map((entry) => [entry.sessionId, Number(entry.count)]),
|
|
31899
|
+
);
|
|
31900
|
+
const latestMessages = options.includeLastMessage
|
|
31901
|
+
? await scopedDb
|
|
31902
|
+
.selectDistinctOn([schema.sessionEvents.sessionId], {
|
|
31903
|
+
sessionId: schema.sessionEvents.sessionId,
|
|
31904
|
+
type: schema.sessionEvents.type,
|
|
31905
|
+
// Extract only the bounded textual preview in PostgreSQL. Selecting
|
|
31906
|
+
// the JSON payload here would re-materialize the exact multi-MB
|
|
31907
|
+
// event bodies this compact discovery path exists to avoid.
|
|
31908
|
+
preview: sql<string | null>`left(coalesce(
|
|
31233
31909
|
${schema.sessionEvents.payload}->>'text',
|
|
31234
31910
|
${schema.sessionEvents.payload}->>'message',
|
|
31235
31911
|
${schema.sessionEvents.payload}->>'content'
|
|
31236
31912
|
), ${SESSION_DISCOVERY_MESSAGE_MAX_CHARS})`,
|
|
31237
|
-
|
|
31913
|
+
previewOriginalChars: sql<number | null>`char_length(coalesce(
|
|
31238
31914
|
${schema.sessionEvents.payload}->>'text',
|
|
31239
31915
|
${schema.sessionEvents.payload}->>'message',
|
|
31240
31916
|
${schema.sessionEvents.payload}->>'content'
|
|
31241
31917
|
))::integer`,
|
|
31242
|
-
|
|
31243
|
-
|
|
31244
|
-
|
|
31245
|
-
|
|
31246
|
-
|
|
31247
|
-
|
|
31248
|
-
|
|
31249
|
-
|
|
31250
|
-
|
|
31251
|
-
|
|
31252
|
-
|
|
31253
|
-
|
|
31254
|
-
|
|
31255
|
-
|
|
31256
|
-
|
|
31257
|
-
|
|
31258
|
-
|
|
31259
|
-
|
|
31260
|
-
|
|
31261
|
-
|
|
31262
|
-
|
|
31263
|
-
|
|
31264
|
-
|
|
31265
|
-
|
|
31266
|
-
|
|
31267
|
-
|
|
31268
|
-
|
|
31269
|
-
|
|
31270
|
-
|
|
31271
|
-
|
|
31272
|
-
|
|
31273
|
-
|
|
31274
|
-
|
|
31275
|
-
goal: goal
|
|
31276
|
-
? {
|
|
31277
|
-
status: goal.status as SessionGoalStatus,
|
|
31278
|
-
text: goal.text,
|
|
31279
|
-
textOriginalChars: Number(goal.textOriginalChars),
|
|
31280
|
-
}
|
|
31281
|
-
: null,
|
|
31282
|
-
queuedPromptCount: queueBySession.get(row.id) ?? 0,
|
|
31283
|
-
treeStats:
|
|
31284
|
-
relatedAccess === "root"
|
|
31285
|
-
? (treeStats.get(row.id) ?? EMPTY_SESSION_TREE_STATS)
|
|
31286
|
-
: EMPTY_SESSION_TREE_STATS,
|
|
31287
|
-
latestMessage: latest
|
|
31288
|
-
? {
|
|
31289
|
-
type: latest.type as SessionEventType,
|
|
31290
|
-
preview: latest.preview,
|
|
31291
|
-
previewOriginalChars:
|
|
31292
|
-
latest.previewOriginalChars === null ? null : Number(latest.previewOriginalChars),
|
|
31293
|
-
}
|
|
31294
|
-
: null,
|
|
31295
|
-
createdAt: row.createdAt.toISOString(),
|
|
31296
|
-
updatedAt: row.updatedAt.toISOString(),
|
|
31297
|
-
sortRevision: row.sortRevision,
|
|
31298
|
-
sortAt: row.sortAt,
|
|
31299
|
-
};
|
|
31300
|
-
});
|
|
31301
|
-
const last = page.at(-1);
|
|
31918
|
+
})
|
|
31919
|
+
.from(schema.sessionEvents)
|
|
31920
|
+
.where(
|
|
31921
|
+
and(
|
|
31922
|
+
eq(schema.sessionEvents.workspaceId, workspaceId),
|
|
31923
|
+
inArray(schema.sessionEvents.sessionId, ids),
|
|
31924
|
+
inArray(schema.sessionEvents.type, ["user.message", "agent.message.completed"]),
|
|
31925
|
+
excludeUnclaimedHumanPromptEventFilter(workspaceId),
|
|
31926
|
+
),
|
|
31927
|
+
)
|
|
31928
|
+
.orderBy(schema.sessionEvents.sessionId, desc(schema.sessionEvents.sequence))
|
|
31929
|
+
: [];
|
|
31930
|
+
const latestBySession = new Map(latestMessages.map((entry) => [entry.sessionId, entry]));
|
|
31931
|
+
const sessions = page.map((row): SessionDiscoverySummary => {
|
|
31932
|
+
const sessionId = row.id!;
|
|
31933
|
+
const control = controls.get(sessionId);
|
|
31934
|
+
if (!control) throw new Error(`Effective control missing for session ${sessionId}`);
|
|
31935
|
+
const relatedAccess = rootRelatedIds.has(sessionId) ? "root" : "target";
|
|
31936
|
+
const goal = goalsBySession.get(sessionId);
|
|
31937
|
+
const latest = latestBySession.get(sessionId);
|
|
31938
|
+
const claims = workClaims.get(sessionId) ?? { claims: [], truncated: false };
|
|
31939
|
+
const match =
|
|
31940
|
+
!includeWorkDiscovery ||
|
|
31941
|
+
row.matchClass === null ||
|
|
31942
|
+
row.matchedField === null ||
|
|
31943
|
+
row.scoreBand === null
|
|
31944
|
+
? null
|
|
31945
|
+
: WorkDiscoveryMatchSchema.parse({
|
|
31946
|
+
class: row.matchClass,
|
|
31947
|
+
field: row.matchedField,
|
|
31948
|
+
scoreBand: row.scoreBand,
|
|
31949
|
+
claimId: row.matchedClaimId,
|
|
31950
|
+
});
|
|
31302
31951
|
return {
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
|
|
31306
|
-
|
|
31307
|
-
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
|
|
31311
|
-
|
|
31312
|
-
|
|
31313
|
-
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31318
|
-
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31952
|
+
id: sessionId,
|
|
31953
|
+
title: row.title,
|
|
31954
|
+
titleOriginalChars: row.titleOriginalChars === null ? null : Number(row.titleOriginalChars),
|
|
31955
|
+
parentSessionId: relatedAccess === "root" ? row.parentSessionId : null,
|
|
31956
|
+
rootSessionId: relatedAccess === "root" ? row.rootSessionId! : sessionId,
|
|
31957
|
+
nestedAgentDepth: Number(row.nestedAgentDepth),
|
|
31958
|
+
status: row.status as SessionStatus,
|
|
31959
|
+
effectiveControl: projectSessionDiscoveryControlForRelatedAccess(
|
|
31960
|
+
control,
|
|
31961
|
+
sessionId,
|
|
31962
|
+
relatedAccess,
|
|
31963
|
+
),
|
|
31964
|
+
goal: goal
|
|
31965
|
+
? {
|
|
31966
|
+
status: goal.status as SessionGoalStatus,
|
|
31967
|
+
text: goal.text,
|
|
31968
|
+
textOriginalChars: Number(goal.textOriginalChars),
|
|
31969
|
+
}
|
|
31970
|
+
: null,
|
|
31971
|
+
queuedPromptCount: queueBySession.get(sessionId) ?? 0,
|
|
31972
|
+
treeStats:
|
|
31973
|
+
relatedAccess === "root"
|
|
31974
|
+
? (treeStats.get(sessionId) ?? EMPTY_SESSION_TREE_STATS)
|
|
31975
|
+
: EMPTY_SESSION_TREE_STATS,
|
|
31976
|
+
latestMessage: latest
|
|
31977
|
+
? {
|
|
31978
|
+
type: latest.type as SessionEventType,
|
|
31979
|
+
preview: latest.preview,
|
|
31980
|
+
previewOriginalChars:
|
|
31981
|
+
latest.previewOriginalChars === null ? null : Number(latest.previewOriginalChars),
|
|
31982
|
+
}
|
|
31983
|
+
: null,
|
|
31984
|
+
workDiscovery: {
|
|
31985
|
+
claims: claims.claims,
|
|
31986
|
+
claimsTruncated: claims.truncated,
|
|
31987
|
+
match,
|
|
31988
|
+
possibleOverlap: match !== null,
|
|
31989
|
+
advisoryOnly: true,
|
|
31990
|
+
noAdditionalAccess: true,
|
|
31991
|
+
},
|
|
31992
|
+
createdAt: sessionDiscoveryIso(row.createdAt!),
|
|
31993
|
+
updatedAt: sessionDiscoveryIso(row.updatedAt!),
|
|
31994
|
+
sortRevision: row.sortRevision!,
|
|
31995
|
+
sortAt: row.sortAt!,
|
|
31996
|
+
sortRank: row.sortRank === null ? null : Number(row.sortRank),
|
|
31323
31997
|
};
|
|
31324
|
-
}
|
|
31325
|
-
|
|
31326
|
-
|
|
31327
|
-
|
|
31328
|
-
|
|
31998
|
+
});
|
|
31999
|
+
const last = page.at(-1);
|
|
32000
|
+
return {
|
|
32001
|
+
sessions,
|
|
32002
|
+
hasMore,
|
|
32003
|
+
nextCursor:
|
|
32004
|
+
hasMore && last
|
|
32005
|
+
? {
|
|
32006
|
+
orderBy,
|
|
32007
|
+
sortRank: last.sortRank === null ? null : Number(last.sortRank),
|
|
32008
|
+
sortRevision: last.sortRevision!,
|
|
32009
|
+
sortAt: last.sortAt!,
|
|
32010
|
+
id: last.id!,
|
|
32011
|
+
snapshotAt,
|
|
32012
|
+
snapshotRevision,
|
|
32013
|
+
updatedAfter,
|
|
32014
|
+
filterHash,
|
|
32015
|
+
}
|
|
32016
|
+
: null,
|
|
32017
|
+
total: selected.total,
|
|
32018
|
+
orderBy,
|
|
32019
|
+
snapshotAt,
|
|
32020
|
+
snapshotRevision,
|
|
32021
|
+
updatedThrough: orderBy === "updatedAt" ? snapshotRevision : null,
|
|
32022
|
+
updatedAfter,
|
|
32023
|
+
filterHash,
|
|
32024
|
+
};
|
|
32025
|
+
};
|
|
32026
|
+
const transactionConfig: PgTransactionConfig =
|
|
32027
|
+
orderBy === "createdAt"
|
|
32028
|
+
? { isolationLevel: "read committed", accessMode: "read only" }
|
|
32029
|
+
: { isolationLevel: "repeatable read", accessMode: "read only" };
|
|
32030
|
+
return options.subjectId
|
|
32031
|
+
? await withWorkspaceSubjectRls(db, workspaceId, options.subjectId, readPage, transactionConfig)
|
|
32032
|
+
: await withWorkspaceRls(db, workspaceId, readPage, transactionConfig);
|
|
31329
32033
|
}
|
|
31330
32034
|
|
|
31331
32035
|
export type SessionDiscoveryAncestor = {
|
|
@@ -31344,10 +32048,34 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31344
32048
|
workspaceId: string,
|
|
31345
32049
|
sessionIds: string[],
|
|
31346
32050
|
authorizationScope?: SessionAuthorizationListScope,
|
|
32051
|
+
subjectId?: string,
|
|
31347
32052
|
): Promise<Map<string, SessionDiscoveryAncestor[]>> {
|
|
31348
32053
|
const targetIds = [...new Set(sessionIds)].slice(0, 100);
|
|
31349
32054
|
if (targetIds.length === 0) return new Map();
|
|
31350
|
-
|
|
32055
|
+
const readPaths = async (scopedDb: Database) => {
|
|
32056
|
+
const authorizationFilters: SQL[] = [eq(schema.sessions.workspaceId, workspaceId)];
|
|
32057
|
+
authorizationFilters.push(
|
|
32058
|
+
subjectId
|
|
32059
|
+
? sql`not exists (
|
|
32060
|
+
select 1
|
|
32061
|
+
from ${schema.slackInteractions} private_slack_interaction
|
|
32062
|
+
where private_slack_interaction.workspace_id = ${schema.sessions.workspaceId}
|
|
32063
|
+
and private_slack_interaction.session_reservation_id = ${schema.sessions.rootSessionId}
|
|
32064
|
+
and private_slack_interaction.visibility = 'private'
|
|
32065
|
+
and private_slack_interaction.owning_subject_id <> ${subjectId}
|
|
32066
|
+
)`
|
|
32067
|
+
: sql`not exists (
|
|
32068
|
+
select 1
|
|
32069
|
+
from ${schema.slackInteractions} private_slack_interaction
|
|
32070
|
+
where private_slack_interaction.workspace_id = ${schema.sessions.workspaceId}
|
|
32071
|
+
and private_slack_interaction.session_reservation_id = ${schema.sessions.rootSessionId}
|
|
32072
|
+
and private_slack_interaction.visibility = 'private'
|
|
32073
|
+
)`,
|
|
32074
|
+
);
|
|
32075
|
+
if (authorizationScope) {
|
|
32076
|
+
authorizationFilters.push(sessionAuthorizationScopeFilter(authorizationScope));
|
|
32077
|
+
}
|
|
32078
|
+
const authorizedPredicate = and(...authorizationFilters) ?? sql`false`;
|
|
31351
32079
|
const rows = await rawRows<{
|
|
31352
32080
|
targetId: string;
|
|
31353
32081
|
id: string;
|
|
@@ -31358,7 +32086,15 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31358
32086
|
}>(
|
|
31359
32087
|
scopedDb,
|
|
31360
32088
|
sql`
|
|
31361
|
-
with recursive
|
|
32089
|
+
with recursive authorized_sessions as materialized (
|
|
32090
|
+
select
|
|
32091
|
+
${schema.sessions.id} as id,
|
|
32092
|
+
${schema.sessions.parentSessionId} as parent_session_id,
|
|
32093
|
+
${schema.sessions.title} as title
|
|
32094
|
+
from ${schema.sessions}
|
|
32095
|
+
where ${authorizedPredicate}
|
|
32096
|
+
),
|
|
32097
|
+
lineage as (
|
|
31362
32098
|
select
|
|
31363
32099
|
target.id as target_id,
|
|
31364
32100
|
target.id,
|
|
@@ -31367,9 +32103,8 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31367
32103
|
0::integer as depth,
|
|
31368
32104
|
array[target.id]::uuid[] as path,
|
|
31369
32105
|
false as cycle
|
|
31370
|
-
from
|
|
31371
|
-
where target.
|
|
31372
|
-
and ${inArray(sql`target.id`, targetIds)}
|
|
32106
|
+
from authorized_sessions target
|
|
32107
|
+
where ${inArray(sql`target.id`, targetIds)}
|
|
31373
32108
|
|
|
31374
32109
|
union all
|
|
31375
32110
|
|
|
@@ -31382,9 +32117,7 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31382
32117
|
child.path || parent.id,
|
|
31383
32118
|
parent.id = any(child.path)
|
|
31384
32119
|
from lineage child
|
|
31385
|
-
join
|
|
31386
|
-
on parent.workspace_id = ${workspaceId}
|
|
31387
|
-
and parent.id = child.parent_session_id
|
|
32120
|
+
join authorized_sessions parent on parent.id = child.parent_session_id
|
|
31388
32121
|
where not child.cycle
|
|
31389
32122
|
and child.depth < 64
|
|
31390
32123
|
)
|
|
@@ -31400,24 +32133,9 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31400
32133
|
order by target_id, depth desc
|
|
31401
32134
|
`,
|
|
31402
32135
|
);
|
|
31403
|
-
const candidateIds = [...new Set(rows.filter((row) => !row.cycle).map((row) => row.id))];
|
|
31404
|
-
let allowed = new Set(candidateIds);
|
|
31405
|
-
if (authorizationScope && authorizationScope.kind !== "all" && candidateIds.length > 0) {
|
|
31406
|
-
const allowedRows = await scopedDb
|
|
31407
|
-
.select({ id: schema.sessions.id })
|
|
31408
|
-
.from(schema.sessions)
|
|
31409
|
-
.where(
|
|
31410
|
-
and(
|
|
31411
|
-
eq(schema.sessions.workspaceId, workspaceId),
|
|
31412
|
-
inArray(schema.sessions.id, candidateIds),
|
|
31413
|
-
sessionAuthorizationScopeFilter(authorizationScope),
|
|
31414
|
-
),
|
|
31415
|
-
);
|
|
31416
|
-
allowed = new Set(allowedRows.map((row) => row.id));
|
|
31417
|
-
}
|
|
31418
32136
|
const result = new Map<string, SessionDiscoveryAncestor[]>();
|
|
31419
32137
|
for (const row of rows) {
|
|
31420
|
-
if (row.cycle
|
|
32138
|
+
if (row.cycle) continue;
|
|
31421
32139
|
const path = result.get(row.targetId) ?? [];
|
|
31422
32140
|
path.push({
|
|
31423
32141
|
id: row.id,
|
|
@@ -31427,7 +32145,10 @@ export async function listSessionDiscoveryAncestorPaths(
|
|
|
31427
32145
|
result.set(row.targetId, path);
|
|
31428
32146
|
}
|
|
31429
32147
|
return result;
|
|
31430
|
-
}
|
|
32148
|
+
};
|
|
32149
|
+
return subjectId
|
|
32150
|
+
? await withWorkspaceSubjectRls(db, workspaceId, subjectId, readPaths)
|
|
32151
|
+
: await withWorkspaceRls(db, workspaceId, readPaths);
|
|
31431
32152
|
}
|
|
31432
32153
|
|
|
31433
32154
|
export type SessionLineage = {
|