assistsx-js 0.2.2 → 0.2.4

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,128 @@
1
+ import { Step, type StepImpl } from "../step";
2
+ import { StepError } from "../step-error";
3
+ import type { StepFlowConfig, StepFlowData, StepFlowOutcome } from "./types";
4
+
5
+ function ensureFlowMeta(step: Step, config: StepFlowConfig): void {
6
+ const data = step.data as StepFlowData;
7
+ if (!data.__flow) {
8
+ data.__flow = { id: config.id, state: config.initial };
9
+ }
10
+ }
11
+
12
+ function setFlowState(step: Step, state: string, config: StepFlowConfig): void {
13
+ const data = step.data as StepFlowData;
14
+ if (!data.__flow) {
15
+ data.__flow = { id: config.id, state };
16
+ } else {
17
+ data.__flow.state = state;
18
+ }
19
+ }
20
+
21
+ function resolveOutcome(
22
+ config: StepFlowConfig,
23
+ currentState: string,
24
+ outcome: StepFlowOutcome
25
+ ): string | null | undefined | "legacy" {
26
+ if (outcome.type === "end") {
27
+ return undefined;
28
+ }
29
+ if (outcome.type === "repeat") {
30
+ return null;
31
+ }
32
+ if (outcome.type === "legacy") {
33
+ return "legacy";
34
+ }
35
+ const stateDef = config.states[currentState];
36
+ if (!stateDef) {
37
+ throw new StepError(`StepFlow: unknown state "${currentState}"`, {
38
+ flowId: config.id,
39
+ state: currentState,
40
+ });
41
+ }
42
+ const nextState = stateDef.on[outcome.name];
43
+ if (!nextState) {
44
+ throw new StepError(
45
+ `StepFlow: no transition for event "${outcome.name}" in state "${currentState}"`,
46
+ { flowId: config.id, state: currentState, event: outcome.name }
47
+ );
48
+ }
49
+ return nextState;
50
+ }
51
+
52
+ function createDispatcher(config: StepFlowConfig): StepImpl {
53
+ const dispatcher: StepImpl = async (step) => {
54
+ ensureFlowMeta(step, config);
55
+ const data = step.data as StepFlowData;
56
+ const currentState = data.__flow!.state;
57
+ const stateDef = config.states[currentState];
58
+ if (!stateDef) {
59
+ throw new StepError(`StepFlow: unknown state "${currentState}"`, {
60
+ flowId: config.id,
61
+ state: currentState,
62
+ });
63
+ }
64
+
65
+ const outcome = await stateDef.run(step);
66
+ const resolved = resolveOutcome(config, currentState, outcome);
67
+
68
+ if (resolved === undefined) {
69
+ return undefined;
70
+ }
71
+ if (resolved === null) {
72
+ return step.repeat();
73
+ }
74
+ if (resolved === "legacy") {
75
+ if (outcome.type !== "legacy") {
76
+ throw new StepError("StepFlow: internal legacy outcome mismatch");
77
+ }
78
+ return step.next(outcome.impl);
79
+ }
80
+
81
+ setFlowState(step, resolved, config);
82
+ return step.next(dispatcher);
83
+ };
84
+ return dispatcher;
85
+ }
86
+
87
+ /** 创建流程调度 StepImpl(需先填充 config.states,见 createLaunchState) */
88
+ export function createFlowDispatcher(config: StepFlowConfig): StepImpl {
89
+ return createDispatcher(config);
90
+ }
91
+
92
+ export function buildFlowInitialData(config: StepFlowConfig): StepFlowData {
93
+ return {
94
+ payload: config.data ?? {},
95
+ __flow: {
96
+ id: config.id,
97
+ state: config.initial,
98
+ },
99
+ };
100
+ }
101
+
102
+ export class StepFlow {
103
+ /**
104
+ * 按状态机配置运行流程,内部复用 Step.run 与现有步骤循环。
105
+ */
106
+ static async run(
107
+ config: StepFlowConfig,
108
+ options?: {
109
+ stepId?: string;
110
+ tag?: string;
111
+ delayMs?: number;
112
+ exceptionRetryCountMax?: number;
113
+ }
114
+ ): Promise<Step | undefined> {
115
+ if (!config.states[config.initial]) {
116
+ throw new StepError(
117
+ `StepFlow: initial state "${config.initial}" is not defined`,
118
+ { flowId: config.id }
119
+ );
120
+ }
121
+ const dispatcher = createDispatcher(config);
122
+ return Step.run(dispatcher, {
123
+ ...options,
124
+ tag: options?.tag ?? config.id,
125
+ data: buildFlowInitialData(config),
126
+ });
127
+ }
128
+ }
package/src/step.ts CHANGED
@@ -6,11 +6,15 @@ import { AssistsX } from "./assistsx";
6
6
  import { Node } from "./node";
7
7
  import { CallMethod } from "./call-method";
8
8
  import { useStepStore } from "./step-state-store";
9
+ import { ensureAssistsXPinia } from "./pinia-ensure";
9
10
  import { generateUUID } from "./utils";
10
11
  import { StepError, StepStopError } from "./step-error";
11
12
  import { StepAsync } from "./step-async";
12
13
  import type { NodeLookupScope } from "./node-lookup-scope";
13
14
 
15
+ // 步骤数据类型,始终为普通对象
16
+ export type StepData = Record<string, any>;
17
+
14
18
  // 步骤结果类型,可以是Step实例或undefined
15
19
  export type StepResult = Step | undefined;
16
20
 
@@ -27,6 +31,34 @@ export class Step {
27
31
  static showLog: boolean = false;
28
32
  static exceptionRetryCountMaxDefault: number = 3;
29
33
 
34
+ /**
35
+ * 判断步骤数据是否有效(非空且为普通对象)
36
+ */
37
+ private static isValidStepData(data: unknown): data is StepData {
38
+ return (
39
+ data !== null &&
40
+ data !== undefined &&
41
+ typeof data === "object" &&
42
+ !Array.isArray(data)
43
+ );
44
+ }
45
+
46
+ /**
47
+ * 解析步骤数据:优先使用传入值,否则使用当前值,均无效时返回空对象
48
+ */
49
+ private static resolveStepData(
50
+ provided?: StepData,
51
+ fallback?: StepData
52
+ ): StepData {
53
+ if (provided !== undefined && Step.isValidStepData(provided)) {
54
+ return provided;
55
+ }
56
+ if (fallback !== undefined && Step.isValidStepData(fallback)) {
57
+ return fallback;
58
+ }
59
+ return {};
60
+ }
61
+
30
62
  /**
31
63
  * 当前执行步骤的ID
32
64
  */
@@ -60,13 +92,15 @@ export class Step {
60
92
  }: {
61
93
  stepId?: string | undefined;
62
94
  tag?: string | undefined;
63
- data?: any | undefined;
95
+ data?: StepData;
64
96
  delayMs?: number;
65
97
  exceptionRetryCountMax?: number;
66
98
  } = {}
67
99
  ): Promise<Step | undefined> {
68
100
  this.exception = undefined;
101
+ ensureAssistsXPinia();
69
102
  const stepStore = useStepStore();
103
+ const resolvedData = Step.resolveStepData(data);
70
104
  let implnName = impl.name;
71
105
  let currentStep: Step | undefined;
72
106
  let nextStep: Step | undefined;
@@ -84,12 +118,12 @@ export class Step {
84
118
  }
85
119
  }
86
120
 
87
- stepStore.startStep(this._stepId, tag, data);
121
+ stepStore.startStep(this._stepId, tag, resolvedData);
88
122
  currentStep = new Step({
89
123
  stepId: this._stepId,
90
124
  impl,
91
125
  tag,
92
- data,
126
+ data: resolvedData,
93
127
  delayMs,
94
128
  exceptionRetryCountMax,
95
129
  });
@@ -228,7 +262,7 @@ export class Step {
228
262
  const errorMsg = JSON.stringify({
229
263
  impl: implnName,
230
264
  tag: tag,
231
- data: data,
265
+ data: resolvedData,
232
266
  error: e?.message ?? String(e),
233
267
  });
234
268
  stepStore.setError(errorMsg);
@@ -402,7 +436,7 @@ export class Step {
402
436
  /**
403
437
  * 步骤数据
404
438
  */
405
- data: any | undefined;
439
+ data: StepData;
406
440
 
407
441
  /**
408
442
  * 步骤延迟时间(毫秒)
@@ -435,7 +469,7 @@ export class Step {
435
469
  stepId: string;
436
470
  impl: StepImpl | undefined;
437
471
  tag?: string | undefined;
438
- data?: any | undefined;
472
+ data?: StepData;
439
473
  delayMs?: number;
440
474
  repeatCountMax?: number;
441
475
  exceptionRetryCountMax?: number;
@@ -443,7 +477,7 @@ export class Step {
443
477
  }) {
444
478
  this.tag = tag;
445
479
  this.stepId = stepId;
446
- this.data = data ?? {};
480
+ this.data = Step.resolveStepData(data);
447
481
  this.impl = impl;
448
482
  this.delayMs = delayMs;
449
483
  this.repeatCountMax = repeatCountMax;
@@ -472,7 +506,7 @@ export class Step {
472
506
  exceptionRetryCountMax = Step.exceptionRetryCountMaxDefault,
473
507
  }: {
474
508
  tag?: string | undefined;
475
- data?: any | undefined;
509
+ data?: StepData;
476
510
  delayMs?: number;
477
511
  repeatCountMax?: number;
478
512
  exceptionRetryCountMax?: number;
@@ -483,7 +517,7 @@ export class Step {
483
517
  stepId: this.stepId,
484
518
  impl,
485
519
  tag,
486
- data: data ?? this.data ?? {},
520
+ data: Step.resolveStepData(data, this.data),
487
521
  delayMs,
488
522
  repeatCountMax,
489
523
  exceptionRetryCountMax,
@@ -498,7 +532,7 @@ export class Step {
498
532
  exceptionRetryCountMax = Step.exceptionRetryCountMaxDefault,
499
533
  }: {
500
534
  tag?: string | undefined;
501
- data?: any | undefined;
535
+ data?: StepData;
502
536
  delayMs?: number;
503
537
  repeatCountMax?: number;
504
538
  exceptionRetryCountMax?: number;
@@ -509,7 +543,7 @@ export class Step {
509
543
  stepId: this.stepId,
510
544
  impl: undefined,
511
545
  tag,
512
- data: data ?? this.data ?? {},
546
+ data: Step.resolveStepData(data, this.data),
513
547
  delayMs,
514
548
  repeatCountMax,
515
549
  exceptionRetryCountMax,
@@ -528,14 +562,14 @@ export class Step {
528
562
  repeat({
529
563
  stepId = this.stepId,
530
564
  tag = this.tag,
531
- data = this.data ?? {},
565
+ data,
532
566
  delayMs = this.delayMs,
533
567
  repeatCountMax = this.repeatCountMax,
534
568
  exceptionRetryCountMax = this.exceptionRetryCountMax,
535
569
  }: {
536
570
  stepId?: string;
537
571
  tag?: string | undefined;
538
- data?: any | undefined;
572
+ data?: StepData;
539
573
  delayMs?: number;
540
574
  repeatCountMax?: number;
541
575
  exceptionRetryCountMax?: number;
@@ -544,7 +578,7 @@ export class Step {
544
578
  this.repeatCount++;
545
579
  this.stepId = stepId;
546
580
  this.tag = tag;
547
- this.data = data;
581
+ this.data = Step.resolveStepData(data, this.data);
548
582
  this.delayMs = delayMs;
549
583
  this.repeatCountMax = repeatCountMax;
550
584
  this.exceptionRetryCountMax = exceptionRetryCountMax;
@@ -90,6 +90,27 @@ export class WindowFlags {
90
90
  return flags & ~flag;
91
91
  }
92
92
 
93
+ /** 输入框获取焦点时使用的悬浮窗 flag 组合(不含 FLAG_NOT_FOCUSABLE) */
94
+ static getOverlayInputFocusFlagList(): number[] {
95
+ return [
96
+ WindowFlags.FLAG_WATCH_OUTSIDE_TOUCH,
97
+ WindowFlags.FLAG_NOT_TOUCH_MODAL,
98
+ WindowFlags.FLAG_LAYOUT_NO_LIMITS,
99
+ WindowFlags.FLAG_LAYOUT_IN_SCREEN,
100
+ ];
101
+ }
102
+
103
+ /** 输入框失去焦点时使用的悬浮窗 flag 组合(含 FLAG_NOT_FOCUSABLE) */
104
+ static getOverlayInputBlurFlagList(): number[] {
105
+ return [
106
+ WindowFlags.FLAG_WATCH_OUTSIDE_TOUCH,
107
+ WindowFlags.FLAG_NOT_TOUCH_MODAL,
108
+ WindowFlags.FLAG_LAYOUT_NO_LIMITS,
109
+ WindowFlags.FLAG_NOT_FOCUSABLE,
110
+ WindowFlags.FLAG_LAYOUT_IN_SCREEN,
111
+ ];
112
+ }
113
+
93
114
  /**
94
115
  * 获取所有标志位的描述信息
95
116
  * @returns 标志位描述对象数组