@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.
- package/CHANGELOG.md +6 -0
- package/dist/deployment/adapter.d.ts +10 -10
- package/dist/deployment/adapter.js +22 -14
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +437 -15
- package/dist/src/monitor-rotation-coordinator.d.ts +76 -0
- package/dist/src/monitor-rotation-coordinator.js +782 -0
- package/dist/src/product-campaign-continuation.d.ts +132 -0
- package/dist/src/product-campaign-continuation.js +279 -0
- package/dist/src/product-discussion-admission.d.ts +92 -0
- package/dist/src/product-discussion-admission.js +191 -0
- package/dist/src/product-discussion-host.d.ts +43 -0
- package/dist/src/product-discussion-host.js +167 -0
- package/dist/src/reconciliation-coordinator.d.ts +1 -0
- package/dist/src/reconciliation-coordinator.js +7 -2
- package/dist/src/role-operations.js +8 -0
- package/package.json +9 -9
- package/proflow.module.json +1 -1
|
@@ -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.
|
|
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-
|
|
39
|
-
"@tomflow/proflow-agent-gateway": "^0.1.
|
|
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": [
|
package/proflow.module.json
CHANGED