@tomflow/proflow-platform-host 0.1.30 → 0.1.32

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.
@@ -0,0 +1,13 @@
1
+ type MonitorPort = {
2
+ invoke(operation: string, input?: unknown): Promise<unknown>;
3
+ };
4
+ type ExecutionPort = {
5
+ invoke(operation: string, input: unknown): Promise<unknown>;
6
+ };
7
+ export declare function createMonitorDriveRelay(options: {
8
+ monitor: MonitorPort;
9
+ execution: ExecutionPort;
10
+ }): Readonly<{
11
+ drive(): Promise<unknown>;
12
+ }>;
13
+ export {};
@@ -0,0 +1,107 @@
1
+ import { parseExecuteCapabilityRequest, parseMonitorEffectReceipt, } from "@tomflow/proflow-execution-contracts";
2
+ function record(value, name) {
3
+ if (typeof value !== "object" || value === null || Array.isArray(value))
4
+ throw new Error(`${name}_INVALID`);
5
+ return value;
6
+ }
7
+ function string(value, name) {
8
+ if (typeof value !== "string" || value.length === 0)
9
+ throw new Error(`${name}_INVALID`);
10
+ return value;
11
+ }
12
+ function capability(value) {
13
+ if (value !== "monitor.chat.submit" && value !== "monitor.chat.create")
14
+ throw new Error("MONITOR_DRIVE_CAPABILITY_INVALID");
15
+ return value;
16
+ }
17
+ function parsePlan(value) {
18
+ if (value === null)
19
+ return null;
20
+ const raw = record(value, "MONITOR_DRIVE_PLAN");
21
+ return {
22
+ driveId: string(raw.driveId, "MONITOR_DRIVE_ID"),
23
+ capability: capability(raw.capability),
24
+ idempotencyKey: string(raw.idempotencyKey, "MONITOR_DRIVE_IDEMPOTENCY_KEY"),
25
+ request: record(raw.request, "MONITOR_DRIVE_REQUEST"),
26
+ };
27
+ }
28
+ export function createMonitorDriveRelay(options) {
29
+ let inFlight = null;
30
+ const driveOnce = async () => {
31
+ const plan = parsePlan(await options.monitor.invoke("drive.next", {}));
32
+ if (!plan) {
33
+ let notificationDelivery = "SETTLED";
34
+ try {
35
+ await options.monitor.invoke("notification.deliver", { limit: 10 });
36
+ }
37
+ catch {
38
+ notificationDelivery = "FAILED";
39
+ }
40
+ return { state: "IDLE", notificationDelivery };
41
+ }
42
+ if (plan.driveId !== plan.idempotencyKey)
43
+ throw new Error("MONITOR_EFFECT_IDENTITY_MISMATCH");
44
+ const request = parseExecuteCapabilityRequest({
45
+ contract: "execution",
46
+ contractVersion: "1.0.0",
47
+ callerRef: "platform-host:monitor-drive",
48
+ idempotencyKey: plan.idempotencyKey,
49
+ capability: plan.capability,
50
+ input: plan.request,
51
+ });
52
+ const authorization = record(await options.monitor.invoke("drive.authorize", { request }), "MONITOR_DRIVE_AUTHORIZATION");
53
+ if (authorization.authorized !== true)
54
+ throw new Error("MONITOR_DRIVE_NOT_AUTHORIZED");
55
+ let execution;
56
+ try {
57
+ execution = await options.execution.invoke("effect.execute", { effectId: plan.driveId, action: plan.capability, payload: plan.request });
58
+ }
59
+ catch {
60
+ // The effect may already be durable or applied. Preserve the pending
61
+ // drive; a later trigger reads the same durable effect; PENDING never replays.
62
+ return {
63
+ state: "PENDING",
64
+ driveId: plan.driveId,
65
+ errorCode: "EXECUTION_OWNER_RESPONSE_UNKNOWN",
66
+ };
67
+ }
68
+ const executionRecord = parseMonitorEffectReceipt(execution);
69
+ if (executionRecord.effectId !== plan.driveId || executionRecord.action !== plan.capability)
70
+ throw new Error("MONITOR_EFFECT_IDENTITY_MISMATCH");
71
+ let settlement;
72
+ try {
73
+ settlement = record(await options.monitor.invoke("drive.result", {
74
+ driveId: plan.driveId, execution: executionRecord,
75
+ }), "MONITOR_DRIVE_SETTLEMENT");
76
+ }
77
+ catch {
78
+ return { state: "PENDING", driveId: plan.driveId,
79
+ errorCode: "MONITOR_SETTLEMENT_RESPONSE_LOST" };
80
+ }
81
+ let notificationDelivery = "SETTLED";
82
+ try {
83
+ await options.monitor.invoke("notification.deliver", { limit: 10 });
84
+ }
85
+ catch {
86
+ notificationDelivery = "FAILED";
87
+ }
88
+ return {
89
+ state: string(settlement.state, "MONITOR_DRIVE_SETTLEMENT_STATE"),
90
+ driveId: plan.driveId,
91
+ ...(typeof executionRecord.executionRef === "string"
92
+ ? { executionRef: executionRecord.executionRef }
93
+ : {}),
94
+ notificationDelivery,
95
+ };
96
+ };
97
+ return Object.freeze({
98
+ drive() {
99
+ if (inFlight)
100
+ return inFlight;
101
+ inFlight = driveOnce().finally(() => {
102
+ inFlight = null;
103
+ });
104
+ return inFlight;
105
+ },
106
+ });
107
+ }
@@ -0,0 +1,13 @@
1
+ import type { ExecuteCapabilityRequest } from "@tomflow/proflow-execution-contracts";
2
+ export type MonitorControlPort = {
3
+ invoke(operation: string, input?: unknown): Promise<unknown>;
4
+ };
5
+ /**
6
+ * Transitional symbol retained only while Platform Host still imports it.
7
+ * The legacy polling/rotation coordinator is removed: Browser timing asks the
8
+ * Node owner for a concrete drive, and Node is the only authorization owner.
9
+ */
10
+ export declare function authorizeMonitorExecution(input: {
11
+ monitor: MonitorControlPort;
12
+ request: ExecuteCapabilityRequest;
13
+ }): Promise<boolean>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Transitional symbol retained only while Platform Host still imports it.
3
+ * The legacy polling/rotation coordinator is removed: Browser timing asks the
4
+ * Node owner for a concrete drive, and Node is the only authorization owner.
5
+ */
6
+ export async function authorizeMonitorExecution(input) {
7
+ try {
8
+ if (input.request.capability !== "monitor.chat.submit" &&
9
+ input.request.capability !== "monitor.chat.create")
10
+ return false;
11
+ if (input.request.callerRef !== "platform-host:monitor-drive")
12
+ return false;
13
+ const value = await input.monitor.invoke("drive.authorize", {
14
+ request: input.request,
15
+ });
16
+ return (typeof value === "object" &&
17
+ value !== null &&
18
+ !Array.isArray(value) &&
19
+ Reflect.get(value, "authorized") === true);
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -0,0 +1,132 @@
1
+ export declare const productDiscussionQueryOperationIds: Set<string>;
2
+ export declare const productDiscussionMutationOperationIds: Set<string>;
3
+ export type ProductRoleBinding = {
4
+ agentPackageRef: string;
5
+ roleRef: string;
6
+ };
7
+ export type ProductDiscussionSessionFact = {
8
+ discussionRef: string;
9
+ roleRef: string;
10
+ conversationRef: string;
11
+ conversationLocator: string;
12
+ campaignRef: string | null;
13
+ goalRevision: number | null;
14
+ currentIntentRef: string | null;
15
+ generation: number;
16
+ status: "ACTIVE" | "CLOSED";
17
+ version: number;
18
+ };
19
+ export type ProductDiscussionAdmissionVerificationInput = {
20
+ discussionRef: string;
21
+ roleRef: string;
22
+ conversationRef: string;
23
+ conversationLocator: string;
24
+ generation: number;
25
+ discussionAdmissionToken: string;
26
+ };
27
+ export type ProductContinuationProjection = {
28
+ campaign: {
29
+ campaignRef: string;
30
+ discussionRef: string;
31
+ goalRevision: number;
32
+ version: number;
33
+ status: "OPEN" | "CLOSED";
34
+ };
35
+ intent: {
36
+ intentRef: string;
37
+ version: number;
38
+ } | null;
39
+ task: {
40
+ taskId: string;
41
+ status: string;
42
+ version: number;
43
+ } | null;
44
+ authorization: {
45
+ authorizationRef: string;
46
+ version: number;
47
+ } | null;
48
+ requirementRevision: number | null;
49
+ nextAction: "NONE" | "PROVISION" | "TRANSFER_REQUIREMENT" | "CONSIDER_START" | "PRODUCT_REVIEW_REQUIRED";
50
+ };
51
+ export type ProductReviewSignal = {
52
+ kind: "PRODUCT_REVIEW_REQUIRED";
53
+ reviewRef: string;
54
+ campaignRef: string;
55
+ taskId: string;
56
+ terminalTaskVersion: number;
57
+ intentRef: string;
58
+ discussionRef: string;
59
+ discussionGeneration: number;
60
+ roleRef: string;
61
+ conversationRef: string;
62
+ conversationLocator: string;
63
+ };
64
+ export type ProductContinuationOutcome = {
65
+ kind: "NOT_CAMPAIGN";
66
+ taskId: string;
67
+ } | {
68
+ kind: "IDLE";
69
+ taskId: string;
70
+ } | {
71
+ kind: "AUTHORIZATION_REQUIRED";
72
+ taskId: string;
73
+ } | {
74
+ kind: "TASK_STARTED";
75
+ taskId: string;
76
+ } | ProductReviewSignal;
77
+ export declare class ProductCampaignContinuationError extends Error {
78
+ readonly code: string;
79
+ readonly httpStatus: number;
80
+ constructor(code: string, httpStatus?: number);
81
+ }
82
+ export declare function canonicalizeProductOperation(input: {
83
+ operationId: string;
84
+ authenticatedRoleRef: string;
85
+ rawInput: unknown;
86
+ getDiscussionSession: (discussionRef: string) => ProductDiscussionSessionFact;
87
+ getRoleBindings: () => readonly ProductRoleBinding[];
88
+ verifyDiscussionAdmission?: (input: ProductDiscussionAdmissionVerificationInput) => boolean;
89
+ }): {
90
+ scope: "TASK" | "PRODUCT_QUERY" | "PRODUCT_MUTATION";
91
+ input: Record<string, unknown>;
92
+ actorRef?: string;
93
+ kickContinuation: boolean;
94
+ };
95
+ export declare function createProductCampaignContinuationCoordinator(options: {
96
+ getProjection: (taskId: string) => Promise<ProductContinuationProjection | null>;
97
+ listTaskIds: () => Promise<readonly string[]>;
98
+ ensureWorkers: (taskId: string) => Promise<void>;
99
+ transferRequirement: (input: {
100
+ intentRef: string;
101
+ taskId: string;
102
+ expectedIntentVersion: number;
103
+ expectedTaskVersion: number;
104
+ idempotencyKey: string;
105
+ }) => Promise<void>;
106
+ startTask: (input: {
107
+ taskId: string;
108
+ expectedTaskVersion: number;
109
+ authorizationRef: string;
110
+ expectedAuthorizationVersion: number;
111
+ expectedCampaignVersion: number;
112
+ idempotencyKey: string;
113
+ }) => Promise<void>;
114
+ getDiscussionSessionForCampaign: (campaignRef: string) => ProductDiscussionSessionFact | undefined;
115
+ updateDiscussionSessionContext: (input: {
116
+ discussionRef: string;
117
+ expectedVersion: number;
118
+ campaignRef: string;
119
+ goalRevision: number;
120
+ currentIntentRef: string;
121
+ }) => ProductDiscussionSessionFact;
122
+ kickTaskReconciliation: (taskId: string) => void;
123
+ onProductReviewRequired?: (signal: ProductReviewSignal) => void | Promise<void>;
124
+ retryDelayMs?: number;
125
+ }): Readonly<{
126
+ start(): void;
127
+ kick: (taskId: string) => void;
128
+ reconcile: (taskId: string) => Promise<ProductContinuationOutcome>;
129
+ sweep: () => Promise<ProductContinuationOutcome[]>;
130
+ drain(): Promise<void>;
131
+ stop(): void;
132
+ }>;
@@ -0,0 +1,279 @@
1
+ export const productDiscussionQueryOperationIds = new Set([
2
+ "getProductCampaign",
3
+ "getProductDiscussionContext",
4
+ "getProductDocument",
5
+ "getCampaignAuthorization",
6
+ ]);
7
+ export const productDiscussionMutationOperationIds = new Set([
8
+ "submitProductTaskIntent",
9
+ "putProductDocument",
10
+ "recordProductGapReview",
11
+ "recordProductGoalSatisfied",
12
+ ]);
13
+ export class ProductCampaignContinuationError extends Error {
14
+ code;
15
+ httpStatus;
16
+ constructor(code, httpStatus = 409) {
17
+ super(code);
18
+ this.code = code;
19
+ this.httpStatus = httpStatus;
20
+ }
21
+ }
22
+ function record(value, label) {
23
+ if (typeof value !== "object" || value === null || Array.isArray(value))
24
+ throw new ProductCampaignContinuationError(`${label.toUpperCase()}_INVALID`, 400);
25
+ return value;
26
+ }
27
+ function requiredString(value, label) {
28
+ if (typeof value !== "string" || value.length === 0)
29
+ throw new ProductCampaignContinuationError(`${label.toUpperCase()}_REQUIRED`, 400);
30
+ return value;
31
+ }
32
+ function requiredGeneration(value) {
33
+ if (!Number.isInteger(value) || Number(value) <= 0)
34
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_GENERATION_REQUIRED", 400);
35
+ return Number(value);
36
+ }
37
+ export function canonicalizeProductOperation(input) {
38
+ const raw = record(input.rawInput, "product action input");
39
+ const productQuery = productDiscussionQueryOperationIds.has(input.operationId);
40
+ const productMutation = productDiscussionMutationOperationIds.has(input.operationId);
41
+ if (!productQuery && !productMutation)
42
+ return { scope: "TASK", input: raw, kickContinuation: false };
43
+ const canonical = { ...raw };
44
+ delete canonical.roleRef;
45
+ delete canonical.actorRef;
46
+ delete canonical.workerRef;
47
+ delete canonical.roleBindings;
48
+ const discussionRef = requiredString(canonical.discussionRef, "discussionRef");
49
+ const discussionGeneration = requiredGeneration(canonical.discussionGeneration);
50
+ const session = input.getDiscussionSession(discussionRef);
51
+ if (session.status !== "ACTIVE" ||
52
+ session.roleRef !== input.authenticatedRoleRef)
53
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_MISMATCH", 403);
54
+ if (session.generation !== discussionGeneration)
55
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_GENERATION_MISMATCH", 403);
56
+ if (!input.verifyDiscussionAdmission)
57
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_ADMISSION_UNAVAILABLE", 503);
58
+ const discussionAdmissionToken = requiredString(canonical.discussionAdmissionToken, "discussionAdmissionToken");
59
+ if (!input.verifyDiscussionAdmission({
60
+ discussionRef,
61
+ roleRef: session.roleRef,
62
+ conversationRef: session.conversationRef,
63
+ conversationLocator: session.conversationLocator,
64
+ generation: discussionGeneration,
65
+ discussionAdmissionToken,
66
+ }))
67
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_ADMISSION_INVALID", 403);
68
+ delete canonical.discussionAdmissionToken;
69
+ if (typeof canonical.campaignRef === "string" &&
70
+ session.campaignRef !== canonical.campaignRef)
71
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_CAMPAIGN_MISMATCH", 403);
72
+ if (typeof canonical.goalRevision === "number" &&
73
+ session.goalRevision !== null &&
74
+ session.goalRevision !== canonical.goalRevision)
75
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_GOAL_REVISION_MISMATCH", 403);
76
+ delete canonical.discussionGeneration;
77
+ if (productQuery) {
78
+ if (input.operationId !== "getProductDocument")
79
+ delete canonical.discussionRef;
80
+ if (input.operationId === "getProductDocument")
81
+ requiredString(canonical.campaignRef, "campaignRef");
82
+ if (input.operationId === "getCampaignAuthorization") {
83
+ requiredString(canonical.campaignRef, "campaignRef");
84
+ delete canonical.campaignRef;
85
+ }
86
+ return {
87
+ scope: "PRODUCT_QUERY",
88
+ input: canonical,
89
+ kickContinuation: false,
90
+ };
91
+ }
92
+ if (input.operationId === "submitProductTaskIntent")
93
+ canonical.roleBindings = input
94
+ .getRoleBindings()
95
+ .map((binding) => ({ ...binding }));
96
+ return {
97
+ scope: "PRODUCT_MUTATION",
98
+ input: canonical,
99
+ actorRef: `product-discussion:${discussionRef}`,
100
+ kickContinuation: input.operationId === "submitProductTaskIntent",
101
+ };
102
+ }
103
+ export function createProductCampaignContinuationCoordinator(options) {
104
+ const retryDelayMs = Math.max(50, options.retryDelayMs ?? 500);
105
+ const inFlight = new Map();
106
+ const retryTimers = new Map();
107
+ let stopped = false;
108
+ let scanTimer;
109
+ let started = false;
110
+ let scanInFlight;
111
+ const reconcileOnce = async (taskId) => {
112
+ for (let step = 0; step < 6; step += 1) {
113
+ if (stopped)
114
+ return { kind: "IDLE", taskId };
115
+ const projection = await options.getProjection(taskId);
116
+ if (stopped)
117
+ return { kind: "IDLE", taskId };
118
+ if (!projection)
119
+ return { kind: "NOT_CAMPAIGN", taskId };
120
+ if (projection.nextAction === "NONE")
121
+ return { kind: "IDLE", taskId };
122
+ if (!projection.task || !projection.intent)
123
+ throw new ProductCampaignContinuationError("PRODUCT_CONTINUATION_FACTS_INCOMPLETE");
124
+ if (projection.nextAction === "PROVISION") {
125
+ await options.ensureWorkers(taskId);
126
+ continue;
127
+ }
128
+ if (projection.nextAction === "TRANSFER_REQUIREMENT") {
129
+ if (projection.requirementRevision === null)
130
+ throw new ProductCampaignContinuationError("PRODUCT_REQUIREMENT_REVISION_MISSING");
131
+ await options.transferRequirement({
132
+ intentRef: projection.intent.intentRef,
133
+ taskId,
134
+ expectedIntentVersion: projection.intent.version,
135
+ expectedTaskVersion: projection.task.version,
136
+ idempotencyKey: `transfer:${projection.intent.intentRef}:${taskId}:${projection.requirementRevision}`,
137
+ });
138
+ continue;
139
+ }
140
+ if (projection.nextAction === "CONSIDER_START") {
141
+ if (!projection.authorization)
142
+ return { kind: "AUTHORIZATION_REQUIRED", taskId };
143
+ await options.startTask({
144
+ taskId,
145
+ expectedTaskVersion: projection.task.version,
146
+ authorizationRef: projection.authorization.authorizationRef,
147
+ expectedAuthorizationVersion: projection.authorization.version,
148
+ expectedCampaignVersion: projection.campaign.version,
149
+ idempotencyKey: `auto-start:${projection.authorization.authorizationRef}:${taskId}:${projection.task.version}`,
150
+ });
151
+ options.kickTaskReconciliation(taskId);
152
+ return { kind: "TASK_STARTED", taskId };
153
+ }
154
+ const session = options.getDiscussionSessionForCampaign(projection.campaign.campaignRef);
155
+ if (!session ||
156
+ session.status !== "ACTIVE" ||
157
+ session.discussionRef !== projection.campaign.discussionRef ||
158
+ session.campaignRef !== projection.campaign.campaignRef ||
159
+ (session.goalRevision !== null &&
160
+ session.goalRevision !== projection.campaign.goalRevision))
161
+ throw new ProductCampaignContinuationError("PRODUCT_DISCUSSION_SESSION_NOT_READY");
162
+ const currentSession = session.currentIntentRef === projection.intent.intentRef
163
+ ? session
164
+ : options.updateDiscussionSessionContext({
165
+ discussionRef: session.discussionRef,
166
+ expectedVersion: session.version,
167
+ campaignRef: projection.campaign.campaignRef,
168
+ goalRevision: projection.campaign.goalRevision,
169
+ currentIntentRef: projection.intent.intentRef,
170
+ });
171
+ const signal = {
172
+ kind: "PRODUCT_REVIEW_REQUIRED",
173
+ reviewRef: `product-review:${projection.campaign.campaignRef}:${taskId}:${projection.task.version}:${currentSession.discussionRef}:${currentSession.generation}`,
174
+ campaignRef: projection.campaign.campaignRef,
175
+ taskId,
176
+ terminalTaskVersion: projection.task.version,
177
+ intentRef: projection.intent.intentRef,
178
+ discussionRef: currentSession.discussionRef,
179
+ discussionGeneration: currentSession.generation,
180
+ roleRef: currentSession.roleRef,
181
+ conversationRef: currentSession.conversationRef,
182
+ conversationLocator: currentSession.conversationLocator,
183
+ };
184
+ await options.onProductReviewRequired?.(signal);
185
+ return signal;
186
+ }
187
+ throw new ProductCampaignContinuationError("PRODUCT_CONTINUATION_STEP_LIMIT");
188
+ };
189
+ const reconcile = (taskId) => {
190
+ const current = inFlight.get(taskId);
191
+ if (current)
192
+ return current;
193
+ const run = reconcileOnce(taskId).finally(() => inFlight.delete(taskId));
194
+ inFlight.set(taskId, run);
195
+ return run;
196
+ };
197
+ const scheduleRetry = (taskId) => {
198
+ if (stopped || retryTimers.has(taskId))
199
+ return;
200
+ const timer = setTimeout(() => {
201
+ retryTimers.delete(taskId);
202
+ if (!stopped)
203
+ void reconcile(taskId).catch(() => scheduleRetry(taskId));
204
+ }, retryDelayMs);
205
+ timer.unref?.();
206
+ retryTimers.set(taskId, timer);
207
+ };
208
+ const kick = (taskId) => {
209
+ if (stopped)
210
+ return;
211
+ void reconcile(taskId).catch(() => scheduleRetry(taskId));
212
+ };
213
+ const sweep = async () => {
214
+ if (stopped)
215
+ return [];
216
+ const taskIds = await options.listTaskIds();
217
+ const outcomes = [];
218
+ const failures = [];
219
+ for (let index = 0; !stopped && index < taskIds.length; index += 4) {
220
+ const batch = await Promise.allSettled(taskIds.slice(index, index + 4).map((taskId) => reconcile(taskId).catch((error) => {
221
+ scheduleRetry(taskId);
222
+ throw error;
223
+ })));
224
+ for (const result of batch) {
225
+ if (result.status === "fulfilled")
226
+ outcomes.push(result.value);
227
+ else
228
+ failures.push(result.reason);
229
+ }
230
+ }
231
+ if (failures.length > 0)
232
+ throw failures[0];
233
+ return outcomes;
234
+ };
235
+ const scan = async () => {
236
+ let delay = 30_000;
237
+ try {
238
+ await sweep();
239
+ }
240
+ catch {
241
+ delay = retryDelayMs;
242
+ }
243
+ finally {
244
+ if (!stopped) {
245
+ scanTimer = setTimeout(() => {
246
+ scanInFlight = scan();
247
+ }, delay);
248
+ scanTimer.unref?.();
249
+ }
250
+ }
251
+ };
252
+ return Object.freeze({
253
+ start() {
254
+ if (stopped)
255
+ throw new ProductCampaignContinuationError("PRODUCT_CONTINUATION_STOPPED");
256
+ if (started)
257
+ return;
258
+ started = true;
259
+ scanInFlight = scan();
260
+ },
261
+ kick,
262
+ reconcile,
263
+ sweep,
264
+ async drain() {
265
+ await Promise.allSettled([
266
+ ...(scanInFlight ? [scanInFlight] : []),
267
+ ...inFlight.values(),
268
+ ]);
269
+ },
270
+ stop() {
271
+ stopped = true;
272
+ if (scanTimer)
273
+ clearTimeout(scanTimer);
274
+ for (const timer of retryTimers.values())
275
+ clearTimeout(timer);
276
+ retryTimers.clear();
277
+ },
278
+ });
279
+ }
@@ -0,0 +1,92 @@
1
+ export type ProductDiscussionConversationIdentity = Readonly<{
2
+ roleRef: string;
3
+ conversationRef: string;
4
+ conversationLocator: string;
5
+ }>;
6
+ export type ProductDiscussionAdmission = Readonly<{
7
+ discussionRef: string;
8
+ roleRef: string;
9
+ conversationRef: string;
10
+ conversationLocator: string;
11
+ generation: number;
12
+ contentInstanceId: string;
13
+ bootstrapRef: string;
14
+ discussionAdmissionToken: string;
15
+ }>;
16
+ export type ProductDiscussionAdmissionVerification = Readonly<{
17
+ discussionRef: string;
18
+ roleRef: string;
19
+ conversationRef: string;
20
+ conversationLocator: string;
21
+ generation: number;
22
+ discussionAdmissionToken: string;
23
+ }>;
24
+ export type ProductDiscussionSessionOwnerFact = Readonly<{
25
+ discussionRef: string;
26
+ roleRef: string;
27
+ conversationRef: string;
28
+ conversationLocator: string;
29
+ generation: number;
30
+ status: "ACTIVE" | "CLOSED";
31
+ }>;
32
+ export type ProductDiscussionTaskRoleBindingFact = Readonly<{
33
+ roleRef: string;
34
+ workerRef: string | null;
35
+ conversationLocator: string | null;
36
+ }>;
37
+ type RandomBytes = (size: number) => Uint8Array;
38
+ export declare function parseProductDiscussionConversationLocator(value: string): ProductDiscussionConversationIdentity | null;
39
+ export declare function productDiscussionRef(identity: ProductDiscussionConversationIdentity): string;
40
+ export declare function createProductDiscussionAdmissionAuthority(options?: {
41
+ randomBytesImpl?: RandomBytes;
42
+ now?: () => number;
43
+ }): Readonly<{
44
+ ensure: (input: {
45
+ discussionRef: string;
46
+ roleRef: string;
47
+ conversationRef: string;
48
+ conversationLocator: string;
49
+ generation: number;
50
+ contentInstanceId: string;
51
+ }) => ProductDiscussionAdmission;
52
+ verify: (input: ProductDiscussionAdmissionVerification) => boolean;
53
+ get(discussionRef: string): ProductDiscussionAdmission | undefined;
54
+ revoke(discussionRef: string): boolean;
55
+ clear(): void;
56
+ }>;
57
+ export declare function assertProductDiscussionConversationPreTask(options: {
58
+ conversationLocator: string;
59
+ listTaskIds(): readonly string[] | Promise<readonly string[]>;
60
+ getTaskRoleBindings(taskId: string): readonly ProductDiscussionTaskRoleBindingFact[] | Promise<readonly ProductDiscussionTaskRoleBindingFact[]>;
61
+ }): Promise<void>;
62
+ export declare function createProductDiscussionAdmissionCoordinator(options: {
63
+ productRoleRef: string;
64
+ authority: ReturnType<typeof createProductDiscussionAdmissionAuthority>;
65
+ isTaskWorkerProvisioning(roleRef: string): boolean;
66
+ listTaskIds(): readonly string[] | Promise<readonly string[]>;
67
+ getTaskRoleBindings(taskId: string): readonly ProductDiscussionTaskRoleBindingFact[] | Promise<readonly ProductDiscussionTaskRoleBindingFact[]>;
68
+ registerDiscussionSession(input: {
69
+ discussionRef: string;
70
+ roleRef: string;
71
+ conversationRef: string;
72
+ conversationLocator: string;
73
+ }): ProductDiscussionSessionOwnerFact | Promise<ProductDiscussionSessionOwnerFact>;
74
+ deliverBootstrap(admission: ProductDiscussionAdmission): void | Promise<void>;
75
+ }): Readonly<{
76
+ observe: (input: {
77
+ conversationLocator: string;
78
+ contentInstanceId: string;
79
+ }) => Promise<Readonly<{
80
+ admitted: false;
81
+ reason: "NOT_CUSTOM_GPT_CONVERSATION";
82
+ }> | Readonly<{
83
+ admitted: false;
84
+ reason: "NOT_PRODUCT_ROLE";
85
+ }> | Readonly<{
86
+ admitted: true;
87
+ discussionRef: string;
88
+ discussionGeneration: number;
89
+ bootstrapRef: string;
90
+ }>>;
91
+ }>;
92
+ export {};