@chenguangyao/devflow-kit 0.1.43 → 0.1.44
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/client/workflow-adapter.js +3 -0
- package/docs/workflow-orchestration.md +97 -6
- package/docs/workflow-ui-prototype.html +46 -1
- package/package.json +4 -5
- package/schemas/status-surface.schema.json +33 -1
- package/schemas/workflow-adapter-catalog.schema.json +104 -0
- package/schemas/workflow-adapter-surface.schema.json +193 -0
- package/schemas/workflow-confirmation-surface.schema.json +3 -1
- package/schemas/workflow-picker.schema.json +6 -1
- package/schemas/workflow-selection-result.schema.json +69 -0
- package/schemas/workflow-selection.schema.json +84 -0
- package/schemas/workflow-ui-command-result.schema.json +29 -0
- package/schemas/workflow-ui-error.schema.json +49 -0
- package/scripts/render-workflow-ui-prototype.js +141 -4
- package/scripts/workflow-adapter-client-example.js +67 -0
- package/scripts/workflow-ui-browser-smoke.js +175 -0
- package/skills/df-orchestrator/SKILL.md +2 -1
- package/skills/df-orchestrator/references/workflow-state-machine.md +2 -2
- package/src/cli/commands/_helpers.js +1 -0
- package/src/cli/commands/flow.js +146 -16
- package/src/cli/commands/help.js +1 -1
- package/src/cli/commands/status.js +2 -5
- package/src/client/workflow-adapter-client.js +291 -0
- package/src/core/workflow-actions.js +25 -0
- package/src/core/workflow-picker.js +16 -8
- package/src/core/workflow-ui-adapter.js +467 -0
- package/src/core/workflow-ui-host.js +837 -0
- package/docs/migration-from-arb.md +0 -232
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
"definitions": {
|
|
32
32
|
"actionMap": {
|
|
33
33
|
"type": "object",
|
|
34
|
-
"required": ["picker", "applySelection", "check", "confirm"],
|
|
34
|
+
"required": ["picker", "applySelection", "applySelectionFile", "card", "check", "confirm"],
|
|
35
35
|
"properties": {
|
|
36
36
|
"picker": { "type": "string" },
|
|
37
37
|
"applySelection": { "type": "string" },
|
|
38
|
+
"applySelectionFile": { "type": "string" },
|
|
39
|
+
"card": { "type": "string" },
|
|
38
40
|
"check": { "type": "string" },
|
|
39
41
|
"confirm": { "type": "string" }
|
|
40
42
|
},
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
"locked",
|
|
57
57
|
"source",
|
|
58
58
|
"status",
|
|
59
|
+
"after",
|
|
60
|
+
"before",
|
|
59
61
|
"installed",
|
|
60
62
|
"command"
|
|
61
63
|
],
|
|
@@ -73,6 +75,8 @@
|
|
|
73
75
|
"locked": { "type": "boolean" },
|
|
74
76
|
"source": { "type": ["string", "null"] },
|
|
75
77
|
"status": { "type": ["string", "null"] },
|
|
78
|
+
"after": { "type": ["string", "null"] },
|
|
79
|
+
"before": { "type": ["string", "null"] },
|
|
76
80
|
"installed": { "type": "boolean" },
|
|
77
81
|
"command": { "type": ["string", "null"] }
|
|
78
82
|
},
|
|
@@ -80,11 +84,12 @@
|
|
|
80
84
|
},
|
|
81
85
|
"actionMap": {
|
|
82
86
|
"type": "object",
|
|
83
|
-
"required": ["card", "check", "applySelection", "confirm", "suggest"],
|
|
87
|
+
"required": ["card", "check", "applySelection", "applySelectionFile", "confirm", "suggest"],
|
|
84
88
|
"properties": {
|
|
85
89
|
"card": { "type": "string" },
|
|
86
90
|
"check": { "type": "string" },
|
|
87
91
|
"applySelection": { "type": "string" },
|
|
92
|
+
"applySelectionFile": { "type": "string" },
|
|
88
93
|
"confirm": { "type": "string" },
|
|
89
94
|
"suggest": { "type": "string" }
|
|
90
95
|
},
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://devflow.dev/schemas/workflow-selection-result.schema.json",
|
|
4
|
+
"title": "devflow workflow selection result surface",
|
|
5
|
+
"description": "Machine-readable successful response returned by devflow flow apply-selection --json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"type",
|
|
9
|
+
"slug",
|
|
10
|
+
"ok",
|
|
11
|
+
"added",
|
|
12
|
+
"disabled",
|
|
13
|
+
"skipped",
|
|
14
|
+
"diff",
|
|
15
|
+
"actions",
|
|
16
|
+
"nextAction"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"type": { "const": "workflow_selection_result_surface" },
|
|
20
|
+
"slug": { "type": "string" },
|
|
21
|
+
"ok": { "const": true },
|
|
22
|
+
"added": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": { "type": "string" }
|
|
25
|
+
},
|
|
26
|
+
"disabled": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": { "type": "string" }
|
|
29
|
+
},
|
|
30
|
+
"skipped": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"required": ["step", "reason"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"step": { "type": ["string", "null"] },
|
|
37
|
+
"reason": { "type": "string" }
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"diff": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["added", "disabled", "moved", "verifyReports"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"added": { "type": "array", "items": { "type": "string" } },
|
|
47
|
+
"disabled": { "type": "array", "items": { "type": "string" } },
|
|
48
|
+
"moved": { "type": "array", "items": { "type": "string" } },
|
|
49
|
+
"verifyReports": { "type": "array", "items": { "type": "string" } }
|
|
50
|
+
},
|
|
51
|
+
"additionalProperties": true
|
|
52
|
+
},
|
|
53
|
+
"actions": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["picker", "applySelection", "applySelectionFile", "card", "check", "confirm"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"picker": { "type": "string" },
|
|
58
|
+
"applySelection": { "type": "string" },
|
|
59
|
+
"applySelectionFile": { "type": "string" },
|
|
60
|
+
"card": { "type": "string" },
|
|
61
|
+
"check": { "type": "string" },
|
|
62
|
+
"confirm": { "type": "string" }
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": true
|
|
65
|
+
},
|
|
66
|
+
"nextAction": { "type": "string" }
|
|
67
|
+
},
|
|
68
|
+
"additionalProperties": true
|
|
69
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://devflow.dev/schemas/workflow-selection.schema.json",
|
|
4
|
+
"title": "devflow workflow selection input",
|
|
5
|
+
"description": "Input accepted by devflow flow apply-selection --selection=<json>. UI may send a minimal items list or the edited workflow picker surface.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/definitions/selection", "title": "Minimal workflow selection" },
|
|
8
|
+
{ "$ref": "#/definitions/pickerSurface", "title": "Edited workflow picker surface" },
|
|
9
|
+
{ "$ref": "#/definitions/chatSelection", "title": "Edited workflow chat selection" }
|
|
10
|
+
],
|
|
11
|
+
"definitions": {
|
|
12
|
+
"selection": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"required": ["items"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"items": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": { "$ref": "#/definitions/item" }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": true
|
|
22
|
+
},
|
|
23
|
+
"pickerSurface": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"required": ["type", "groups"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"type": { "const": "workflow_picker_surface" },
|
|
28
|
+
"groups": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"required": ["items"],
|
|
33
|
+
"properties": {
|
|
34
|
+
"items": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": { "$ref": "#/definitions/item" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": true
|
|
44
|
+
},
|
|
45
|
+
"chatSelection": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"required": ["type", "groups"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"type": { "const": "workflow_chat_selection" },
|
|
50
|
+
"layout": { "const": "linear-checkboxes" },
|
|
51
|
+
"groups": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["items"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"id": { "type": "string" },
|
|
58
|
+
"label": { "type": "string" },
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": { "$ref": "#/definitions/item" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"additionalProperties": true
|
|
69
|
+
},
|
|
70
|
+
"item": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"required": ["step", "selected"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"step": { "type": "string" },
|
|
75
|
+
"skill": { "type": "string" },
|
|
76
|
+
"selected": { "type": "boolean" },
|
|
77
|
+
"reason": { "type": ["string", "null"] },
|
|
78
|
+
"after": { "type": ["string", "null"] },
|
|
79
|
+
"before": { "type": ["string", "null"] }
|
|
80
|
+
},
|
|
81
|
+
"additionalProperties": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://devflow.dev/schemas/workflow-ui-command-result.schema.json",
|
|
4
|
+
"title": "devflow workflow UI command result surface",
|
|
5
|
+
"description": "Stable command result surface returned by devflow local workflow UI host mutation endpoints.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["type", "schema", "ok", "slug", "output", "error"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"type": {
|
|
10
|
+
"const": "workflow_ui_command_result"
|
|
11
|
+
},
|
|
12
|
+
"schema": {
|
|
13
|
+
"const": "https://devflow.dev/schemas/workflow-ui-command-result.schema.json"
|
|
14
|
+
},
|
|
15
|
+
"ok": {
|
|
16
|
+
"type": "boolean"
|
|
17
|
+
},
|
|
18
|
+
"slug": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"output": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"error": {
|
|
25
|
+
"type": ["string", "null"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"additionalProperties": true
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://devflow.dev/schemas/workflow-ui-error.schema.json",
|
|
4
|
+
"title": "devflow workflow UI error surface",
|
|
5
|
+
"description": "Stable error surface returned by devflow local workflow UI host endpoints.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["type", "schema", "ok", "code", "message"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"type": {
|
|
10
|
+
"const": "workflow_ui_error"
|
|
11
|
+
},
|
|
12
|
+
"schema": {
|
|
13
|
+
"const": "https://devflow.dev/schemas/workflow-ui-error.schema.json"
|
|
14
|
+
},
|
|
15
|
+
"ok": {
|
|
16
|
+
"const": false
|
|
17
|
+
},
|
|
18
|
+
"code": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": [
|
|
21
|
+
"invalid-workflow-selection",
|
|
22
|
+
"invalid-json-body",
|
|
23
|
+
"invalid-checkpoint-resolution",
|
|
24
|
+
"request-body-too-large",
|
|
25
|
+
"route-not-found",
|
|
26
|
+
"devflow-command-failed",
|
|
27
|
+
"devflow-json-missing",
|
|
28
|
+
"workflow-ui-error"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"message": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"details": {},
|
|
35
|
+
"command": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
"exitCode": {
|
|
39
|
+
"type": "number"
|
|
40
|
+
},
|
|
41
|
+
"stdout": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"stderr": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": true
|
|
49
|
+
}
|
|
@@ -10,7 +10,12 @@ const childProcess = require('child_process');
|
|
|
10
10
|
function renderWorkflowUiPrototype(options = {}) {
|
|
11
11
|
const root = options.root || path.resolve(__dirname, '..');
|
|
12
12
|
const fixtures = options.pickers && options.card
|
|
13
|
-
? {
|
|
13
|
+
? {
|
|
14
|
+
pickers: options.pickers,
|
|
15
|
+
card: options.card,
|
|
16
|
+
selectionResults: options.selectionResults || [],
|
|
17
|
+
policyCards: options.policyCards || [],
|
|
18
|
+
}
|
|
14
19
|
: loadFixtureSurfaces({ root });
|
|
15
20
|
const sourceLabel = options.sourceLabel || fixtures.card.slug || fixtures.card.baseRecipe?.id || 'workflow';
|
|
16
21
|
|
|
@@ -35,7 +40,9 @@ function renderWorkflowUiPrototype(options = {}) {
|
|
|
35
40
|
` <code>${escapeHtml(sourceLabel)} · ${escapeHtml(fixtures.card.actions.picker || fixtures.card.nextAction || '-')}</code>`,
|
|
36
41
|
' </section>',
|
|
37
42
|
fixtures.pickers.map((entry) => renderPickerSurface(entry.surface, entry.title)).join('\n'),
|
|
43
|
+
(fixtures.selectionResults || []).map(renderSelectionResultSurface).join('\n'),
|
|
38
44
|
renderConfirmationSurface(fixtures.card),
|
|
45
|
+
(fixtures.policyCards || []).map(renderPolicyConfirmationSurface).join('\n'),
|
|
39
46
|
' </main>',
|
|
40
47
|
'</body>',
|
|
41
48
|
'</html>',
|
|
@@ -50,6 +57,12 @@ function loadFixtureSurfaces({ root = path.resolve(__dirname, '..') } = {}) {
|
|
|
50
57
|
{ title: '风险推荐', surface: readJson(root, 'test/fixtures/workflow-surfaces/picker-risk-recommended.json') },
|
|
51
58
|
],
|
|
52
59
|
card: readJson(root, 'test/fixtures/workflow-surfaces/confirmation-card.json'),
|
|
60
|
+
selectionResults: [
|
|
61
|
+
readJson(root, 'test/fixtures/workflow-surfaces/selection-result.json'),
|
|
62
|
+
],
|
|
63
|
+
policyCards: [
|
|
64
|
+
readJson(root, 'test/fixtures/workflow-surfaces/policy-confirmation-card.json'),
|
|
65
|
+
],
|
|
53
66
|
};
|
|
54
67
|
}
|
|
55
68
|
|
|
@@ -57,15 +70,36 @@ function loadWorkflowSurfacesFromCli({
|
|
|
57
70
|
root = path.resolve(__dirname, '..'),
|
|
58
71
|
slug,
|
|
59
72
|
cwd = process.cwd(),
|
|
73
|
+
selection = null,
|
|
74
|
+
selectionFile = null,
|
|
60
75
|
execFileSync = childProcess.execFileSync,
|
|
61
76
|
} = {}) {
|
|
62
77
|
if (!slug) throw new Error('loadWorkflowSurfacesFromCli requires slug');
|
|
63
78
|
const picker = runDevflowJson(root, cwd, ['flow', 'picker', `--slug=${slug}`, '--json'], execFileSync);
|
|
79
|
+
let selectionArgs = null;
|
|
80
|
+
if (selection) {
|
|
81
|
+
selectionArgs = ['flow', 'apply-selection', `--slug=${slug}`, `--selection=${selection}`, '--json'];
|
|
82
|
+
} else if (selectionFile) {
|
|
83
|
+
selectionArgs = picker.actions?.applySelectionFile
|
|
84
|
+
? actionToDevflowArgs(picker.actions.applySelectionFile, { '<file>': selectionFile })
|
|
85
|
+
: ['flow', 'apply-selection', `--slug=${slug}`, `--selection-file=${selectionFile}`, '--json'];
|
|
86
|
+
}
|
|
87
|
+
const selectionSurface = selectionArgs
|
|
88
|
+
? runDevflowJson(root, cwd, selectionArgs, execFileSync)
|
|
89
|
+
: null;
|
|
64
90
|
const card = runDevflowJson(root, cwd, ['flow', 'card', `--slug=${slug}`, '--json'], execFileSync);
|
|
65
|
-
|
|
91
|
+
const surfaces = {
|
|
66
92
|
pickers: [{ title: '当前 workflow', surface: picker }],
|
|
67
93
|
card,
|
|
94
|
+
selectionResults: [],
|
|
95
|
+
policyCards: [],
|
|
68
96
|
};
|
|
97
|
+
if (selectionSurface?.type === 'workflow_policy_confirmation_surface') {
|
|
98
|
+
surfaces.policyCards.push(selectionSurface);
|
|
99
|
+
} else if (selectionSurface) {
|
|
100
|
+
surfaces.selectionResults.push(selectionSurface);
|
|
101
|
+
}
|
|
102
|
+
return surfaces;
|
|
69
103
|
}
|
|
70
104
|
|
|
71
105
|
function runDevflowJson(root, cwd, args, execFileSync) {
|
|
@@ -77,6 +111,45 @@ function runDevflowJson(root, cwd, args, execFileSync) {
|
|
|
77
111
|
return JSON.parse(String(out));
|
|
78
112
|
}
|
|
79
113
|
|
|
114
|
+
function actionToDevflowArgs(action, replacements = {}) {
|
|
115
|
+
const tokens = splitActionTokens(action).map((token) => replacePlaceholders(token, replacements));
|
|
116
|
+
if (tokens[0] !== 'devflow') {
|
|
117
|
+
throw new Error(`unsupported workflow action: ${action}`);
|
|
118
|
+
}
|
|
119
|
+
return tokens.slice(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function splitActionTokens(action) {
|
|
123
|
+
const tokens = [];
|
|
124
|
+
let token = '';
|
|
125
|
+
let quote = null;
|
|
126
|
+
for (const ch of String(action)) {
|
|
127
|
+
if (quote) {
|
|
128
|
+
if (ch === quote) quote = null;
|
|
129
|
+
else token += ch;
|
|
130
|
+
} else if (ch === '\'' || ch === '"') {
|
|
131
|
+
quote = ch;
|
|
132
|
+
} else if (/\s/.test(ch)) {
|
|
133
|
+
if (token) {
|
|
134
|
+
tokens.push(token);
|
|
135
|
+
token = '';
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
token += ch;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (token) tokens.push(token);
|
|
142
|
+
return tokens;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function replacePlaceholders(value, replacements) {
|
|
146
|
+
let out = value;
|
|
147
|
+
for (const [placeholder, replacement] of Object.entries(replacements)) {
|
|
148
|
+
out = out.split(placeholder).join(replacement);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
80
153
|
function readJson(root, rel) {
|
|
81
154
|
return JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8'));
|
|
82
155
|
}
|
|
@@ -147,6 +220,55 @@ function renderConfirmationSurface(surface) {
|
|
|
147
220
|
].join('\n');
|
|
148
221
|
}
|
|
149
222
|
|
|
223
|
+
function renderSelectionResultSurface(surface) {
|
|
224
|
+
return [
|
|
225
|
+
' <section class="surface result" data-surface="workflow-selection-result" aria-label="Workflow selection result">',
|
|
226
|
+
` <div class="surface-head"><h2>选择已应用</h2><span>${escapeHtml(surface.type)} / ${surface.ok ? 'ok' : 'blocked'}</span></div>`,
|
|
227
|
+
' <div class="result-grid">',
|
|
228
|
+
renderResultMetric('新增', surface.added),
|
|
229
|
+
renderResultMetric('禁用', surface.disabled),
|
|
230
|
+
renderResultMetric('移动', surface.diff?.moved || []),
|
|
231
|
+
renderResultMetric('验证报告', surface.diff?.verifyReports || []),
|
|
232
|
+
' </div>',
|
|
233
|
+
' <div class="actions">',
|
|
234
|
+
` <button type="button">continue</button>`,
|
|
235
|
+
` <code>${escapeHtml(surface.nextAction)}</code>`,
|
|
236
|
+
` <code>${escapeHtml(surface.actions.picker)}</code>`,
|
|
237
|
+
' </div>',
|
|
238
|
+
' </section>',
|
|
239
|
+
].join('\n');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderResultMetric(label, values = []) {
|
|
243
|
+
const text = values.length ? values.join(', ') : '-';
|
|
244
|
+
return [
|
|
245
|
+
' <div>',
|
|
246
|
+
` <small>${escapeHtml(label)}</small>`,
|
|
247
|
+
` <strong>${escapeHtml(text)}</strong>`,
|
|
248
|
+
' </div>',
|
|
249
|
+
].join('\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderPolicyConfirmationSurface(surface) {
|
|
253
|
+
const card = surface.confirmationCard;
|
|
254
|
+
const secondary = (card.secondaryActions || [])[0];
|
|
255
|
+
return [
|
|
256
|
+
' <section class="surface policy" data-surface="workflow-policy-card" aria-label="Workflow policy confirmation card">',
|
|
257
|
+
` <div class="surface-head"><h2>${escapeHtml(card.title || '确认 workflow 风险')}</h2><span>${escapeHtml(card.type)} / ${escapeHtml(surface.checkpoint.status || '-')}</span></div>`,
|
|
258
|
+
` <p>${escapeHtml(card.question || surface.checkpoint.summary || '')}</p>`,
|
|
259
|
+
' <div class="risk-box">',
|
|
260
|
+
` <strong>${escapeHtml(surface.checkpoint.summary || '-')}</strong>`,
|
|
261
|
+
` <small>${escapeHtml(surface.checkpoint.id)} · ${escapeHtml(surface.checkpoint.phase || '-')}</small>`,
|
|
262
|
+
' </div>',
|
|
263
|
+
' <div class="actions">',
|
|
264
|
+
` <button type="button">${escapeHtml(card.primaryAction.id)}</button>`,
|
|
265
|
+
` <code>${escapeHtml(card.primaryAction.command)}</code>`,
|
|
266
|
+
secondary ? ` <code>${escapeHtml(secondary.command)}</code>` : '',
|
|
267
|
+
' </div>',
|
|
268
|
+
' </section>',
|
|
269
|
+
].filter(Boolean).join('\n');
|
|
270
|
+
}
|
|
271
|
+
|
|
150
272
|
function renderTimelineStep(step, num) {
|
|
151
273
|
const tags = [
|
|
152
274
|
step.required ? 'required' : null,
|
|
@@ -188,11 +310,17 @@ function renderCss() {
|
|
|
188
310
|
' small { color: var(--muted); font-size: 12px; line-height: 1.35; }',
|
|
189
311
|
' li p { color: var(--warn); font-size: 12px; margin: 0; }',
|
|
190
312
|
' .command-row, .actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; align-items: center; }',
|
|
313
|
+
' .result { border-color: #95c9bf; background: #f5fbf9; }',
|
|
314
|
+
' .result-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }',
|
|
315
|
+
' .result-grid div { display: grid; gap: 5px; border: 1px solid var(--line); border-radius: 8px; background: white; padding: 12px; min-height: 72px; }',
|
|
316
|
+
' .result-grid strong { overflow-wrap: anywhere; }',
|
|
317
|
+
' .policy { border-color: #e3b16f; background: #fffaf3; }',
|
|
318
|
+
' .risk-box { display: grid; gap: 4px; margin-top: 12px; border: 1px solid #e3b16f; border-radius: 8px; background: white; padding: 12px; }',
|
|
191
319
|
' .timeline { display: grid; grid-template-columns: repeat(8, minmax(96px, 1fr)); gap: 8px; overflow-x: auto; padding-bottom: 4px; }',
|
|
192
320
|
' .timeline li { min-width: 96px; border: 1px solid var(--line); border-radius: 8px; padding: 10px; background: var(--panel); display: grid; gap: 5px; }',
|
|
193
321
|
' .timeline span { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 999px; background: var(--accent); color: white; font-size: 12px; font-weight: 700; }',
|
|
194
322
|
' button { border: 0; border-radius: 6px; padding: 8px 12px; background: var(--accent); color: white; font-weight: 700; }',
|
|
195
|
-
' @media (max-width: 860px) { .toolbar { align-items: start; flex-direction: column; } .groups { grid-template-columns: 1fr; } .timeline { grid-template-columns: repeat(4, minmax(96px, 1fr)); } }',
|
|
323
|
+
' @media (max-width: 860px) { .toolbar { align-items: start; flex-direction: column; } .groups, .result-grid { grid-template-columns: 1fr; } .timeline { grid-template-columns: repeat(4, minmax(96px, 1fr)); } }',
|
|
196
324
|
].join('\n');
|
|
197
325
|
}
|
|
198
326
|
|
|
@@ -234,7 +362,13 @@ if (require.main === module) {
|
|
|
234
362
|
const args = parseArgs(process.argv.slice(2));
|
|
235
363
|
const root = args.root || path.resolve(__dirname, '..');
|
|
236
364
|
const surfaces = args.slug
|
|
237
|
-
? loadWorkflowSurfacesFromCli({
|
|
365
|
+
? loadWorkflowSurfacesFromCli({
|
|
366
|
+
root,
|
|
367
|
+
slug: args.slug,
|
|
368
|
+
cwd: args.cwd || process.cwd(),
|
|
369
|
+
selection: args.selection || null,
|
|
370
|
+
selectionFile: args.selectionFile || null,
|
|
371
|
+
})
|
|
238
372
|
: loadFixtureSurfaces({ root });
|
|
239
373
|
const options = {
|
|
240
374
|
root,
|
|
@@ -257,6 +391,8 @@ function parseArgs(argv) {
|
|
|
257
391
|
else if (arg.startsWith('--cwd=')) out.cwd = arg.slice('--cwd='.length);
|
|
258
392
|
else if (arg.startsWith('--root=')) out.root = arg.slice('--root='.length);
|
|
259
393
|
else if (arg.startsWith('--out=')) out.out = arg.slice('--out='.length);
|
|
394
|
+
else if (arg.startsWith('--selection=')) out.selection = arg.slice('--selection='.length);
|
|
395
|
+
else if (arg.startsWith('--selection-file=')) out.selectionFile = arg.slice('--selection-file='.length);
|
|
260
396
|
}
|
|
261
397
|
return out;
|
|
262
398
|
}
|
|
@@ -265,6 +401,7 @@ module.exports = {
|
|
|
265
401
|
renderWorkflowUiPrototype,
|
|
266
402
|
loadFixtureSurfaces,
|
|
267
403
|
loadWorkflowSurfacesFromCli,
|
|
404
|
+
actionToDevflowArgs,
|
|
268
405
|
outputPathFor,
|
|
269
406
|
writePrototype,
|
|
270
407
|
parseArgs,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const client = require('../src/client/workflow-adapter-client.js');
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (const arg of argv) {
|
|
12
|
+
if (!arg.startsWith('--')) continue;
|
|
13
|
+
const eq = arg.indexOf('=');
|
|
14
|
+
if (eq === -1) flags[arg.slice(2)] = true;
|
|
15
|
+
else flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
16
|
+
}
|
|
17
|
+
return flags;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseSelectedSteps(flags) {
|
|
21
|
+
const raw = flags.steps || flags.selected || '';
|
|
22
|
+
if (!raw || raw === true) return null;
|
|
23
|
+
return String(raw).split(',').map((item) => item.trim()).filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readSurface(flags) {
|
|
27
|
+
if (flags['surface-json']) return JSON.parse(String(flags['surface-json']));
|
|
28
|
+
const file = flags['surface-file'];
|
|
29
|
+
if (!file || file === true) throw new Error('usage: node scripts/workflow-adapter-client-example.js --surface-file=<adapter.json> [--host=<name>]');
|
|
30
|
+
return JSON.parse(fs.readFileSync(path.resolve(file), 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readCatalog(flags) {
|
|
34
|
+
if (flags['catalog-json']) return JSON.parse(String(flags['catalog-json']));
|
|
35
|
+
const file = flags['catalog-file'];
|
|
36
|
+
if (!file || file === true) return null;
|
|
37
|
+
return JSON.parse(fs.readFileSync(path.resolve(file), 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main(argv = process.argv.slice(2)) {
|
|
41
|
+
const flags = parseArgs(argv);
|
|
42
|
+
const catalog = readCatalog(flags);
|
|
43
|
+
if (catalog) {
|
|
44
|
+
process.stdout.write(client.renderCatalogExample(catalog, { host: flags.host }));
|
|
45
|
+
process.stdout.write('\n');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const surface = readSurface(flags);
|
|
50
|
+
if (flags.submit === true || flags['dry-run'] === true) {
|
|
51
|
+
const selected = parseSelectedSteps(flags) || client.collectDefaultSelectedSteps(surface);
|
|
52
|
+
const result = await client.submitSelection(surface, selected, { dryRun: flags['dry-run'] === true });
|
|
53
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write(client.renderHostExample(surface, { host: flags.host }));
|
|
57
|
+
process.stdout.write('\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (require.main === module) {
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
process.stderr.write(`${err.message}\n`);
|
|
63
|
+
process.exitCode = 2;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = client;
|