@tomflow/proflow-platform-host 0.1.30 → 0.1.31

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,43 @@
1
+ import type { ExecuteCapabilityRequest } from "@tomflow/proflow-execution-contracts";
2
+ import { type ProductDiscussionAdmissionVerification, type ProductDiscussionSessionOwnerFact, type ProductDiscussionTaskRoleBindingFact } from "./product-discussion-admission.ts";
3
+ export type ProductDiscussionHostTaskFact = Readonly<{
4
+ taskId: string;
5
+ status: string;
6
+ roleBindings: readonly ProductDiscussionTaskRoleBindingFact[];
7
+ }>;
8
+ export declare function createProductDiscussionHost(options: {
9
+ productRoleRef: string;
10
+ listTaskIds(): readonly string[] | Promise<readonly string[]>;
11
+ getTaskFact(taskId: string): ProductDiscussionHostTaskFact | Promise<ProductDiscussionHostTaskFact>;
12
+ registerDiscussionSession(input: {
13
+ discussionRef: string;
14
+ roleRef: string;
15
+ conversationRef: string;
16
+ conversationLocator: string;
17
+ }): ProductDiscussionSessionOwnerFact | Promise<ProductDiscussionSessionOwnerFact>;
18
+ getDiscussionSession(discussionRef: string): ProductDiscussionSessionOwnerFact | Promise<ProductDiscussionSessionOwnerFact>;
19
+ executeCapability(request: ExecuteCapabilityRequest): Promise<unknown>;
20
+ }): Readonly<{
21
+ recoverReservations: () => Promise<void>;
22
+ reserveTaskWorker: (taskId: string, roleRef: string) => void;
23
+ releaseTaskWorker: (taskId: string, roleRef: string) => void;
24
+ isTaskWorkerProvisioning: (roleRef: string) => boolean;
25
+ observe: (input: {
26
+ conversationLocator: string;
27
+ contentInstanceId: string;
28
+ }) => Promise<Readonly<{
29
+ admitted: false;
30
+ reason: "NOT_CUSTOM_GPT_CONVERSATION";
31
+ }> | Readonly<{
32
+ admitted: false;
33
+ reason: "NOT_PRODUCT_ROLE";
34
+ }> | Readonly<{
35
+ admitted: true;
36
+ discussionRef: string;
37
+ discussionGeneration: number;
38
+ bootstrapRef: string;
39
+ }>>;
40
+ authorizeBootstrapExecution: (request: ExecuteCapabilityRequest) => Promise<boolean>;
41
+ verifyAdmission(input: ProductDiscussionAdmissionVerification): boolean;
42
+ clear(): void;
43
+ }>;
@@ -0,0 +1,167 @@
1
+ import { assertProductDiscussionConversationPreTask, createProductDiscussionAdmissionAuthority, createProductDiscussionAdmissionCoordinator, parseProductDiscussionConversationLocator, productDiscussionRef, } from "./product-discussion-admission.js";
2
+ const reservableTaskStatuses = new Set(["PENDING", "READY", "ACTIVE", "WAITING"]);
3
+ function object(value, code) {
4
+ if (typeof value !== "object" || value === null || Array.isArray(value))
5
+ throw new Error(code);
6
+ return value;
7
+ }
8
+ export function createProductDiscussionHost(options) {
9
+ const authority = createProductDiscussionAdmissionAuthority();
10
+ const provisioning = new Map();
11
+ let recovered = false;
12
+ const reserveTaskWorker = (taskId, roleRef) => {
13
+ if (roleRef !== options.productRoleRef)
14
+ return;
15
+ const current = provisioning.get(roleRef) ?? new Set();
16
+ current.add(taskId);
17
+ provisioning.set(roleRef, current);
18
+ };
19
+ const releaseTaskWorker = (taskId, roleRef) => {
20
+ if (roleRef !== options.productRoleRef)
21
+ return;
22
+ const current = provisioning.get(roleRef);
23
+ if (!current)
24
+ return;
25
+ current.delete(taskId);
26
+ if (current.size === 0)
27
+ provisioning.delete(roleRef);
28
+ };
29
+ const isTaskWorkerProvisioning = (roleRef) => roleRef === options.productRoleRef &&
30
+ (!recovered || (provisioning.get(roleRef)?.size ?? 0) > 0);
31
+ const coordinator = createProductDiscussionAdmissionCoordinator({
32
+ productRoleRef: options.productRoleRef,
33
+ authority,
34
+ isTaskWorkerProvisioning,
35
+ listTaskIds: options.listTaskIds,
36
+ async getTaskRoleBindings(taskId) {
37
+ return (await options.getTaskFact(taskId)).roleBindings;
38
+ },
39
+ registerDiscussionSession: options.registerDiscussionSession,
40
+ async deliverBootstrap(admission) {
41
+ const request = {
42
+ contract: "execution",
43
+ contractVersion: "1.0.0",
44
+ idempotencyKey: admission.bootstrapRef,
45
+ callerRef: "platform-host:product-discussion-admission",
46
+ correlationId: admission.bootstrapRef,
47
+ roleRef: admission.roleRef,
48
+ capability: "product.discussion.bootstrap.deliver",
49
+ input: {
50
+ bootstrapRef: admission.bootstrapRef,
51
+ discussionRef: admission.discussionRef,
52
+ discussionGeneration: admission.generation,
53
+ discussionAdmissionToken: admission.discussionAdmissionToken,
54
+ roleRef: admission.roleRef,
55
+ conversationRef: admission.conversationRef,
56
+ conversationLocator: admission.conversationLocator,
57
+ contentInstanceId: admission.contentInstanceId,
58
+ contentFingerprint: admission.bootstrapRef,
59
+ },
60
+ };
61
+ const execution = object(await options.executeCapability(request), "PRODUCT_DISCUSSION_BOOTSTRAP_EXECUTION_INVALID");
62
+ const result = execution.result === undefined
63
+ ? undefined
64
+ : object(execution.result, "PRODUCT_DISCUSSION_BOOTSTRAP_RESULT_INVALID");
65
+ const data = result?.data === undefined
66
+ ? undefined
67
+ : object(result.data, "PRODUCT_DISCUSSION_BOOTSTRAP_RESULT_DATA_INVALID");
68
+ if (execution.status !== "SUCCEEDED" ||
69
+ execution.sideEffectState !== "APPLIED" ||
70
+ result?.capability !== "product.discussion.bootstrap.deliver" ||
71
+ data?.bootstrapRef !== admission.bootstrapRef ||
72
+ data?.delivered !== true ||
73
+ typeof data.evidenceRef !== "string")
74
+ throw new Error(`PRODUCT_DISCUSSION_BOOTSTRAP_NOT_CONFIRMED:${String(execution.status)}:${String(execution.sideEffectState)}`);
75
+ },
76
+ });
77
+ const recoverReservations = async () => {
78
+ if (recovered)
79
+ return;
80
+ const taskIds = await options.listTaskIds();
81
+ for (const taskId of taskIds) {
82
+ const task = await options.getTaskFact(taskId);
83
+ if (!reservableTaskStatuses.has(task.status))
84
+ continue;
85
+ for (const binding of task.roleBindings)
86
+ if (binding.roleRef === options.productRoleRef &&
87
+ (!binding.workerRef || !binding.conversationLocator))
88
+ reserveTaskWorker(task.taskId, binding.roleRef);
89
+ }
90
+ recovered = true;
91
+ };
92
+ const authorizeBootstrapExecution = async (request) => {
93
+ if (request.capability !== "product.discussion.bootstrap.deliver")
94
+ return false;
95
+ try {
96
+ if (!recovered)
97
+ return false;
98
+ if (request.callerRef !== "platform-host:product-discussion-admission")
99
+ return false;
100
+ if (request.taskId !== undefined ||
101
+ request.nodeId !== undefined ||
102
+ request.runNo !== undefined ||
103
+ request.workerRef !== undefined ||
104
+ request.projectRoot !== undefined)
105
+ return false;
106
+ const input = request.input;
107
+ if (request.roleRef !== options.productRoleRef ||
108
+ input.roleRef !== options.productRoleRef ||
109
+ isTaskWorkerProvisioning(input.roleRef) ||
110
+ input.contentFingerprint !== input.bootstrapRef)
111
+ return false;
112
+ const identity = parseProductDiscussionConversationLocator(input.conversationLocator);
113
+ if (!identity ||
114
+ identity.roleRef !== input.roleRef ||
115
+ identity.conversationRef !== input.conversationRef ||
116
+ productDiscussionRef(identity) !== input.discussionRef)
117
+ return false;
118
+ await assertProductDiscussionConversationPreTask({
119
+ conversationLocator: input.conversationLocator,
120
+ listTaskIds: options.listTaskIds,
121
+ async getTaskRoleBindings(taskId) {
122
+ return (await options.getTaskFact(taskId)).roleBindings;
123
+ },
124
+ });
125
+ const session = await options.getDiscussionSession(input.discussionRef);
126
+ if (session.status !== "ACTIVE" ||
127
+ session.discussionRef !== input.discussionRef ||
128
+ session.roleRef !== input.roleRef ||
129
+ session.conversationRef !== input.conversationRef ||
130
+ session.conversationLocator !== input.conversationLocator ||
131
+ session.generation !== input.discussionGeneration)
132
+ return false;
133
+ const current = authority.get(input.discussionRef);
134
+ if (!current ||
135
+ current.bootstrapRef !== input.bootstrapRef ||
136
+ current.contentInstanceId !== input.contentInstanceId)
137
+ return false;
138
+ return authority.verify({
139
+ discussionRef: input.discussionRef,
140
+ roleRef: input.roleRef,
141
+ conversationRef: input.conversationRef,
142
+ conversationLocator: input.conversationLocator,
143
+ generation: input.discussionGeneration,
144
+ discussionAdmissionToken: input.discussionAdmissionToken,
145
+ });
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ };
151
+ return Object.freeze({
152
+ recoverReservations,
153
+ reserveTaskWorker,
154
+ releaseTaskWorker,
155
+ isTaskWorkerProvisioning,
156
+ observe: coordinator.observe,
157
+ authorizeBootstrapExecution,
158
+ verifyAdmission(input) {
159
+ return recovered && !isTaskWorkerProvisioning(input.roleRef) && authority.verify(input);
160
+ },
161
+ clear() {
162
+ authority.clear();
163
+ provisioning.clear();
164
+ recovered = false;
165
+ },
166
+ });
167
+ }
@@ -33,5 +33,6 @@ export declare function createReconciliationCoordinator(options: ReconciliationC
33
33
  kick(taskId: string, signal?: TaskResumeSignal): void;
34
34
  reconcile: (taskId: string, signal?: TaskResumeSignal) => Promise<void>;
35
35
  sweep: () => Promise<void>;
36
+ drain(): Promise<void>;
36
37
  stop(): void;
37
38
  }>;
@@ -197,8 +197,7 @@ export function createReconciliationCoordinator(options) {
197
197
  }
198
198
  })().finally(() => {
199
199
  taskInFlight.delete(taskId);
200
- if (!stopped &&
201
- (pendingSignals.has(taskId) || failures.has(taskId)))
200
+ if (!stopped && (pendingSignals.has(taskId) || failures.has(taskId)))
202
201
  schedulePendingRetry(taskId);
203
202
  else
204
203
  admittedTasks.delete(taskId);
@@ -318,6 +317,12 @@ export function createReconciliationCoordinator(options) {
318
317
  },
319
318
  reconcile,
320
319
  sweep,
320
+ async drain() {
321
+ await Promise.allSettled([
322
+ ...(sweepInFlight ? [sweepInFlight] : []),
323
+ ...taskInFlight.values(),
324
+ ]);
325
+ },
321
326
  stop() {
322
327
  if (stopped)
323
328
  return;
@@ -42,6 +42,14 @@ export const roleOperations = {
42
42
  "getTask",
43
43
  "putTaskDocument",
44
44
  "getTaskDocument",
45
+ "getProductCampaign",
46
+ "getProductDiscussionContext",
47
+ "getProductDocument",
48
+ "putProductDocument",
49
+ "getCampaignAuthorization",
50
+ "submitProductTaskIntent",
51
+ "recordProductGapReview",
52
+ "recordProductGoalSatisfied",
45
53
  "askPeer",
46
54
  "replyPeer",
47
55
  ...directToolActionIds,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-host",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,19 +24,19 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "zod": "4.1.12",
27
- "@tomflow/proflow-execution-browser-extension": "^0.1.62",
28
- "@tomflow/proflow-task-migration-runner": "^0.1.11",
29
- "@tomflow/proflow-task-orchestration": "^0.1.11",
30
- "@tomflow/proflow-task-store-sqlite": "^0.1.12",
31
27
  "@tomflow/proflow-agent-runtime": "^0.1.17",
32
28
  "@tomflow/proflow-module-contract": "^0.1.13",
33
- "@tomflow/proflow-execution-contracts": "^0.1.11"
29
+ "@tomflow/proflow-execution-contracts": "^0.1.11",
30
+ "@tomflow/proflow-execution-browser-extension": "^0.1.65",
31
+ "@tomflow/proflow-task-store-sqlite": "^0.1.12",
32
+ "@tomflow/proflow-task-migration-runner": "^0.1.11",
33
+ "@tomflow/proflow-task-orchestration": "^0.1.12"
34
34
  },
35
35
  "devDependencies": {
36
- "@tomflow/proflow-deployment-conformance": "^0.1.13",
37
36
  "@tomflow/proflow-execution-runtime": "^0.1.19",
38
- "@tomflow/proflow-model-runtime": "^0.1.25",
39
- "@tomflow/proflow-agent-gateway": "^0.1.18"
37
+ "@tomflow/proflow-deployment-conformance": "^0.1.13",
38
+ "@tomflow/proflow-agent-gateway": "^0.1.19",
39
+ "@tomflow/proflow-model-runtime": "^0.1.26"
40
40
  },
41
41
  "description": "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.",
42
42
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-host",
5
5
  "packageName": "@tomflow/proflow-platform-host",
6
- "moduleVersion": "0.1.30",
6
+ "moduleVersion": "0.1.31",
7
7
  "kind": "service",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",