@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.
- package/CHANGELOG.md +12 -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 +503 -15
- package/dist/src/monitor-drive-relay.d.ts +13 -0
- package/dist/src/monitor-drive-relay.js +107 -0
- package/dist/src/monitor-rotation-coordinator.d.ts +13 -0
- package/dist/src/monitor-rotation-coordinator.js +24 -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 +11 -11
- package/proflow.module.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -5,7 +5,9 @@ import { chmod, mkdir, readFile, stat, writeFile, } from "node:fs/promises";
|
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { createAgentRuntime } from "@tomflow/proflow-agent-runtime";
|
|
8
|
+
import { createMonitorControlClient, } from "@tomflow/proflow-execution-browser-extension";
|
|
8
9
|
import { createLocalToolBridgeHostClient, LocalToolBridgeError, } from "@tomflow/proflow-execution-browser-extension/local-tool-bridge";
|
|
10
|
+
import { browserActionInputSchema, parseMonitorEffectRequest } from "@tomflow/proflow-execution-contracts";
|
|
9
11
|
import { applyMigrations } from "@tomflow/proflow-task-migration-runner";
|
|
10
12
|
import { createTaskServices, publicOperationNames, } from "@tomflow/proflow-task-orchestration";
|
|
11
13
|
import { SqliteTaskStore } from "@tomflow/proflow-task-store-sqlite";
|
|
@@ -13,10 +15,24 @@ import { taskMigrations } from "@tomflow/proflow-task-store-sqlite/migrations";
|
|
|
13
15
|
import { z } from "zod";
|
|
14
16
|
import { resolveBrowserPermissionTaskBinding } from "./browser-permission-context.js";
|
|
15
17
|
import { classifyBrowserPermission, } from "./browser-permission-policy.js";
|
|
18
|
+
import { authorizeMonitorExecution } from "./monitor-rotation-coordinator.js";
|
|
19
|
+
import { createMonitorDriveRelay } from "./monitor-drive-relay.js";
|
|
20
|
+
import { canonicalizeProductOperation, createProductCampaignContinuationCoordinator, } from "./product-campaign-continuation.js";
|
|
21
|
+
import { createProductDiscussionHost } from "./product-discussion-host.js";
|
|
16
22
|
import { createReconciliationCoordinator } from "./reconciliation-coordinator.js";
|
|
17
23
|
import { directToolActionIds, roleAllowsDirectToolOperation, roleOperations, rolePackageRefs, } from "./role-operations.js";
|
|
18
24
|
const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
19
25
|
const OWNER_INVOKE_WATCHDOG_MS = 120_000;
|
|
26
|
+
const productCampaignOperationIds = new Set([
|
|
27
|
+
"getProductCampaign",
|
|
28
|
+
"getProductDiscussionContext",
|
|
29
|
+
"getProductDocument",
|
|
30
|
+
"putProductDocument",
|
|
31
|
+
"getCampaignAuthorization",
|
|
32
|
+
"submitProductTaskIntent",
|
|
33
|
+
"recordProductGapReview",
|
|
34
|
+
"recordProductGoalSatisfied",
|
|
35
|
+
]);
|
|
20
36
|
const systemObserverReasonResultSchema = z
|
|
21
37
|
.object({
|
|
22
38
|
scope: z.string().min(1).max(240).optional(),
|
|
@@ -207,7 +223,7 @@ async function responseJson(response) {
|
|
|
207
223
|
});
|
|
208
224
|
return value;
|
|
209
225
|
}
|
|
210
|
-
function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection) {
|
|
226
|
+
function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection, shutdownSignal) {
|
|
211
227
|
const connection = async () => {
|
|
212
228
|
const resolved = resolveConnection
|
|
213
229
|
? await resolveConnection(owner)
|
|
@@ -262,6 +278,8 @@ function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection) {
|
|
|
262
278
|
}
|
|
263
279
|
else if (operationId === "materializeExternalFiles")
|
|
264
280
|
path = "/external-files/materialize";
|
|
281
|
+
else if (operationId === "effect.execute")
|
|
282
|
+
path = "/effects/execute";
|
|
265
283
|
else if (operationId === "executeCapability")
|
|
266
284
|
path = "/executions";
|
|
267
285
|
else if (operationId === "lookupExecutionIntent")
|
|
@@ -319,7 +337,9 @@ function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection) {
|
|
|
319
337
|
...(method === "POST"
|
|
320
338
|
? { body: JSON.stringify(requestBody) }
|
|
321
339
|
: {}),
|
|
322
|
-
signal:
|
|
340
|
+
signal: shutdownSignal
|
|
341
|
+
? AbortSignal.any([shutdownSignal, AbortSignal.timeout(operationId === "effect.execute" ? 190_000 : OWNER_INVOKE_WATCHDOG_MS)])
|
|
342
|
+
: AbortSignal.timeout(operationId === "effect.execute" ? 190_000 : OWNER_INVOKE_WATCHDOG_MS),
|
|
323
343
|
}));
|
|
324
344
|
},
|
|
325
345
|
});
|
|
@@ -382,7 +402,7 @@ async function readDeploymentOwnerSummary(stateRoot) {
|
|
|
382
402
|
return undefined;
|
|
383
403
|
}
|
|
384
404
|
}
|
|
385
|
-
async function constructGraph(config, executionCredential, modelCredential, resolveConnection, resolveLocalToolConnection) {
|
|
405
|
+
async function constructGraph(config, executionCredential, modelCredential, resolveConnection, resolveLocalToolConnection, resolveMonitorControlConnection) {
|
|
386
406
|
const sink = createOperationSink(config.stateRoot);
|
|
387
407
|
const observer = createHostOperationObserver(entry => sink.write("platform-host", entry));
|
|
388
408
|
const databasePath = join(config.stateRoot, "state", "task.sqlite");
|
|
@@ -445,9 +465,27 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
445
465
|
taskStore.close();
|
|
446
466
|
throw error;
|
|
447
467
|
}
|
|
448
|
-
const
|
|
449
|
-
const
|
|
468
|
+
const ownerShutdown = new AbortController();
|
|
469
|
+
const execution = observer.port("execution", createOwnerHttpClient("execution", config.executionBaseUrl, executionCredential, resolveConnection, ownerShutdown.signal));
|
|
470
|
+
const model = observer.port("model", createOwnerHttpClient("model", config.modelBaseUrl, modelCredential, resolveConnection, ownerShutdown.signal));
|
|
471
|
+
const monitor = resolveMonitorControlConnection
|
|
472
|
+
? Object.freeze({
|
|
473
|
+
async invoke(operation, input = {}) {
|
|
474
|
+
const connection = await resolveMonitorControlConnection();
|
|
475
|
+
if (!connection)
|
|
476
|
+
throw new Error("MONITOR_CONTROL_UNAVAILABLE");
|
|
477
|
+
return createMonitorControlClient({
|
|
478
|
+
endpoint: connection.endpoint,
|
|
479
|
+
token: connection.credential,
|
|
480
|
+
}).invoke(operation, input);
|
|
481
|
+
},
|
|
482
|
+
})
|
|
483
|
+
: undefined;
|
|
484
|
+
const monitorDriveRelay = monitor
|
|
485
|
+
? createMonitorDriveRelay({ monitor, execution })
|
|
486
|
+
: undefined;
|
|
450
487
|
let reconciliationCoordinator;
|
|
488
|
+
let productCampaignContinuationCoordinator;
|
|
451
489
|
const boundedSystemView = async (view) => {
|
|
452
490
|
if (view === "task") {
|
|
453
491
|
const tasks = unwrap(task.queries.listTasks({})).tasks;
|
|
@@ -649,8 +687,59 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
649
687
|
throw Object.assign(new Error(code), { httpStatus });
|
|
650
688
|
}
|
|
651
689
|
}
|
|
652
|
-
const taskOperation =
|
|
690
|
+
const taskOperation = operationId === "getProductDiscussionContext"
|
|
691
|
+
? () => {
|
|
692
|
+
const session = agent.getProductDiscussionSession(string(input.discussionRef, "discussionRef"));
|
|
693
|
+
return { ok: true, data: { ...session, discussionGeneration: session.generation } };
|
|
694
|
+
}
|
|
695
|
+
: taskOperations.get(operationId);
|
|
653
696
|
if (taskOperation) {
|
|
697
|
+
const productOperation = role.agentPackageRef === "@tomflow/proflow-agent-product" &&
|
|
698
|
+
productCampaignOperationIds.has(operationId)
|
|
699
|
+
? canonicalizeProductOperation({
|
|
700
|
+
operationId,
|
|
701
|
+
authenticatedRoleRef,
|
|
702
|
+
rawInput: input,
|
|
703
|
+
getDiscussionSession: (discussionRef) => agent.getProductDiscussionSession(discussionRef),
|
|
704
|
+
getRoleBindings: () => rolePackageRefs.map((agentPackageRef) => ({
|
|
705
|
+
agentPackageRef,
|
|
706
|
+
roleRef: roleForPackage(agentPackageRef).roleRef,
|
|
707
|
+
})),
|
|
708
|
+
verifyDiscussionAdmission: (admission) => {
|
|
709
|
+
if (!productDiscussionHost?.verifyAdmission(admission))
|
|
710
|
+
return false;
|
|
711
|
+
return !unwrap(task.queries.listTasks({})).tasks.some((summary) => unwrap(task.queries.getTask({ taskId: summary.taskId })).roleBindings.some((binding) => binding.conversationLocator === admission.conversationLocator ||
|
|
712
|
+
binding.workerRef === admission.conversationRef));
|
|
713
|
+
},
|
|
714
|
+
})
|
|
715
|
+
: undefined;
|
|
716
|
+
if (productOperation?.scope === "PRODUCT_QUERY") {
|
|
717
|
+
if (operationId === "getCampaignAuthorization") {
|
|
718
|
+
const authorization = unwrap(task.queries.getCampaignAuthorization(productOperation.input));
|
|
719
|
+
if (authorization.campaignRef !== input.campaignRef ||
|
|
720
|
+
authorization.discussionRef !== input.discussionRef)
|
|
721
|
+
throw Object.assign(new Error("PRODUCT_DISCUSSION_CAMPAIGN_MISMATCH"), { httpStatus: 403 });
|
|
722
|
+
}
|
|
723
|
+
return taskOperation(productOperation.input);
|
|
724
|
+
}
|
|
725
|
+
if (productOperation?.scope === "PRODUCT_MUTATION") {
|
|
726
|
+
const taskResult = await taskOperation({
|
|
727
|
+
...productOperation.input,
|
|
728
|
+
actorRef: productOperation.actorRef,
|
|
729
|
+
});
|
|
730
|
+
if (productOperation.kickContinuation &&
|
|
731
|
+
typeof taskResult === "object" &&
|
|
732
|
+
taskResult !== null &&
|
|
733
|
+
Reflect.get(taskResult, "ok") === true) {
|
|
734
|
+
const data = Reflect.get(taskResult, "data");
|
|
735
|
+
if (typeof data === "object" && data !== null) {
|
|
736
|
+
const taskId = Reflect.get(data, "taskId");
|
|
737
|
+
if (typeof taskId === "string")
|
|
738
|
+
productCampaignContinuationCoordinator?.kick(taskId);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return taskResult;
|
|
742
|
+
}
|
|
654
743
|
let actorRef = authenticatedRoleRef;
|
|
655
744
|
let canonicalWorkerRef;
|
|
656
745
|
if (typeof input.taskId === "string") {
|
|
@@ -691,8 +780,10 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
691
780
|
const taskResult = await taskOperation(queryOperations.has(operationId)
|
|
692
781
|
? canonicalTaskInput
|
|
693
782
|
: { ...canonicalTaskInput, actorRef });
|
|
694
|
-
if (taskMutationOperations.has(operationId) && typeof input.taskId === "string")
|
|
783
|
+
if (taskMutationOperations.has(operationId) && typeof input.taskId === "string") {
|
|
695
784
|
reconciliationCoordinator?.kick(input.taskId);
|
|
785
|
+
productCampaignContinuationCoordinator?.kick(input.taskId);
|
|
786
|
+
}
|
|
696
787
|
if (operationId === "getTaskDocument")
|
|
697
788
|
return fileBridgeOutputForTaskResult(taskResult);
|
|
698
789
|
return taskResult;
|
|
@@ -713,8 +804,14 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
713
804
|
const declared = current.roleBindings.find((item) => item.roleRef === binding.roleRef);
|
|
714
805
|
if (!declared)
|
|
715
806
|
throw new Error("AGENT_PACKAGE_NOT_ELIGIBLE");
|
|
716
|
-
if (declared.workerRef === binding.workerRef)
|
|
807
|
+
if (declared.workerRef === binding.workerRef) {
|
|
808
|
+
if (declared.conversationLocator !== null &&
|
|
809
|
+
declared.conversationLocator !== binding.conversationLocator)
|
|
810
|
+
throw new Error("TASK_ROLE_BINDING_CONVERSATION_MISMATCH");
|
|
811
|
+
if (declared.conversationLocator === binding.conversationLocator)
|
|
812
|
+
productDiscussionHost?.releaseTaskWorker(binding.taskId, binding.roleRef);
|
|
717
813
|
return;
|
|
814
|
+
}
|
|
718
815
|
if (declared.workerRef)
|
|
719
816
|
throw new Error("TASK_ROLE_BINDING_CONFLICT");
|
|
720
817
|
unwrap(task.commands.bindTaskWorker({
|
|
@@ -727,7 +824,9 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
727
824
|
actorRef: "platform-host:worker-provisioning",
|
|
728
825
|
idempotencyKey: `browser-bind:${binding.taskId}:${binding.roleRef}:${binding.workerRef}`,
|
|
729
826
|
}));
|
|
827
|
+
productDiscussionHost?.releaseTaskWorker(binding.taskId, binding.roleRef);
|
|
730
828
|
reconciliationCoordinator?.kick(binding.taskId);
|
|
829
|
+
productCampaignContinuationCoordinator?.kick(binding.taskId);
|
|
731
830
|
},
|
|
732
831
|
}),
|
|
733
832
|
agent: Object.freeze({
|
|
@@ -764,6 +863,94 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
764
863
|
});
|
|
765
864
|
const authorizeExecution = async (request) => {
|
|
766
865
|
try {
|
|
866
|
+
if (request.capability === "browser.action") {
|
|
867
|
+
if (request.callerRef !== "platform-host:browser-action")
|
|
868
|
+
return false;
|
|
869
|
+
const input = browserActionInputSchema.parse(request.input);
|
|
870
|
+
// Authenticated local Action owner; reload must also work before Monitor initialization.
|
|
871
|
+
if (input.kind === "RELOAD")
|
|
872
|
+
return true;
|
|
873
|
+
if (!monitor)
|
|
874
|
+
return false;
|
|
875
|
+
const state = object(await monitor.invoke("state.read", {}), "monitor state");
|
|
876
|
+
const chats = object(state.chats, "monitor chats");
|
|
877
|
+
const config = object(state.config, "monitor config");
|
|
878
|
+
const allowed = new Set(Object.values(chats).flatMap((chat) => {
|
|
879
|
+
if (!chat || typeof chat !== "object" || !("conversationLocator" in chat) ||
|
|
880
|
+
typeof chat.conversationLocator !== "string")
|
|
881
|
+
return [];
|
|
882
|
+
return [chat.conversationLocator];
|
|
883
|
+
}));
|
|
884
|
+
if (typeof config.projectLocator === "string")
|
|
885
|
+
allowed.add(config.projectLocator);
|
|
886
|
+
allowed.add("chrome://extensions/?id=eehdadpmjffomabiedcjijiakconalab");
|
|
887
|
+
allowed.add("chrome://extensions/?errors=eehdadpmjffomabiedcjijiakconalab");
|
|
888
|
+
return input.targets.every((target) => allowed.has(target.url));
|
|
889
|
+
}
|
|
890
|
+
if (request.capability === "product.discussion.bootstrap.deliver")
|
|
891
|
+
return productDiscussionHost
|
|
892
|
+
? productDiscussionHost.authorizeBootstrapExecution(request)
|
|
893
|
+
: false;
|
|
894
|
+
if (request.capability === "monitor.chat.submit" ||
|
|
895
|
+
request.capability === "monitor.chat.create") {
|
|
896
|
+
if (!monitor)
|
|
897
|
+
return false;
|
|
898
|
+
if (request.callerRef === "platform-host:monitor-drive") {
|
|
899
|
+
const authorization = object(await monitor.invoke("drive.authorize", { request }), "monitor drive authorization");
|
|
900
|
+
return authorization.authorized === true;
|
|
901
|
+
}
|
|
902
|
+
// Transitional compatibility until legacy Monitor lifecycle code is removed.
|
|
903
|
+
return authorizeMonitorExecution({
|
|
904
|
+
monitor,
|
|
905
|
+
request: request,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
if (request.capability === "product.review.deliver") {
|
|
909
|
+
if (request.callerRef !== "platform-host:campaign-continuation")
|
|
910
|
+
return false;
|
|
911
|
+
if (request.taskId !== undefined ||
|
|
912
|
+
request.nodeId !== undefined ||
|
|
913
|
+
request.runNo !== undefined ||
|
|
914
|
+
request.workerRef !== undefined ||
|
|
915
|
+
request.projectRoot !== undefined)
|
|
916
|
+
return false;
|
|
917
|
+
const reviewInput = object(request.input, "product review execution input");
|
|
918
|
+
const reviewRef = string(reviewInput.reviewRef, "input.reviewRef");
|
|
919
|
+
const discussionRef = string(reviewInput.discussionRef, "input.discussionRef");
|
|
920
|
+
const discussionGeneration = positiveInteger(reviewInput.discussionGeneration, "input.discussionGeneration");
|
|
921
|
+
const roleRef = string(reviewInput.roleRef, "input.roleRef");
|
|
922
|
+
const conversationRef = string(reviewInput.conversationRef, "input.conversationRef");
|
|
923
|
+
const conversationLocator = string(reviewInput.conversationLocator, "input.conversationLocator");
|
|
924
|
+
const campaignRef = string(reviewInput.campaignRef, "input.campaignRef");
|
|
925
|
+
const intentRef = string(reviewInput.intentRef, "input.intentRef");
|
|
926
|
+
const terminalTaskId = string(reviewInput.terminalTaskId, "input.terminalTaskId");
|
|
927
|
+
const terminalTaskVersion = positiveInteger(reviewInput.terminalTaskVersion, "input.terminalTaskVersion");
|
|
928
|
+
if (request.roleRef !== roleRef)
|
|
929
|
+
return false;
|
|
930
|
+
const productRole = agent
|
|
931
|
+
.listRegisteredRoles()
|
|
932
|
+
.find((candidate) => candidate.agentPackageRef === "@tomflow/proflow-agent-product");
|
|
933
|
+
if (!productRole || productRole.roleRef !== roleRef)
|
|
934
|
+
return false;
|
|
935
|
+
const session = agent.getProductDiscussionSession(discussionRef);
|
|
936
|
+
if (session.status !== "ACTIVE" ||
|
|
937
|
+
session.roleRef !== roleRef ||
|
|
938
|
+
session.generation !== discussionGeneration ||
|
|
939
|
+
session.conversationRef !== conversationRef ||
|
|
940
|
+
session.conversationLocator !== conversationLocator ||
|
|
941
|
+
session.campaignRef !== campaignRef ||
|
|
942
|
+
session.currentIntentRef !== intentRef)
|
|
943
|
+
return false;
|
|
944
|
+
const terminalTask = unwrap(task.queries.getTask({ taskId: terminalTaskId }));
|
|
945
|
+
if (!taskIsTerminal(terminalTask.status) ||
|
|
946
|
+
terminalTask.version !== terminalTaskVersion)
|
|
947
|
+
return false;
|
|
948
|
+
const expectedReviewRef = `product-review:${campaignRef}:${terminalTaskId}:${terminalTaskVersion}:${discussionRef}:${discussionGeneration}`;
|
|
949
|
+
if (reviewRef !== expectedReviewRef ||
|
|
950
|
+
reviewInput.contentFingerprint !== expectedReviewRef)
|
|
951
|
+
return false;
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
767
954
|
const browserCapability = request.capability === "worker.create" ||
|
|
768
955
|
request.capability === "worker.restore" ||
|
|
769
956
|
request.capability === "worker.wake" ||
|
|
@@ -908,6 +1095,29 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
908
1095
|
throw new Error("ROLE_NOT_FOUND");
|
|
909
1096
|
return role;
|
|
910
1097
|
};
|
|
1098
|
+
const productRole = agent
|
|
1099
|
+
.listRegisteredRoles()
|
|
1100
|
+
.find((candidate) => candidate.agentPackageRef === "@tomflow/proflow-agent-product");
|
|
1101
|
+
const productDiscussionHost = productRole
|
|
1102
|
+
? createProductDiscussionHost({
|
|
1103
|
+
productRoleRef: productRole.roleRef,
|
|
1104
|
+
listTaskIds: () => unwrap(task.queries.listTasks({})).tasks.map((summary) => summary.taskId),
|
|
1105
|
+
getTaskFact: (taskId) => {
|
|
1106
|
+
const current = unwrap(task.queries.getTask({ taskId }));
|
|
1107
|
+
return {
|
|
1108
|
+
taskId: current.taskId,
|
|
1109
|
+
status: current.status,
|
|
1110
|
+
roleBindings: current.roleBindings,
|
|
1111
|
+
};
|
|
1112
|
+
},
|
|
1113
|
+
registerDiscussionSession: (input) => agent.registerProductDiscussionSession(input),
|
|
1114
|
+
getDiscussionSession: (discussionRef) => agent.getProductDiscussionSession(discussionRef),
|
|
1115
|
+
async executeCapability(request) {
|
|
1116
|
+
return execution.invoke("executeCapability", request);
|
|
1117
|
+
},
|
|
1118
|
+
})
|
|
1119
|
+
: undefined;
|
|
1120
|
+
await productDiscussionHost?.recoverReservations();
|
|
911
1121
|
const browserPermissionRole = (roleRef) => {
|
|
912
1122
|
const role = agent.listRegisteredRoles().find((candidate) => candidate.roleRef === roleRef);
|
|
913
1123
|
if (!role || !rolePackageRefs.includes(role.agentPackageRef))
|
|
@@ -953,6 +1163,49 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
953
1163
|
}
|
|
954
1164
|
if (operation === "role.list")
|
|
955
1165
|
return agent.listRegisteredRoles();
|
|
1166
|
+
if (operation === "product.campaign.authorize") {
|
|
1167
|
+
const discussionRef = string(value.discussionRef, "discussionRef");
|
|
1168
|
+
const campaignRef = string(value.campaignRef, "campaignRef");
|
|
1169
|
+
const goalRevision = positiveInteger(value.goalRevision, "goalRevision");
|
|
1170
|
+
const productRole = roleForPackage("@tomflow/proflow-agent-product");
|
|
1171
|
+
const before = agent.getProductDiscussionSession(discussionRef);
|
|
1172
|
+
if (before.status !== "ACTIVE" || before.roleRef !== productRole.roleRef)
|
|
1173
|
+
throw new Error("PRODUCT_DISCUSSION_SESSION_NOT_READY");
|
|
1174
|
+
const granted = unwrap(task.commands.createCampaignAuthorization({
|
|
1175
|
+
...(value.authorizationRef === undefined
|
|
1176
|
+
? {}
|
|
1177
|
+
: { authorizationRef: string(value.authorizationRef, "authorizationRef") }),
|
|
1178
|
+
campaignRef,
|
|
1179
|
+
discussionRef,
|
|
1180
|
+
goalRevision,
|
|
1181
|
+
goalSummary: string(value.goalSummary, "goalSummary"),
|
|
1182
|
+
scope: value.scope,
|
|
1183
|
+
constraints: value.constraints,
|
|
1184
|
+
actorRef: "trusted:product-control",
|
|
1185
|
+
idempotencyKey: string(value.idempotencyKey, "idempotencyKey"),
|
|
1186
|
+
}));
|
|
1187
|
+
const current = agent.getProductDiscussionSession(discussionRef);
|
|
1188
|
+
if (current.status !== "ACTIVE" || current.roleRef !== productRole.roleRef)
|
|
1189
|
+
throw new Error("PRODUCT_DISCUSSION_SESSION_NOT_READY");
|
|
1190
|
+
const discussionSession = current.campaignRef === campaignRef &&
|
|
1191
|
+
current.goalRevision === goalRevision
|
|
1192
|
+
? current
|
|
1193
|
+
: agent.updateProductDiscussionSessionContext({
|
|
1194
|
+
discussionRef,
|
|
1195
|
+
expectedVersion: current.version,
|
|
1196
|
+
campaignRef,
|
|
1197
|
+
goalRevision,
|
|
1198
|
+
});
|
|
1199
|
+
return { ...granted, discussionSession };
|
|
1200
|
+
}
|
|
1201
|
+
if (operation === "product.campaign.revoke")
|
|
1202
|
+
return unwrap(task.commands.revokeCampaignAuthorization({
|
|
1203
|
+
authorizationRef: string(value.authorizationRef, "authorizationRef"),
|
|
1204
|
+
expectedAuthorizationVersion: positiveInteger(value.expectedAuthorizationVersion, "expectedAuthorizationVersion"),
|
|
1205
|
+
reason: string(value.reason, "reason"),
|
|
1206
|
+
actorRef: "trusted:product-control",
|
|
1207
|
+
idempotencyKey: string(value.idempotencyKey, "idempotencyKey"),
|
|
1208
|
+
}));
|
|
956
1209
|
const agentPackageRef = string(value.agentPackageRef, "agentPackageRef");
|
|
957
1210
|
const role = roleForPackage(agentPackageRef);
|
|
958
1211
|
if (operation === "role.show")
|
|
@@ -989,9 +1242,12 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
989
1242
|
const binding = current.roleBindings.find((candidate) => candidate.agentPackageRef === agentPackageRef);
|
|
990
1243
|
if (!binding)
|
|
991
1244
|
throw new Error("TASK_ROLE_BINDING_REQUIRED");
|
|
992
|
-
if (binding.workerRef && binding.conversationLocator)
|
|
1245
|
+
if (binding.workerRef && binding.conversationLocator) {
|
|
1246
|
+
productDiscussionHost?.releaseTaskWorker(taskId, binding.roleRef);
|
|
993
1247
|
return;
|
|
1248
|
+
}
|
|
994
1249
|
const role = roleForPackage(agentPackageRef);
|
|
1250
|
+
productDiscussionHost?.reserveTaskWorker(taskId, role.roleRef);
|
|
995
1251
|
const executionRecord = object(await execution.invoke("executeCapability", {
|
|
996
1252
|
contract: "execution",
|
|
997
1253
|
contractVersion: "1.0.0",
|
|
@@ -1007,12 +1263,18 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1007
1263
|
bootstrapFingerprint: `new-task:${taskId}:${agentPackageRef}`,
|
|
1008
1264
|
},
|
|
1009
1265
|
}), "worker create execution");
|
|
1010
|
-
if (executionRecord.status !== "SUCCEEDED")
|
|
1266
|
+
if (executionRecord.status !== "SUCCEEDED") {
|
|
1267
|
+
current = unwrap(task.queries.getTask({ taskId }));
|
|
1268
|
+
const observed = current.roleBindings.find((candidate) => candidate.agentPackageRef === agentPackageRef);
|
|
1269
|
+
if (observed?.workerRef && observed.conversationLocator)
|
|
1270
|
+
productDiscussionHost?.releaseTaskWorker(taskId, observed.roleRef);
|
|
1011
1271
|
throw new Error(`WORKER_CREATE_NOT_CONFIRMED:${String(executionRecord.status)}`);
|
|
1272
|
+
}
|
|
1012
1273
|
current = unwrap(task.queries.getTask({ taskId }));
|
|
1013
1274
|
const persisted = current.roleBindings.find((candidate) => candidate.agentPackageRef === agentPackageRef);
|
|
1014
1275
|
if (!persisted?.workerRef || !persisted.conversationLocator)
|
|
1015
1276
|
throw new Error("WORKER_CREATE_BINDING_NOT_PERSISTED");
|
|
1277
|
+
productDiscussionHost?.releaseTaskWorker(taskId, persisted.roleRef);
|
|
1016
1278
|
};
|
|
1017
1279
|
const waitFor = new Set(options?.waitFor ?? []);
|
|
1018
1280
|
const shouldWait = (agentPackageRef) => waitFor.size === 0 || waitFor.has(roleForPackage(agentPackageRef).roleRef);
|
|
@@ -1081,6 +1343,180 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1081
1343
|
getProjection: (taskId) => taskDriverPorts.getTaskDriveProjection(taskId),
|
|
1082
1344
|
requestWake: requestTaskWake,
|
|
1083
1345
|
});
|
|
1346
|
+
productCampaignContinuationCoordinator = createProductCampaignContinuationCoordinator({
|
|
1347
|
+
async getProjection(taskId) {
|
|
1348
|
+
const result = task.queries.getProductContinuationProjection({ taskId });
|
|
1349
|
+
if (!result.ok) {
|
|
1350
|
+
const code = typeof result.error === "object" && result.error !== null
|
|
1351
|
+
? Reflect.get(result.error, "code")
|
|
1352
|
+
: undefined;
|
|
1353
|
+
if (code === "PRODUCT_CAMPAIGN_NOT_FOUND")
|
|
1354
|
+
return null;
|
|
1355
|
+
throw new Error(`PRODUCT_CONTINUATION_PROJECTION_FAILED:${String(code ?? "UNKNOWN")}`);
|
|
1356
|
+
}
|
|
1357
|
+
return result.data;
|
|
1358
|
+
},
|
|
1359
|
+
async listTaskIds() {
|
|
1360
|
+
return unwrap(task.queries.listTasks({})).tasks.map((summary) => summary.taskId);
|
|
1361
|
+
},
|
|
1362
|
+
async ensureWorkers(taskId) {
|
|
1363
|
+
await ensureTaskWorkers(taskId);
|
|
1364
|
+
},
|
|
1365
|
+
async transferRequirement(input) {
|
|
1366
|
+
unwrap(task.commands.transferProductIntentToTask({
|
|
1367
|
+
...input,
|
|
1368
|
+
actorRef: "platform:campaign-continuation",
|
|
1369
|
+
}));
|
|
1370
|
+
},
|
|
1371
|
+
async startTask(input) {
|
|
1372
|
+
unwrap(task.commands.startTask({
|
|
1373
|
+
taskId: input.taskId,
|
|
1374
|
+
expectedTaskVersion: input.expectedTaskVersion,
|
|
1375
|
+
startAuthority: {
|
|
1376
|
+
type: "CAMPAIGN_AUTHORIZATION",
|
|
1377
|
+
authorizationRef: input.authorizationRef,
|
|
1378
|
+
expectedAuthorizationVersion: input.expectedAuthorizationVersion,
|
|
1379
|
+
expectedCampaignVersion: input.expectedCampaignVersion,
|
|
1380
|
+
},
|
|
1381
|
+
actorRef: "platform:campaign-continuation",
|
|
1382
|
+
idempotencyKey: input.idempotencyKey,
|
|
1383
|
+
}));
|
|
1384
|
+
},
|
|
1385
|
+
getDiscussionSessionForCampaign: (campaignRef) => agent.getProductDiscussionSessionForCampaign(campaignRef),
|
|
1386
|
+
updateDiscussionSessionContext: (input) => agent.updateProductDiscussionSessionContext(input),
|
|
1387
|
+
kickTaskReconciliation: (taskId) => reconciliationCoordinator?.kick(taskId),
|
|
1388
|
+
async onProductReviewRequired(signal) {
|
|
1389
|
+
const session = agent.getProductDiscussionSession(signal.discussionRef);
|
|
1390
|
+
if (session.status !== "ACTIVE" ||
|
|
1391
|
+
session.roleRef !== signal.roleRef ||
|
|
1392
|
+
session.generation !== signal.discussionGeneration ||
|
|
1393
|
+
session.conversationRef !== signal.conversationRef ||
|
|
1394
|
+
session.conversationLocator !== signal.conversationLocator ||
|
|
1395
|
+
session.campaignRef !== signal.campaignRef ||
|
|
1396
|
+
session.currentIntentRef !== signal.intentRef)
|
|
1397
|
+
throw new Error("PRODUCT_REVIEW_DISCUSSION_STALE");
|
|
1398
|
+
const reviewRequest = {
|
|
1399
|
+
contract: "execution",
|
|
1400
|
+
contractVersion: "1.0.0",
|
|
1401
|
+
idempotencyKey: signal.reviewRef,
|
|
1402
|
+
callerRef: "platform-host:campaign-continuation",
|
|
1403
|
+
correlationId: signal.reviewRef,
|
|
1404
|
+
roleRef: signal.roleRef,
|
|
1405
|
+
capability: "product.review.deliver",
|
|
1406
|
+
input: {
|
|
1407
|
+
reviewRef: signal.reviewRef,
|
|
1408
|
+
discussionRef: signal.discussionRef,
|
|
1409
|
+
discussionGeneration: signal.discussionGeneration,
|
|
1410
|
+
roleRef: signal.roleRef,
|
|
1411
|
+
conversationRef: signal.conversationRef,
|
|
1412
|
+
conversationLocator: signal.conversationLocator,
|
|
1413
|
+
campaignRef: signal.campaignRef,
|
|
1414
|
+
intentRef: signal.intentRef,
|
|
1415
|
+
terminalTaskId: signal.taskId,
|
|
1416
|
+
terminalTaskVersion: signal.terminalTaskVersion,
|
|
1417
|
+
contentFingerprint: signal.reviewRef,
|
|
1418
|
+
},
|
|
1419
|
+
};
|
|
1420
|
+
if (!(await authorizeExecution(reviewRequest)))
|
|
1421
|
+
throw new Error("PRODUCT_REVIEW_EXECUTION_IDENTITY_DENIED");
|
|
1422
|
+
let executionRecord;
|
|
1423
|
+
try {
|
|
1424
|
+
executionRecord = object(await execution.invoke("executeCapability", reviewRequest), "product review delivery execution");
|
|
1425
|
+
}
|
|
1426
|
+
catch (error) {
|
|
1427
|
+
await sink.write("platform-host", {
|
|
1428
|
+
timestamp: new Date().toISOString(),
|
|
1429
|
+
level: "WARN",
|
|
1430
|
+
component: "platform-host-product-campaign-continuation",
|
|
1431
|
+
event: "PRODUCT_REVIEW_REQUIRED",
|
|
1432
|
+
eventId: signal.reviewRef,
|
|
1433
|
+
correlationId: signal.reviewRef,
|
|
1434
|
+
status: "UNKNOWN",
|
|
1435
|
+
taskId: signal.taskId,
|
|
1436
|
+
roleRef: signal.roleRef,
|
|
1437
|
+
conversationLocator: signal.conversationLocator,
|
|
1438
|
+
campaignRef: signal.campaignRef,
|
|
1439
|
+
intentRef: signal.intentRef,
|
|
1440
|
+
discussionRef: signal.discussionRef,
|
|
1441
|
+
discussionGeneration: signal.discussionGeneration,
|
|
1442
|
+
terminalTaskVersion: signal.terminalTaskVersion,
|
|
1443
|
+
errorCode: "EXECUTION_OWNER_RESPONSE_UNKNOWN",
|
|
1444
|
+
});
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1447
|
+
const status = string(executionRecord.status, "execution.status");
|
|
1448
|
+
const sideEffectState = string(executionRecord.sideEffectState, "execution.sideEffectState");
|
|
1449
|
+
const executionRef = typeof executionRecord.executionRef === "string"
|
|
1450
|
+
? executionRecord.executionRef
|
|
1451
|
+
: undefined;
|
|
1452
|
+
const executionResult = typeof executionRecord.result === "object" &&
|
|
1453
|
+
executionRecord.result !== null &&
|
|
1454
|
+
!Array.isArray(executionRecord.result)
|
|
1455
|
+
? executionRecord.result
|
|
1456
|
+
: undefined;
|
|
1457
|
+
const resultData = executionResult &&
|
|
1458
|
+
typeof executionResult.data === "object" &&
|
|
1459
|
+
executionResult.data !== null &&
|
|
1460
|
+
!Array.isArray(executionResult.data)
|
|
1461
|
+
? executionResult.data
|
|
1462
|
+
: undefined;
|
|
1463
|
+
const evidenceRef = executionResult?.capability === "product.review.deliver" &&
|
|
1464
|
+
resultData?.reviewRef === signal.reviewRef &&
|
|
1465
|
+
resultData.delivered === true &&
|
|
1466
|
+
typeof resultData.evidenceRef === "string"
|
|
1467
|
+
? resultData.evidenceRef
|
|
1468
|
+
: undefined;
|
|
1469
|
+
const delivered = status === "SUCCEEDED" &&
|
|
1470
|
+
sideEffectState === "APPLIED" &&
|
|
1471
|
+
evidenceRef !== undefined;
|
|
1472
|
+
const error = typeof executionRecord.error === "object" &&
|
|
1473
|
+
executionRecord.error !== null &&
|
|
1474
|
+
!Array.isArray(executionRecord.error)
|
|
1475
|
+
? executionRecord.error
|
|
1476
|
+
: undefined;
|
|
1477
|
+
const errorCode = typeof error?.code === "string" ? error.code : undefined;
|
|
1478
|
+
await sink.write("platform-host", {
|
|
1479
|
+
timestamp: new Date().toISOString(),
|
|
1480
|
+
level: delivered ? "INFO" : "WARN",
|
|
1481
|
+
component: "platform-host-product-campaign-continuation",
|
|
1482
|
+
event: "PRODUCT_REVIEW_REQUIRED",
|
|
1483
|
+
eventId: signal.reviewRef,
|
|
1484
|
+
correlationId: signal.reviewRef,
|
|
1485
|
+
status: delivered
|
|
1486
|
+
? "DELIVERED"
|
|
1487
|
+
: status === "UNKNOWN"
|
|
1488
|
+
? "UNKNOWN"
|
|
1489
|
+
: "FAILED",
|
|
1490
|
+
taskId: signal.taskId,
|
|
1491
|
+
roleRef: signal.roleRef,
|
|
1492
|
+
conversationLocator: signal.conversationLocator,
|
|
1493
|
+
campaignRef: signal.campaignRef,
|
|
1494
|
+
intentRef: signal.intentRef,
|
|
1495
|
+
discussionRef: signal.discussionRef,
|
|
1496
|
+
discussionGeneration: signal.discussionGeneration,
|
|
1497
|
+
terminalTaskVersion: signal.terminalTaskVersion,
|
|
1498
|
+
...(executionRef ? { executionRef } : {}),
|
|
1499
|
+
...(evidenceRef ? { evidenceRef } : {}),
|
|
1500
|
+
...(errorCode ? { errorCode } : {}),
|
|
1501
|
+
});
|
|
1502
|
+
if (!delivered)
|
|
1503
|
+
throw new Error(`PRODUCT_REVIEW_DELIVERY_NOT_CONFIRMED:${status}:${sideEffectState}`);
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
const monitorApplication = observer.port("monitor", {
|
|
1507
|
+
async invoke(operation, rawInput) {
|
|
1508
|
+
object(rawInput, "monitor application input");
|
|
1509
|
+
if (operation === "effect.execute") {
|
|
1510
|
+
parseMonitorEffectRequest(rawInput);
|
|
1511
|
+
return execution.invoke("effect.execute", rawInput);
|
|
1512
|
+
}
|
|
1513
|
+
if (operation !== "drive")
|
|
1514
|
+
throw new Error("UNSUPPORTED_MONITOR_APPLICATION_OPERATION");
|
|
1515
|
+
if (!monitorDriveRelay)
|
|
1516
|
+
throw new Error("MONITOR_CONTROL_UNAVAILABLE");
|
|
1517
|
+
return monitorDriveRelay.drive();
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1084
1520
|
const taskApplication = observer.port("task", {
|
|
1085
1521
|
async invoke(operation, rawInput) {
|
|
1086
1522
|
const value = object(rawInput, "task application input");
|
|
@@ -1243,6 +1679,18 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1243
1679
|
void reconciliationCoordinator?.sweep();
|
|
1244
1680
|
return { scheduled: true };
|
|
1245
1681
|
}
|
|
1682
|
+
if (operation === "product.discussion.register")
|
|
1683
|
+
throw new Error("PRODUCT_DISCUSSION_REGISTER_DIRECT_FORBIDDEN");
|
|
1684
|
+
if (operation === "product.discussion.observe") {
|
|
1685
|
+
if (!productDiscussionHost)
|
|
1686
|
+
throw new Error("PRODUCT_DISCUSSION_ADMISSION_UNAVAILABLE");
|
|
1687
|
+
return productDiscussionHost.observe({
|
|
1688
|
+
conversationLocator: string(value.conversationLocator, "conversationLocator"),
|
|
1689
|
+
contentInstanceId: string(value.contentInstanceId, "contentInstanceId"),
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
if (operation === "product.discussion.get")
|
|
1693
|
+
return agent.getProductDiscussionSession(string(value.discussionRef, "discussionRef"));
|
|
1246
1694
|
if (operation === "browser.permission.classify") {
|
|
1247
1695
|
const roleRef = string(value.roleRef, "roleRef");
|
|
1248
1696
|
if (value.taskId !== undefined && typeof value.taskId !== "string")
|
|
@@ -1374,6 +1822,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1374
1822
|
},
|
|
1375
1823
|
});
|
|
1376
1824
|
reconciliationCoordinator.start();
|
|
1825
|
+
productCampaignContinuationCoordinator.start();
|
|
1377
1826
|
return Object.freeze({
|
|
1378
1827
|
route,
|
|
1379
1828
|
operationSink: sink,
|
|
@@ -1385,6 +1834,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1385
1834
|
taskApplication,
|
|
1386
1835
|
approvalApplication,
|
|
1387
1836
|
observerApplication,
|
|
1837
|
+
monitorApplication,
|
|
1388
1838
|
async lookup(_operationId, _authenticatedRoleRef, _input) {
|
|
1389
1839
|
throw Object.assign(new Error("ACTION_RESULT_LOOKUP_UNSUPPORTED"), { httpStatus: 409 });
|
|
1390
1840
|
},
|
|
@@ -1414,8 +1864,16 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1414
1864
|
model: modelStatus,
|
|
1415
1865
|
};
|
|
1416
1866
|
},
|
|
1417
|
-
close() {
|
|
1867
|
+
async close() {
|
|
1868
|
+
productCampaignContinuationCoordinator?.stop();
|
|
1418
1869
|
reconciliationCoordinator?.stop();
|
|
1870
|
+
ownerShutdown.abort();
|
|
1871
|
+
await Promise.all([
|
|
1872
|
+
productCampaignContinuationCoordinator?.drain(),
|
|
1873
|
+
reconciliationCoordinator?.drain(),
|
|
1874
|
+
]);
|
|
1875
|
+
productDiscussionHost?.clear();
|
|
1876
|
+
await sink.drain();
|
|
1419
1877
|
agent.close();
|
|
1420
1878
|
taskStore.close();
|
|
1421
1879
|
},
|
|
@@ -1659,9 +2117,9 @@ export function createPlatformHost(input) {
|
|
|
1659
2117
|
? await readExecutionTransportCredential(input.config.executionTransportCredentialFile)
|
|
1660
2118
|
: input.executionCredential;
|
|
1661
2119
|
log("DEPENDENCY_INITIALIZATION_STARTED", {
|
|
1662
|
-
order: ["task", "agent", "execution-client", "model-client"],
|
|
2120
|
+
order: ["task", "agent", "execution-client", "model-client", "monitor-control-client"],
|
|
1663
2121
|
});
|
|
1664
|
-
graph = await constructGraph(input.config, executionTransportCredential, modelTransportCredential, input.resolveOwnerConnection, input.resolveLocalToolConnection);
|
|
2122
|
+
graph = await constructGraph(input.config, executionTransportCredential, modelTransportCredential, input.resolveOwnerConnection, input.resolveLocalToolConnection, input.resolveMonitorControlConnection);
|
|
1665
2123
|
server = createServer((request, response) => {
|
|
1666
2124
|
const work = operationContext.run({ operationRef: incomingOperationRef(request.headers["x-proflow-operation-ref"]) }, async () => {
|
|
1667
2125
|
const url = new URL(request.url ?? "/", "http://platform-host.local");
|
|
@@ -1745,6 +2203,36 @@ export function createPlatformHost(input) {
|
|
|
1745
2203
|
});
|
|
1746
2204
|
}
|
|
1747
2205
|
}
|
|
2206
|
+
if (request.method === "POST" && url.pathname === "/application/monitor") {
|
|
2207
|
+
if (!taskApplicationCredential ||
|
|
2208
|
+
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
2209
|
+
return respond(response, 401, {
|
|
2210
|
+
error: "MONITOR_APPLICATION_AUTH_FAILED",
|
|
2211
|
+
});
|
|
2212
|
+
const chunks = [];
|
|
2213
|
+
let bytes = 0;
|
|
2214
|
+
for await (const chunk of request) {
|
|
2215
|
+
const buffer = Buffer.isBuffer(chunk)
|
|
2216
|
+
? chunk
|
|
2217
|
+
: Buffer.from(chunk);
|
|
2218
|
+
bytes += buffer.byteLength;
|
|
2219
|
+
if (bytes > 65_536)
|
|
2220
|
+
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
2221
|
+
chunks.push(buffer);
|
|
2222
|
+
}
|
|
2223
|
+
const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "monitor application request");
|
|
2224
|
+
try {
|
|
2225
|
+
const result = await graph.monitorApplication.invoke(string(body.operation, "operation"), body.input ?? {});
|
|
2226
|
+
return respond(response, 200, result);
|
|
2227
|
+
}
|
|
2228
|
+
catch (error) {
|
|
2229
|
+
return respond(response, 400, {
|
|
2230
|
+
error: error instanceof Error
|
|
2231
|
+
? error.message
|
|
2232
|
+
: "INVALID_REQUEST",
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
1748
2236
|
if (request.method === "POST" && url.pathname === "/application/observer") {
|
|
1749
2237
|
if (!taskApplicationCredential ||
|
|
1750
2238
|
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
@@ -1882,7 +2370,7 @@ export function createPlatformHost(input) {
|
|
|
1882
2370
|
accepting = false;
|
|
1883
2371
|
server?.close();
|
|
1884
2372
|
server = undefined;
|
|
1885
|
-
graph?.close();
|
|
2373
|
+
await graph?.close();
|
|
1886
2374
|
graph = undefined;
|
|
1887
2375
|
roleManagementCredential = undefined;
|
|
1888
2376
|
taskApplicationCredential = undefined;
|
|
@@ -1902,7 +2390,7 @@ export function createPlatformHost(input) {
|
|
|
1902
2390
|
if (running)
|
|
1903
2391
|
await new Promise((resolveStop, reject) => running.close((error) => (error ? reject(error) : resolveStop())));
|
|
1904
2392
|
await Promise.allSettled([...active]);
|
|
1905
|
-
graph?.close();
|
|
2393
|
+
await graph?.close();
|
|
1906
2394
|
graph = undefined;
|
|
1907
2395
|
roleManagementCredential = undefined;
|
|
1908
2396
|
taskApplicationCredential = undefined;
|