@haiiro/docuflow 0.0.16 → 0.0.18

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.
Files changed (28) hide show
  1. package/README.md +8 -2
  2. package/manifest.json +34 -19
  3. package/package.json +1 -1
  4. package/public/app/docflow-app.js +5 -5
  5. package/src/front-components/docflow-documents.front-component.mjs +1 -1
  6. package/src/front-components/docflow-processes.front-component.mjs +35 -35
  7. package/src/front-components/docflow-processes.front-component.tsx +187 -85
  8. package/src/front-components/docflow-tasks.front-component.mjs +24 -24
  9. package/src/front-components/launch-process.front-component.mjs +19 -19
  10. package/src/front-components/launch-process.front-component.tsx +36 -60
  11. package/src/front-components/process-steps.front-component.mjs +17 -17
  12. package/src/front-components/task-form.front-component.mjs +10 -10
  13. package/src/logic-functions/bitrix-app.logic-function.mjs +1 -1
  14. package/src/logic-functions/complete-task.logic-function.mjs +39 -8
  15. package/src/logic-functions/docflow-app.logic-function.mjs +1 -1
  16. package/src/logic-functions/hand-out-form-step.logic-function.mjs +71284 -11217
  17. package/src/logic-functions/hand-out-form-step.logic-function.ts +6 -1
  18. package/src/logic-functions/launch-process.logic-function.mjs +269 -33
  19. package/src/logic-functions/launch-process.logic-function.ts +42 -37
  20. package/src/logic-functions/on-task-updated.logic-function.mjs +39 -8
  21. package/src/logic-functions/on-workflow-run-updated.logic-function.mjs +48 -9
  22. package/src/logic-functions/on-workflow-run-updated.logic-function.ts +18 -3
  23. package/src/logic-functions/process-catalog.logic-function.mjs +119726 -0
  24. package/src/logic-functions/process-catalog.logic-function.ts +84 -0
  25. package/src/logic-functions/publish-process.logic-function.mjs +218 -16
  26. package/src/logic-functions/publish-process.logic-function.ts +13 -5
  27. package/src/logic-functions/save-launch-fields.logic-function.mjs +5 -2
  28. package/src/logic-functions/save-step-settings.logic-function.mjs +5 -2
@@ -59485,12 +59485,16 @@ var readUserMessage = (value) => {
59485
59485
  }
59486
59486
  return void 0;
59487
59487
  };
59488
+ var isMissingGraphqlField = (error) => error instanceof CoreGraphqlError && /Cannot query field|Unknown type/i.test(error.message);
59488
59489
  var callCoreGraphql = async ({
59489
59490
  query,
59490
- variables
59491
+ variables,
59492
+ runAs = "person"
59491
59493
  }) => {
59492
59494
  const apiUrl = process.env.TWENTY_API_URL;
59493
- const token = process.env.TWENTY_APP_ACCESS_TOKEN;
59495
+ const personToken = process.env.TWENTY_APP_ACCESS_TOKEN;
59496
+ const applicationToken = process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN;
59497
+ const token = runAs === "person" ? personToken ?? applicationToken : applicationToken ?? personToken;
59494
59498
  if (apiUrl === void 0 || token === void 0) {
59495
59499
  throw new CoreGraphqlError(
59496
59500
  "TWENTY_API_URL or TWENTY_APP_ACCESS_TOKEN is not set",
@@ -59515,6 +59519,9 @@ var callCoreGraphql = async ({
59515
59519
  }
59516
59520
  return body.data;
59517
59521
  };
59522
+ var SUBMIT_FORM_STEP_MUTATION = `mutation SubmitFormStep($input: SubmitFormStepInput!) {
59523
+ submitFormStep(input: $input)
59524
+ }`;
59518
59525
  var ANSWER_TOOL_CALL_MUTATION = `mutation AnswerToolCall($input: AnswerToolCallInput!) {
59519
59526
  answerToolCall(input: $input) { streamId }
59520
59527
  }`;
@@ -59574,17 +59581,24 @@ var getFormStepAssignment = ({
59574
59581
  now
59575
59582
  )
59576
59583
  });
59584
+ var getStepWaitKey = ({
59585
+ workflowRunId,
59586
+ stepId,
59587
+ stepInfo
59588
+ }) => typeof stepInfo?.threadId === "string" && stepInfo.threadId.length > 0 ? stepInfo.threadId : `${workflowRunId}.${stepId}`;
59589
+ var isStepWaitingForAnswer = (stepInfo) => stepInfo?.status === "PENDING" && (stepInfo.error === void 0 || stepInfo.error === null) && (typeof stepInfo.threadId === "string" && stepInfo.threadId.length > 0 || stepInfo.wait?.type === "CALLBACK");
59577
59590
 
59578
59591
  // src/server/workflow-engine.ts
59579
59592
  var answerFormStep = async ({
59580
59593
  state,
59594
+ workflowRunId,
59581
59595
  stepId,
59582
- threadId,
59596
+ waitKey,
59583
59597
  values
59584
59598
  }) => {
59585
59599
  const step = state?.flow?.steps?.find((candidate) => candidate.id === stepId);
59586
59600
  const stepInfo = state?.stepInfos?.[stepId];
59587
- if (step === void 0 || !isFormStep(step) || stepInfo?.status !== "PENDING" || stepInfo.threadId !== threadId) {
59601
+ if (step === void 0 || !isFormStep(step) || !isStepWaitingForAnswer(stepInfo) || getStepWaitKey({ workflowRunId, stepId, stepInfo }) !== waitKey) {
59588
59602
  return { status: "NOT_WAITING" };
59589
59603
  }
59590
59604
  const fields = resolveFormStepFields(
@@ -59608,10 +59622,26 @@ var answerFormStep = async ({
59608
59622
  ),
59609
59623
  values
59610
59624
  });
59611
- await callCoreGraphql({
59612
- query: ANSWER_TOOL_CALL_MUTATION,
59613
- variables: { input: { threadId, toolCallId: stepId, response } }
59625
+ const threadId = stepInfo?.threadId;
59626
+ const submitFormStep = () => callCoreGraphql({
59627
+ query: SUBMIT_FORM_STEP_MUTATION,
59628
+ variables: { input: { stepId, workflowRunId, response } }
59614
59629
  });
59630
+ if (typeof threadId === "string" && threadId.length > 0) {
59631
+ try {
59632
+ await callCoreGraphql({
59633
+ query: ANSWER_TOOL_CALL_MUTATION,
59634
+ variables: { input: { threadId, toolCallId: stepId, response } }
59635
+ });
59636
+ } catch (error) {
59637
+ if (!isMissingGraphqlField(error)) {
59638
+ throw error;
59639
+ }
59640
+ await submitFormStep();
59641
+ }
59642
+ } else {
59643
+ await submitFormStep();
59644
+ }
59615
59645
  return { status: "ANSWERED" };
59616
59646
  };
59617
59647
 
@@ -59625,8 +59655,9 @@ var answerStepOfTask = async (task, values) => {
59625
59655
  try {
59626
59656
  const result = await answerFormStep({
59627
59657
  state: run?.state,
59658
+ workflowRunId: task.smbWorkflowRunId,
59628
59659
  stepId: task.smbWorkflowStepId,
59629
- threadId: task.smbWorkflowThreadId,
59660
+ waitKey: task.smbWorkflowThreadId,
59630
59661
  values
59631
59662
  });
59632
59663
  return result.status === "MISSING_REQUIRED" ? { status: "MISSING_REQUIRED", labels: result.labels } : void 0;
@@ -119321,13 +119321,18 @@ var xL = async (e3) => {
119321
119321
  var getDocflowEngine = (value = process.env.DOCFLOW_ENGINE) => value === "FORK" ? "FORK" : "APP";
119322
119322
  var isAppEngine = (value) => getDocflowEngine(value) === "APP";
119323
119323
 
119324
+ // src/shared/log.util.ts
119325
+ var PREFIX = "[docflow]";
119326
+ var logDocflow = (event, details) => console.info(PREFIX, event, details ?? "");
119327
+
119324
119328
  // src/shared/docflow.constants.ts
119325
119329
  var DOCFLOW_LAUNCH_PARAMETERS_KEY = "smbDocflowParameters";
119326
119330
  var LAUNCH_PARAMETERS_STEP_INPUT = `{{trigger.${DOCFLOW_LAUNCH_PARAMETERS_KEY}}}`;
119327
- var LAUNCH_TRIGGER_NAME = "\u0417\u0430\u043F\u0443\u0441\u043A \u0441 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C\u0438";
119331
+ var DOCFLOW_LAUNCHED_BY_KEY = "smbDocflowLaunchedBy";
119328
119332
  var TASK_DONE_STATUS = "DONE";
119329
119333
  var FORM_FIELD_ROLE_SETTING = "smbRole";
119330
119334
  var FORM_FIELD_DUE_IN_DAYS_SETTING = "smbDueInDays";
119335
+ var TRIGGER_STEP_ID = "trigger";
119331
119336
 
119332
119337
  // src/shared/form/form-fields.util.ts
119333
119338
  var getFormFieldRole = (field) => {
@@ -119340,8 +119345,7 @@ var parseFormFields = (value) => Array.isArray(value) ? value.filter(
119340
119345
  ) : [];
119341
119346
 
119342
119347
  // src/shared/workflow/launch-step.util.ts
119343
- var isLaunchTrigger = (trigger) => trigger?.type === "WEBHOOK" && trigger.name === LAUNCH_TRIGGER_NAME;
119344
- var isLaunchableTrigger = (trigger) => trigger?.type === "MANUAL" || isLaunchTrigger(trigger);
119348
+ var isLaunchableTrigger = (trigger) => trigger?.type === "MANUAL" || trigger?.type === "WEBHOOK";
119345
119349
 
119346
119350
  // src/shared/workflow/workflow-run.util.ts
119347
119351
  var DAY_IN_MS = 24 * 60 * 60 * 1e3;
@@ -119398,7 +119402,26 @@ var getFormStepAssignment = ({
119398
119402
  now
119399
119403
  )
119400
119404
  });
119405
+ var getStepWaitKey = ({
119406
+ workflowRunId,
119407
+ stepId,
119408
+ stepInfo
119409
+ }) => typeof stepInfo?.threadId === "string" && stepInfo.threadId.length > 0 ? stepInfo.threadId : `${workflowRunId}.${stepId}`;
119410
+ var isStepWaitingForAnswer = (stepInfo) => stepInfo?.status === "PENDING" && (stepInfo.error === void 0 || stepInfo.error === null) && (typeof stepInfo.threadId === "string" && stepInfo.threadId.length > 0 || stepInfo.wait?.type === "CALLBACK");
119411
+ var getRunStarterWorkspaceMemberId = ({
119412
+ createdByWorkspaceMemberId,
119413
+ state
119414
+ }) => {
119415
+ if (typeof createdByWorkspaceMemberId === "string" && createdByWorkspaceMemberId.length > 0) {
119416
+ return createdByWorkspaceMemberId;
119417
+ }
119418
+ const triggerResult = state?.stepInfos?.[TRIGGER_STEP_ID]?.result;
119419
+ return readWorkspaceMemberId(
119420
+ isPlainRecord(triggerResult) ? triggerResult[DOCFLOW_LAUNCHED_BY_KEY] : void 0
119421
+ );
119422
+ };
119401
119423
  var getFormStepTaskRequests = ({
119424
+ workflowRunId,
119402
119425
  state,
119403
119426
  runStarterWorkspaceMemberId,
119404
119427
  now
@@ -119406,9 +119429,13 @@ var getFormStepTaskRequests = ({
119406
119429
  const steps = state?.flow?.steps ?? [];
119407
119430
  const stepInfos = state?.stepInfos ?? {};
119408
119431
  const isStartedByHand = isLaunchableTrigger(state?.flow?.trigger);
119432
+ const runStarterId = getRunStarterWorkspaceMemberId({
119433
+ createdByWorkspaceMemberId: runStarterWorkspaceMemberId,
119434
+ state
119435
+ });
119409
119436
  return steps.flatMap((step) => {
119410
119437
  const stepInfo = stepInfos[step.id];
119411
- if (!isFormStep(step) || stepInfo?.status !== "PENDING" || stepInfo.error !== void 0 && stepInfo.error !== null || typeof stepInfo.threadId !== "string" || stepInfo.threadId.length === 0) {
119438
+ if (!isFormStep(step) || !isStepWaitingForAnswer(stepInfo)) {
119412
119439
  return [];
119413
119440
  }
119414
119441
  const fields = resolveFormStepFields(getFormStepFields(step), stepInfos);
@@ -119416,7 +119443,7 @@ var getFormStepTaskRequests = ({
119416
119443
  fields,
119417
119444
  now
119418
119445
  });
119419
- const fallbackAssigneeId = isStartedByHand ? runStarterWorkspaceMemberId ?? void 0 : void 0;
119446
+ const fallbackAssigneeId = isStartedByHand ? runStarterId : void 0;
119420
119447
  const taskAssigneeId = assigneeWorkspaceMemberId ?? fallbackAssigneeId;
119421
119448
  if (taskAssigneeId === void 0) {
119422
119449
  return [];
@@ -119424,7 +119451,7 @@ var getFormStepTaskRequests = ({
119424
119451
  return [
119425
119452
  {
119426
119453
  stepId: step.id,
119427
- threadId: stepInfo.threadId,
119454
+ waitKey: getStepWaitKey({ workflowRunId, stepId: step.id, stepInfo }),
119428
119455
  title: step.name,
119429
119456
  assigneeWorkspaceMemberId: taskAssigneeId,
119430
119457
  dueAt,
@@ -119497,7 +119524,7 @@ var closeTasksOfAnsweredSteps = async (run) => {
119497
119524
  let closedCount = 0;
119498
119525
  for (const task of tasks) {
119499
119526
  const stepInfo = typeof task.smbWorkflowStepId === "string" ? run.state?.stepInfos?.[task.smbWorkflowStepId] : void 0;
119500
- if (stepInfo?.status !== "SUCCESS" || stepInfo.threadId !== task.smbWorkflowThreadId) {
119527
+ if (typeof task.smbWorkflowStepId !== "string" || stepInfo?.status !== "SUCCESS" || getStepWaitKey({ workflowRunId: run.id, stepId: task.smbWorkflowStepId, stepInfo }) !== task.smbWorkflowThreadId) {
119501
119528
  continue;
119502
119529
  }
119503
119530
  const answer = typeof stepInfo.result === "object" && stepInfo.result !== null ? stepInfo.result : {};
@@ -119524,9 +119551,11 @@ var handler = async (event) => {
119524
119551
  }
119525
119552
  const run = { ...event.properties.after, id: event.recordId };
119526
119553
  if (run.status !== "RUNNING" || run.state === void 0 || run.state === null) {
119554
+ logDocflow("run updated: skipped", { workflowRunId: run.id, status: run.status, hasState: run.state != null });
119527
119555
  return { skipped: "NOT_RUNNING" };
119528
119556
  }
119529
119557
  const requests = getFormStepTaskRequests({
119558
+ workflowRunId: run.id,
119530
119559
  state: run.state,
119531
119560
  runStarterWorkspaceMemberId: run.createdBy?.workspaceMemberId,
119532
119561
  now: /* @__PURE__ */ new Date()
@@ -119538,14 +119567,24 @@ var handler = async (event) => {
119538
119567
  payload: {
119539
119568
  workflowRunId: run.id,
119540
119569
  stepId: request.stepId,
119541
- threadId: request.threadId
119570
+ // The wait key travels as threadId: jobs queued by 0.0.16 and
119571
+ // earlier carry that name
119572
+ threadId: request.waitKey
119542
119573
  },
119543
- jobId: `hand-out-${request.threadId}`
119574
+ jobId: `hand-out-${request.waitKey}`
119544
119575
  })),
119545
119576
  retryLimit: 2
119546
119577
  });
119547
119578
  }
119548
119579
  const closedTaskCount = await closeTasksOfAnsweredSteps(run);
119580
+ logDocflow("run updated", {
119581
+ workflowRunId: run.id,
119582
+ queued: requests.map(({ stepId, waitKey }) => ({ stepId, waitKey })),
119583
+ closedTaskCount,
119584
+ stepStatuses: Object.fromEntries(
119585
+ Object.entries(run.state.stepInfos ?? {}).map(([stepId, info]) => [stepId, info?.status])
119586
+ )
119587
+ });
119549
119588
  return { queuedTaskCount: requests.length, closedTaskCount };
119550
119589
  };
119551
119590
  var on_workflow_run_updated_logic_function_default = defineLogicFunction({
@@ -6,6 +6,7 @@ import {
6
6
  } from 'twenty-sdk/logic-function';
7
7
 
8
8
  import { isAppEngine } from 'src/shared/engine';
9
+ import { logDocflow } from 'src/shared/log.util';
9
10
  import { getFormStepTaskRequests } from 'src/shared/workflow/workflow-run.util';
10
11
  import { closeTasksOfAnsweredSteps } from 'src/server/process-tasks';
11
12
  import { type WorkflowRunRecord } from 'src/server/records';
@@ -16,7 +17,7 @@ import {
16
17
 
17
18
  // Process tasks follow the run: a FORM step the run waits on becomes a CRM
18
19
  // task, and a step answered elsewhere closes its task. Handing out is queued
19
- // under the step's thread id, so repeated run updates create one task.
20
+ // under the step's wait key, so repeated run updates create one task.
20
21
  const handler = async (
21
22
  event: DatabaseEventPayload<ObjectRecordUpdateEvent<WorkflowRunRecord>>,
22
23
  ) => {
@@ -27,10 +28,13 @@ const handler = async (
27
28
  const run = { ...event.properties.after, id: event.recordId };
28
29
 
29
30
  if (run.status !== 'RUNNING' || run.state === undefined || run.state === null) {
31
+ logDocflow('run updated: skipped', { workflowRunId: run.id, status: run.status, hasState: run.state != null });
32
+
30
33
  return { skipped: 'NOT_RUNNING' };
31
34
  }
32
35
 
33
36
  const requests = getFormStepTaskRequests({
37
+ workflowRunId: run.id,
34
38
  state: run.state,
35
39
  runStarterWorkspaceMemberId: run.createdBy?.workspaceMemberId,
36
40
  now: new Date(),
@@ -44,9 +48,11 @@ const handler = async (
44
48
  payload: {
45
49
  workflowRunId: run.id,
46
50
  stepId: request.stepId,
47
- threadId: request.threadId,
51
+ // The wait key travels as threadId: jobs queued by 0.0.16 and
52
+ // earlier carry that name
53
+ threadId: request.waitKey,
48
54
  },
49
- jobId: `hand-out-${request.threadId}`,
55
+ jobId: `hand-out-${request.waitKey}`,
50
56
  })),
51
57
  retryLimit: 2,
52
58
  });
@@ -54,6 +60,15 @@ const handler = async (
54
60
 
55
61
  const closedTaskCount = await closeTasksOfAnsweredSteps(run);
56
62
 
63
+ logDocflow('run updated', {
64
+ workflowRunId: run.id,
65
+ queued: requests.map(({ stepId, waitKey }) => ({ stepId, waitKey })),
66
+ closedTaskCount,
67
+ stepStatuses: Object.fromEntries(
68
+ Object.entries(run.state.stepInfos ?? {}).map(([stepId, info]) => [stepId, info?.status]),
69
+ ),
70
+ });
71
+
57
72
  return { queuedTaskCount: requests.length, closedTaskCount };
58
73
  };
59
74