@deployxai/dxc 0.2.5 → 0.3.0

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/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __dxcCreateRequire } from "node:module"; const require = __dxcCreateRequire(import.meta.url);
3
3
  import {
4
4
  CONTENT_ARTIFACT_KINDS,
5
- CONTENT_STAGE_INPUTS,
5
+ CONTENT_STAGE_DEFINITIONS,
6
6
  CONTENT_WORKFLOW_ID,
7
7
  LocalStateError,
8
8
  __commonJS,
@@ -15,15 +15,14 @@ import {
15
15
  articleBundleSchema,
16
16
  contentMemoryAddResultSchema,
17
17
  contentMemoryArchiveResultSchema,
18
+ contentMemoryCorrectResultSchema,
18
19
  contentMemoryInputSchema,
19
20
  contentMemoryListResultSchema,
20
21
  contentMemoryStoreSchema,
21
22
  contentProfileInputSchema,
22
- contentProfileSaveResultSchema,
23
23
  contentProfileSchema,
24
+ contentProfileSessionResultSchema,
24
25
  contentProfileStatusResultSchema,
25
- contentProfileUpdateInputSchema,
26
- contentProfileUpdateResultSchema,
27
26
  contentProjectIndexSchema,
28
27
  contentProjectManifestSchema,
29
28
  createDeviceBindingSessionResponseSchema,
@@ -38,6 +37,7 @@ import {
38
37
  proveDeviceBindingSessionResponseSchema,
39
38
  publishingApprovalSchema,
40
39
  publishingArticleSchema,
40
+ publishingDraftAttentionDiagnosticsSchema,
41
41
  publishingDraftIntentResponseSchema,
42
42
  publishingPreviewLinkSchema,
43
43
  publishingPreviewSelectionStateSchema,
@@ -52,7 +52,7 @@ import {
52
52
  wechatRenderResultSchema,
53
53
  writePrivateBuffer,
54
54
  writePrivateJson
55
- } from "./chunks/chunk-JB5USZ4F.js";
55
+ } from "./chunks/chunk-6JNQZHAQ.js";
56
56
 
57
57
  // node_modules/.pnpm/pino-std-serializers@7.1.0/node_modules/pino-std-serializers/lib/err-helpers.js
58
58
  var require_err_helpers = __commonJS({
@@ -1306,7 +1306,7 @@ var require_sonic_boom = __commonJS({
1306
1306
  if (!(this instanceof SonicBoom)) {
1307
1307
  return new SonicBoom(opts);
1308
1308
  }
1309
- let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir5, retryEAGAIN, fsync, contentMode, mode } = opts || {};
1309
+ let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir6, retryEAGAIN, fsync, contentMode, mode } = opts || {};
1310
1310
  fd = fd || dest;
1311
1311
  this._len = 0;
1312
1312
  this.fd = -1;
@@ -1331,7 +1331,7 @@ var require_sonic_boom = __commonJS({
1331
1331
  this.append = append2 || false;
1332
1332
  this.mode = mode;
1333
1333
  this.retryEAGAIN = retryEAGAIN || (() => true);
1334
- this.mkdir = mkdir5 || false;
1334
+ this.mkdir = mkdir6 || false;
1335
1335
  let fsWriteSync;
1336
1336
  let fsWrite;
1337
1337
  if (contentMode === kContentModeBuffer) {
@@ -23266,7 +23266,7 @@ import os3 from "node:os";
23266
23266
  // apps/cli/package.json
23267
23267
  var package_default = {
23268
23268
  name: "@dxc/cli",
23269
- version: "0.2.5",
23269
+ version: "0.3.0",
23270
23270
  private: true,
23271
23271
  type: "module",
23272
23272
  bin: {
@@ -26711,13 +26711,78 @@ async function runDoctor(runtime) {
26711
26711
 
26712
26712
  // apps/cli/src/profile.ts
26713
26713
  import { randomUUID } from "node:crypto";
26714
- import { lstat, readFile, rename, stat } from "node:fs/promises";
26714
+ import { unlink } from "node:fs/promises";
26715
26715
  import path2 from "node:path";
26716
26716
  var PROFILE_DISPLAY_PATH = "~/.dxc/content-profile.json";
26717
26717
  var MEMORY_DISPLAY_PATH = "~/.dxc/content-memory.json";
26718
26718
  var USER_MEMORY_DISPLAY_PATH = "~/.dxc/USER.md";
26719
26719
  var PROFILE_MAXIMUM_BYTES = 64 * 1024;
26720
26720
  var MEMORY_MAXIMUM_BYTES = 128 * 1024;
26721
+ var PROFILE_SESSION_MAXIMUM_BYTES = 32 * 1024;
26722
+ var PROFILE_QUESTIONS = [
26723
+ {
26724
+ choices: [
26725
+ { choiceId: "profile_creator.expert", label: "\u4E2A\u4EBA\u4E13\u5BB6\u6216\u987E\u95EE" },
26726
+ { choiceId: "profile_creator.creator", label: "\u804C\u4E1A\u5185\u5BB9\u521B\u4F5C\u8005" },
26727
+ { choiceId: "profile_creator.brand", label: "\u54C1\u724C\u6216\u4F01\u4E1A\u5185\u5BB9\u56E2\u961F" },
26728
+ { choiceId: "profile_creator.educator", label: "\u6559\u80B2\u6216\u57F9\u8BAD\u8005" }
26729
+ ],
26730
+ inputType: "single-choice",
26731
+ prompt: "\u8BF7\u9009\u62E9\u6700\u7B26\u5408\u4F60\u7684\u535A\u4E3B\u8EAB\u4EFD\u3002",
26732
+ questionId: "creator-role"
26733
+ },
26734
+ {
26735
+ choices: [],
26736
+ inputType: "text",
26737
+ prompt: "\u8BF7\u8F93\u5165\u5E0C\u671B\u8BFB\u8005\u770B\u5230\u7684\u540D\u79F0\u6216\u7B14\u540D\u3002",
26738
+ questionId: "display-name"
26739
+ },
26740
+ {
26741
+ choices: [
26742
+ { choiceId: "profile_domain.business", label: "\u5546\u4E1A\u4E0E\u521B\u4E1A" },
26743
+ { choiceId: "profile_domain.technology", label: "\u6280\u672F\u4E0E AI" },
26744
+ { choiceId: "profile_domain.career", label: "\u804C\u573A\u4E0E\u4E2A\u4EBA\u6210\u957F" },
26745
+ { choiceId: "profile_domain.lifestyle", label: "\u751F\u6D3B\u65B9\u5F0F" },
26746
+ { choiceId: "profile_domain.education", label: "\u6559\u80B2\u4E0E\u77E5\u8BC6\u4F20\u64AD" }
26747
+ ],
26748
+ inputType: "multi-choice",
26749
+ prompt: "\u8BF7\u9009\u62E9 1\u20135 \u4E2A\u4E3B\u8981\u5185\u5BB9\u9886\u57DF\u3002",
26750
+ questionId: "primary-domains"
26751
+ },
26752
+ {
26753
+ choices: [
26754
+ { choiceId: "profile_audience.beginner", label: "\u521A\u5F00\u59CB\u4E86\u89E3" },
26755
+ { choiceId: "profile_audience.problem-aware", label: "\u5DF2\u610F\u8BC6\u5230\u95EE\u9898" },
26756
+ { choiceId: "profile_audience.evaluating", label: "\u6B63\u5728\u6BD4\u8F83\u65B9\u6848" },
26757
+ { choiceId: "profile_audience.customer", label: "\u5DF2\u6709\u7ECF\u9A8C\u6216\u5BA2\u6237" }
26758
+ ],
26759
+ inputType: "single-choice",
26760
+ prompt: "\u8BF7\u9009\u62E9\u6838\u5FC3\u8BFB\u8005\u5F53\u524D\u6240\u5904\u9636\u6BB5\u3002",
26761
+ questionId: "target-audience"
26762
+ },
26763
+ {
26764
+ choices: [
26765
+ { choiceId: "profile_value.action", label: "\u7ED9\u4E00\u4E2A\u53EF\u6267\u884C\u7684\u65B9\u6CD5" },
26766
+ { choiceId: "profile_value.explain", label: "\u89E3\u91CA\u590D\u6742\u95EE\u9898" },
26767
+ { choiceId: "profile_value.case", label: "\u63D0\u4F9B\u771F\u5B9E\u6848\u4F8B" },
26768
+ { choiceId: "profile_value.decision", label: "\u5E2E\u52A9\u505A\u51B3\u7B56" },
26769
+ { choiceId: "profile_value.trust", label: "\u5EFA\u7ACB\u4E13\u4E1A\u4FE1\u4EFB" }
26770
+ ],
26771
+ inputType: "single-choice",
26772
+ prompt: "\u8BF7\u9009\u62E9\u5185\u5BB9\u6700\u6838\u5FC3\u7684\u8BFB\u8005\u627F\u8BFA\u3002",
26773
+ questionId: "content-value-promise"
26774
+ },
26775
+ {
26776
+ choices: [
26777
+ { choiceId: "profile_confirmation.guided", label: "\u5173\u952E\u9636\u6BB5\u786E\u8BA4" },
26778
+ { choiceId: "profile_confirmation.review-every-stage", label: "\u6BCF\u4E2A\u5185\u5BB9\u9636\u6BB5\u90FD\u786E\u8BA4" },
26779
+ { choiceId: "profile_confirmation.auto-until-delivery", label: "\u53EA\u5728\u6700\u7EC8\u9884\u89C8\u786E\u8BA4" }
26780
+ ],
26781
+ inputType: "single-choice",
26782
+ prompt: "\u8BF7\u9009\u62E9\u5185\u5BB9\u5DE5\u4F5C\u6D41\u7684\u786E\u8BA4\u65B9\u5F0F\u3002",
26783
+ questionId: "confirmation-policy"
26784
+ }
26785
+ ];
26721
26786
  var ProfileCliError = class extends Error {
26722
26787
  constructor(code, message, hint, fields) {
26723
26788
  super(message);
@@ -26747,6 +26812,23 @@ function userMemoryPath(homeDirectory) {
26747
26812
  function memoryPath(homeDirectory) {
26748
26813
  return path2.join(homeDirectory, ".dxc", "content-memory.json");
26749
26814
  }
26815
+ function profileSessionPath(homeDirectory, profileSessionId) {
26816
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
26817
+ profileSessionId
26818
+ )) {
26819
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD ID \u65E0\u6548\u3002");
26820
+ }
26821
+ return path2.join(homeDirectory, ".dxc", "profile-sessions", `${profileSessionId}.json`);
26822
+ }
26823
+ function profileSession(value, expectedId) {
26824
+ if (typeof value !== "object" || value === null || Array.isArray(value) || !("version" in value) || value.version !== 1 || !("profileSessionId" in value) || value.profileSessionId !== expectedId || !("index" in value) || !Number.isInteger(value.index) || Number(value.index) < 0 || Number(value.index) > PROFILE_QUESTIONS.length || !("createdAt" in value) || typeof value.createdAt !== "string" || !("answers" in value) || typeof value.answers !== "object" || value.answers === null || Array.isArray(value.answers)) {
26825
+ return void 0;
26826
+ }
26827
+ return value;
26828
+ }
26829
+ function choiceLabel(questionIndex, choiceId) {
26830
+ return PROFILE_QUESTIONS[questionIndex]?.choices.find((choice) => choice.choiceId === choiceId)?.label;
26831
+ }
26750
26832
  function singleLine(value) {
26751
26833
  return value.replace(/[\r\n]+/gu, " ").replace(/\s+/gu, " ").trim();
26752
26834
  }
@@ -26906,149 +26988,133 @@ async function refreshUserMemory(homeDirectory, user, now) {
26906
26988
  Buffer.from(renderUserMemory(user, memory.memories))
26907
26989
  );
26908
26990
  }
26909
- async function readProfileInput(currentDirectory, homeDirectory, inputPath) {
26910
- try {
26911
- const target = resolveUserPath({ currentDirectory, homeDirectory, input: inputPath });
26912
- const metadata = await stat(target);
26913
- if (!metadata.isFile() || metadata.size > PROFILE_MAXIMUM_BYTES) {
26914
- throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "Profile input is invalid");
26915
- }
26916
- return parseJson(
26917
- await readFile(target, "utf8"),
26918
- "DXC_PROFILE_INPUT_INVALID",
26919
- "Profile input is invalid"
26920
- );
26921
- } catch (error) {
26922
- if (error instanceof ProfileCliError) {
26923
- throw error;
26924
- }
26925
- throw new ProfileCliError(
26926
- "DXC_PROFILE_INPUT_INVALID",
26927
- "\u753B\u50CF\u8F93\u5165\u6587\u4EF6\u65E0\u6CD5\u8BFB\u53D6\u6216\u4E0D\u662F\u6709\u6548 JSON\u3002",
26928
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u91CD\u65B0\u751F\u6210\u753B\u50CF\u8F93\u5165\uFF1B\u65E0\u9700\u624B\u52A8\u4FEE\u6539\u8DEF\u5F84\u6216\u5E94\u7528\u6570\u636E\u6587\u4EF6\u3002"
26929
- );
26930
- }
26931
- }
26932
- function issueFields(issues) {
26933
- return [
26934
- ...new Set(
26935
- issues.map((issue) => issue.path.map(String).join(".")).filter((field) => field.length > 0)
26936
- )
26937
- ];
26938
- }
26939
- async function readProfileForSave(target, timestamp) {
26940
- try {
26941
- return await readProfile(target);
26942
- } catch (error) {
26943
- if (!(error instanceof ProfileCliError) || error.code !== "DXC_PROFILE_INVALID") {
26944
- throw error;
26945
- }
26946
- try {
26947
- const metadata = await lstat(target);
26948
- if (!metadata.isFile() || metadata.isSymbolicLink()) {
26949
- throw error;
26950
- }
26951
- const suffix = timestamp.replaceAll(":", "-");
26952
- await rename(target, `${target}.invalid-${suffix}.bak`);
26953
- return void 0;
26954
- } catch (recoveryError) {
26955
- if (recoveryError === error) {
26956
- throw error;
26957
- }
26958
- throw new ProfileCliError(
26959
- "DXC_PROFILE_INVALID",
26960
- "\u53D1\u73B0\u65E0\u6548\u7684\u65E7\u753B\u50CF\uFF0C\u4F46\u65E0\u6CD5\u5B89\u5168\u5907\u4EFD\u5E76\u6062\u590D\u3002",
26961
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u91CD\u8BD5\uFF1B\u4E0D\u8981\u624B\u52A8\u5220\u9664 ~/.dxc \u4E2D\u7684\u6587\u4EF6\u3002"
26962
- );
26963
- }
26964
- }
26965
- }
26966
26991
  var ProfileCli = class {
26967
- #currentDirectory;
26968
26992
  #homeDirectory;
26969
26993
  #now;
26970
26994
  #randomUUID;
26971
26995
  constructor(dependencies) {
26972
- this.#currentDirectory = dependencies.currentDirectory;
26973
26996
  this.#homeDirectory = dependencies.homeDirectory;
26974
26997
  this.#now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
26975
26998
  this.#randomUUID = dependencies.randomUUID ?? randomUUID;
26976
26999
  }
26977
- async save(inputPath) {
26978
- const input = contentProfileInputSchema.safeParse(
26979
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
26980
- );
26981
- if (!input.success) {
26982
- const fields = issueFields(input.error.issues);
26983
- throw new ProfileCliError(
26984
- "DXC_PROFILE_INPUT_INVALID",
26985
- fields.length === 0 ? "\u753B\u50CF\u8F93\u5165\u4E0D\u7B26\u5408\u5F53\u524D\u7248\u672C\u8981\u6C42\u3002" : `\u753B\u50CF\u8F93\u5165\u7F3A\u5C11\u6216\u5305\u542B\u65E0\u6548\u5B57\u6BB5\uFF1A${fields.join("\u3001")}\u3002`,
26986
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u8865\u9F50\u8FD9\u4E9B\u5B57\u6BB5\u540E\u81EA\u52A8\u91CD\u8BD5\uFF1B\u65E0\u9700\u624B\u5199 JSON\u3002",
26987
- fields
26988
- );
26989
- }
26990
- const target = profilePath(this.#homeDirectory);
26991
- const timestamp = this.#now().toISOString();
26992
- const existing = await readProfileForSave(target, timestamp);
26993
- const profile = contentProfileSchema.parse({
26994
- ...input.data,
26995
- createdAt: existing?.createdAt ?? timestamp,
26996
- profileId: existing?.profileId ?? this.#randomUUID(),
26997
- schemaVersion: "1",
26998
- updatedAt: timestamp
26999
- });
27000
- try {
27001
- await writePrivateJson(target, profile);
27002
- await refreshUserMemory(this.#homeDirectory, profile.user, this.#now());
27003
- } catch {
27004
- throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "Unable to save local content profile");
27005
- }
27006
- return contentProfileSaveResultSchema.parse({
27007
- command: "profile.save",
27008
- data: {
27009
- profile,
27010
- profilePath: PROFILE_DISPLAY_PATH,
27011
- userMemoryPath: USER_MEMORY_DISPLAY_PATH
27012
- },
27000
+ async begin() {
27001
+ const profileSessionId = this.#randomUUID();
27002
+ const session = {
27003
+ answers: {},
27004
+ createdAt: this.#now().toISOString(),
27005
+ index: 0,
27006
+ profileSessionId,
27007
+ version: 1
27008
+ };
27009
+ await writePrivateJson(profileSessionPath(this.#homeDirectory, profileSessionId), session);
27010
+ return contentProfileSessionResultSchema.parse({
27011
+ command: "profile.begin",
27012
+ data: { profileSessionId, question: PROFILE_QUESTIONS[0], status: "question" },
27013
27013
  ok: true
27014
27014
  });
27015
27015
  }
27016
- async update(inputPath) {
27017
- const input = contentProfileUpdateInputSchema.safeParse(
27018
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
27019
- );
27020
- if (!input.success) {
27021
- throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "Profile update input is invalid");
27016
+ async answer(profileSessionId, input) {
27017
+ const target = profileSessionPath(this.#homeDirectory, profileSessionId);
27018
+ let session;
27019
+ try {
27020
+ const parsed = profileSession(
27021
+ JSON.parse(await readPrivateFile(target, PROFILE_SESSION_MAXIMUM_BYTES)),
27022
+ profileSessionId
27023
+ );
27024
+ if (parsed === void 0) {
27025
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u5DF2\u635F\u574F\u3002");
27026
+ }
27027
+ session = parsed;
27028
+ } catch (error) {
27029
+ if (error instanceof ProfileCliError) {
27030
+ throw error;
27031
+ }
27032
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u5B8C\u6210\u3002");
27033
+ }
27034
+ const question = PROFILE_QUESTIONS[session.index];
27035
+ if (question === void 0) {
27036
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u5DF2\u7ECF\u5B8C\u6210\u3002");
27022
27037
  }
27023
- const target = profilePath(this.#homeDirectory);
27024
- const existing = await readProfile(target);
27025
- if (existing === void 0) {
27026
- throw new ProfileCliError("DXC_PROFILE_INVALID", "Content profile is not initialized");
27038
+ const choices = [...new Set(input.choices)];
27039
+ if (question.inputType === "text") {
27040
+ if (input.text === void 0 || input.text.trim().length === 0 || input.text.length > 120) {
27041
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u5F53\u524D\u753B\u50CF\u95EE\u9898\u9700\u8981\u6709\u6548\u6587\u672C\u7B54\u6848\u3002");
27042
+ }
27043
+ } else {
27044
+ const maximum = question.inputType === "multi-choice" ? 5 : 1;
27045
+ if (choices.length < 1 || choices.length > maximum || choices.some((choice) => choiceLabel(session.index, choice) === void 0)) {
27046
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u9009\u62E9\u4E0D\u5C5E\u4E8E\u5F53\u524D\u95EE\u9898\u3002");
27047
+ }
27048
+ }
27049
+ const answer = {
27050
+ choices,
27051
+ ...input.note === void 0 ? {} : { note: input.note.trim().slice(0, 500) },
27052
+ ...input.text === void 0 ? {} : { text: input.text.trim() }
27053
+ };
27054
+ const answers = { ...session.answers, [question.questionId]: answer };
27055
+ const nextIndex = session.index + 1;
27056
+ if (nextIndex < PROFILE_QUESTIONS.length) {
27057
+ await writePrivateJson(target, { ...session, answers, index: nextIndex });
27058
+ return contentProfileSessionResultSchema.parse({
27059
+ command: "profile.answer",
27060
+ data: {
27061
+ profileSessionId,
27062
+ question: PROFILE_QUESTIONS[nextIndex],
27063
+ status: "question"
27064
+ },
27065
+ ok: true
27066
+ });
27027
27067
  }
27028
- const answerByQuestionId = new Map(
27029
- existing.answers.map((answer) => [answer.questionId, answer])
27068
+ const creatorRole = choiceLabel(0, answers["creator-role"]?.choices[0] ?? "");
27069
+ const displayName = answers["display-name"]?.text;
27070
+ const primaryDomains = (answers["primary-domains"]?.choices ?? []).map((choice) => choiceLabel(2, choice)).filter((label) => label !== void 0);
27071
+ const targetAudience = choiceLabel(3, answers["target-audience"]?.choices[0] ?? "");
27072
+ const contentValuePromise = choiceLabel(4, answers["content-value-promise"]?.choices[0] ?? "");
27073
+ const confirmationChoice = answers["confirmation-policy"]?.choices[0];
27074
+ const confirmationPolicy = confirmationChoice?.replace("profile_confirmation.", "");
27075
+ const existing = await readProfile(profilePath(this.#homeDirectory));
27076
+ const existingUser = existing?.user;
27077
+ const userWithoutRetiredPolicy = existingUser === void 0 ? {} : Object.fromEntries(
27078
+ Object.entries(existingUser).filter(([key]) => key !== "visualGenerationAuthorization")
27030
27079
  );
27031
- for (const answer of input.data.answers ?? []) {
27032
- answerByQuestionId.set(answer.questionId, answer);
27080
+ const parsedInput = contentProfileInputSchema.safeParse({
27081
+ answers: existing?.answers ?? [],
27082
+ questionnaireVersion: "dxc-onboarding@1",
27083
+ user: {
27084
+ ...userWithoutRetiredPolicy,
27085
+ confirmationPolicy,
27086
+ contentValuePromise,
27087
+ creatorRole,
27088
+ displayName,
27089
+ primaryDomains,
27090
+ primaryPlatform: "wechat-official-account",
27091
+ targetAudience
27092
+ }
27093
+ });
27094
+ if (!parsedInput.success) {
27095
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u7B54\u6848\u65E0\u6CD5\u5F62\u6210\u6709\u6548\u753B\u50CF\u3002");
27033
27096
  }
27034
27097
  const timestamp = this.#now().toISOString();
27035
27098
  const profile = contentProfileSchema.parse({
27036
- ...existing,
27037
- answers: [...answerByQuestionId.values()],
27038
- ...input.data.user === void 0 ? {} : { user: { ...existing.user, ...input.data.user } },
27099
+ ...parsedInput.data,
27100
+ createdAt: existing?.createdAt ?? timestamp,
27101
+ profileId: existing?.profileId ?? this.#randomUUID(),
27102
+ schemaVersion: "1",
27039
27103
  updatedAt: timestamp
27040
27104
  });
27041
27105
  try {
27042
- await writePrivateJson(target, profile);
27106
+ await writePrivateJson(profilePath(this.#homeDirectory), profile);
27043
27107
  await refreshUserMemory(this.#homeDirectory, profile.user, this.#now());
27108
+ await unlink(target);
27044
27109
  } catch {
27045
- throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "Unable to update local content profile");
27110
+ throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "\u65E0\u6CD5\u539F\u5B50\u4FDD\u5B58\u753B\u50CF\u95EE\u7B54\u7ED3\u679C\u3002");
27046
27111
  }
27047
- return contentProfileUpdateResultSchema.parse({
27048
- command: "profile.update",
27112
+ return contentProfileSessionResultSchema.parse({
27113
+ command: "profile.answer",
27049
27114
  data: {
27050
27115
  profile,
27051
27116
  profilePath: PROFILE_DISPLAY_PATH,
27117
+ status: "completed",
27052
27118
  userMemoryPath: USER_MEMORY_DISPLAY_PATH
27053
27119
  },
27054
27120
  ok: true
@@ -27073,20 +27139,16 @@ var ProfileCli = class {
27073
27139
  }
27074
27140
  };
27075
27141
  var ContentMemoryCli = class {
27076
- #currentDirectory;
27077
27142
  #homeDirectory;
27078
27143
  #now;
27079
27144
  #randomUUID;
27080
27145
  constructor(dependencies) {
27081
- this.#currentDirectory = dependencies.currentDirectory;
27082
27146
  this.#homeDirectory = dependencies.homeDirectory;
27083
27147
  this.#now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
27084
27148
  this.#randomUUID = dependencies.randomUUID ?? randomUUID;
27085
27149
  }
27086
- async add(inputPath) {
27087
- const input = contentMemoryInputSchema.safeParse(
27088
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
27089
- );
27150
+ async add(inputValue) {
27151
+ const input = contentMemoryInputSchema.safeParse(inputValue);
27090
27152
  if (!input.success) {
27091
27153
  throw new ContentMemoryCliError(
27092
27154
  "DXC_MEMORY_INPUT_INVALID",
@@ -27181,11 +27243,73 @@ var ContentMemoryCli = class {
27181
27243
  ok: true
27182
27244
  });
27183
27245
  }
27246
+ async correct(memoryId, inputValue) {
27247
+ const input = contentMemoryInputSchema.safeParse(inputValue);
27248
+ if (!input.success) {
27249
+ throw new ContentMemoryCliError(
27250
+ "DXC_MEMORY_INPUT_INVALID",
27251
+ "Content memory correction is invalid"
27252
+ );
27253
+ }
27254
+ const target = memoryPath(this.#homeDirectory);
27255
+ const store = await readMemoryStore(target);
27256
+ const index = store.memories.findIndex(
27257
+ (memory2) => memory2.id === memoryId && memory2.status === "active"
27258
+ );
27259
+ if (index < 0) {
27260
+ throw new ContentMemoryCliError(
27261
+ "DXC_MEMORY_NOT_FOUND",
27262
+ "Active content memory was not found"
27263
+ );
27264
+ }
27265
+ if (store.memories.length >= 100) {
27266
+ throw new ContentMemoryCliError("DXC_MEMORY_IO_FAILED", "Content memory capacity is reached");
27267
+ }
27268
+ const timestamp = this.#now().toISOString();
27269
+ const archivedMemory = {
27270
+ ...store.memories[index],
27271
+ status: "archived",
27272
+ updatedAt: timestamp
27273
+ };
27274
+ const memory = {
27275
+ ...input.data,
27276
+ createdAt: timestamp,
27277
+ id: this.#randomUUID(),
27278
+ source: "user-stated",
27279
+ status: "active",
27280
+ updatedAt: timestamp
27281
+ };
27282
+ const memories = [
27283
+ ...store.memories.map((entry, entryIndex) => entryIndex === index ? archivedMemory : entry),
27284
+ memory
27285
+ ];
27286
+ const next = contentMemoryStoreSchema.parse({ ...store, memories });
27287
+ try {
27288
+ await writePrivateJson(target, next);
27289
+ const profile = await readProfile(profilePath(this.#homeDirectory));
27290
+ await refreshUserMemory(this.#homeDirectory, profile?.user, this.#now());
27291
+ } catch (error) {
27292
+ if (error instanceof ContentMemoryCliError || error instanceof ProfileCliError) {
27293
+ throw error;
27294
+ }
27295
+ throw new ContentMemoryCliError("DXC_MEMORY_IO_FAILED", "Unable to correct content memory");
27296
+ }
27297
+ return contentMemoryCorrectResultSchema.parse({
27298
+ command: "memory.correct",
27299
+ data: {
27300
+ archivedMemory,
27301
+ memories: next.memories.filter((entry) => entry.status === "active"),
27302
+ memory,
27303
+ memoryPath: MEMORY_DISPLAY_PATH
27304
+ },
27305
+ ok: true
27306
+ });
27307
+ }
27184
27308
  };
27185
27309
 
27186
27310
  // apps/cli/src/project.ts
27187
27311
  import { randomUUID as randomUUID2 } from "node:crypto";
27188
- import { lstat as lstat2, mkdir } from "node:fs/promises";
27312
+ import { lstat, mkdir } from "node:fs/promises";
27189
27313
  import path3 from "node:path";
27190
27314
  var MANIFEST_NAME = "dxc.project.json";
27191
27315
  var MANIFEST_MAXIMUM_BYTES = 32 * 1024;
@@ -27291,7 +27415,7 @@ var ProjectCli = class {
27291
27415
  });
27292
27416
  const manifestTarget = path3.join(directory, MANIFEST_NAME);
27293
27417
  try {
27294
- await lstat2(manifestTarget);
27418
+ await lstat(manifestTarget);
27295
27419
  throw new ProjectCliError("DXC_PROJECT_EXISTS", "\u8BE5\u76EE\u5F55\u5DF2\u7ECF\u662F DxC \u5185\u5BB9\u9879\u76EE\u3002");
27296
27420
  } catch (error) {
27297
27421
  if (error instanceof ProjectCliError) {
@@ -27427,7 +27551,17 @@ var ProjectCli = class {
27427
27551
  };
27428
27552
 
27429
27553
  // apps/cli/src/skills.ts
27430
- import { cp, lstat as lstat3, mkdir as mkdir2, mkdtemp, readdir, rename as rename2, rm } from "node:fs/promises";
27554
+ import {
27555
+ cp,
27556
+ lstat as lstat2,
27557
+ mkdir as mkdir2,
27558
+ mkdtemp,
27559
+ readFile,
27560
+ readdir,
27561
+ rename,
27562
+ rm,
27563
+ writeFile
27564
+ } from "node:fs/promises";
27431
27565
  import os from "node:os";
27432
27566
  import path4 from "node:path";
27433
27567
  import { fileURLToPath } from "node:url";
@@ -27457,7 +27591,7 @@ var SkillInstallerError = class extends Error {
27457
27591
  };
27458
27592
  async function isDirectory(directory) {
27459
27593
  try {
27460
- return (await lstat3(directory)).isDirectory();
27594
+ return (await lstat2(directory)).isDirectory();
27461
27595
  } catch {
27462
27596
  return false;
27463
27597
  }
@@ -27481,7 +27615,7 @@ async function findBundledSkills() {
27481
27615
  async function assertRegularSkillTree(directory) {
27482
27616
  for (const entry of await readdir(directory, { withFileTypes: true })) {
27483
27617
  const entryPath = path4.join(directory, entry.name);
27484
- const entryStat = await lstat3(entryPath);
27618
+ const entryStat = await lstat2(entryPath);
27485
27619
  if (entryStat.isSymbolicLink()) {
27486
27620
  throw new SkillInstallerError(
27487
27621
  "DXC_SKILL_SOURCE_UNSAFE",
@@ -27500,6 +27634,26 @@ async function assertRegularSkillTree(directory) {
27500
27634
  }
27501
27635
  }
27502
27636
  }
27637
+ async function renderCliInvocation(sourceRoot, stagedSkill) {
27638
+ const skillFile = path4.join(stagedSkill, "SKILL.md");
27639
+ const skill = await readFile(skillFile, "utf8");
27640
+ if (!skill.includes("{{DXC_CLI_INVOCATION}}")) {
27641
+ return;
27642
+ }
27643
+ const invocationTemplate = await readFile(
27644
+ path4.join(sourceRoot, ".dxc-cli-invocation.template.md"),
27645
+ "utf8"
27646
+ );
27647
+ const invocation = invocationTemplate.replaceAll("{{VERSION}}", package_default.version).trim();
27648
+ const rendered = skill.replaceAll("{{DXC_CLI_INVOCATION}}", invocation);
27649
+ if (rendered.includes("{{DXC_CLI_INVOCATION}}") || rendered.includes("{{VERSION}}")) {
27650
+ throw new SkillInstallerError(
27651
+ "DXC_SKILL_TEMPLATE_INVALID",
27652
+ "Bundled Skill invocation template contains an unresolved placeholder"
27653
+ );
27654
+ }
27655
+ await writeFile(skillFile, rendered, { mode: 384 });
27656
+ }
27503
27657
  function defaultTargetDirectory(homeDirectory, target) {
27504
27658
  return path4.join(homeDirectory, target === "codex" ? ".codex" : ".workbuddy", "skills");
27505
27659
  }
@@ -27542,10 +27696,11 @@ var SkillInstaller = class {
27542
27696
  errorOnExist: true,
27543
27697
  recursive: true
27544
27698
  });
27699
+ await renderCliInvocation(sourceRoot, stagedSkill);
27545
27700
  if (destinationExists) {
27546
- await rename2(destination, backup);
27701
+ await rename(destination, backup);
27547
27702
  }
27548
- await rename2(stagedSkill, destination);
27703
+ await rename(stagedSkill, destination);
27549
27704
  installed.push(skill);
27550
27705
  if (destinationExists) {
27551
27706
  try {
@@ -27556,7 +27711,7 @@ var SkillInstaller = class {
27556
27711
  }
27557
27712
  } catch (error) {
27558
27713
  if (!await isDirectory(destination) && await isDirectory(backup)) {
27559
- await rename2(backup, destination);
27714
+ await rename(backup, destination);
27560
27715
  }
27561
27716
  throw error;
27562
27717
  } finally {
@@ -27587,7 +27742,7 @@ import {
27587
27742
  randomUUID as randomUUID4
27588
27743
  } from "node:crypto";
27589
27744
  import { constants as fileConstants3 } from "node:fs";
27590
- import { chmod, lstat as lstat6, mkdir as mkdir4, open as open3, rename as rename4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
27745
+ import { chmod, lstat as lstat5, mkdir as mkdir5, open as open3, rename as rename3, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
27591
27746
  import os2 from "node:os";
27592
27747
  import path7 from "node:path";
27593
27748
 
@@ -27735,10 +27890,10 @@ var LOG_REDACTION_PATHS = Object.freeze([
27735
27890
  ]);
27736
27891
 
27737
27892
  // apps/cli/src/publishing-assets.ts
27738
- import { createHash as createHash4 } from "node:crypto";
27739
- import { constants as fileConstants } from "node:fs";
27740
- import { lstat as lstat4, open, readdir as readdir2, realpath } from "node:fs/promises";
27741
- import path5 from "node:path";
27893
+ import { createHash as createHash5 } from "node:crypto";
27894
+ import { constants as fileConstants2 } from "node:fs";
27895
+ import { lstat as lstat4, mkdir as mkdir4, open as open2, readdir as readdir2, readFile as readFile3, realpath as realpath2 } from "node:fs/promises";
27896
+ import path6 from "node:path";
27742
27897
 
27743
27898
  // packages/renderer-wechat/src/article.ts
27744
27899
  import { createHash as createHash2 } from "node:crypto";
@@ -29860,7 +30015,24 @@ function rewriteWechatAssetSources(html, replacements) {
29860
30015
  }
29861
30016
 
29862
30017
  // apps/cli/src/publishing-assets.ts
30018
+ var import_yaml3 = __toESM(require_dist(), 1);
30019
+ import sharp2 from "sharp";
30020
+
30021
+ // apps/cli/src/publishing-image-optimization.ts
29863
30022
  var import_yaml2 = __toESM(require_dist(), 1);
30023
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
30024
+ import { constants as fileConstants } from "node:fs";
30025
+ import {
30026
+ lstat as lstat3,
30027
+ mkdir as mkdir3,
30028
+ open,
30029
+ readFile as readFile2,
30030
+ realpath,
30031
+ rename as rename2,
30032
+ unlink as unlink2,
30033
+ writeFile as writeFile2
30034
+ } from "node:fs/promises";
30035
+ import path5 from "node:path";
29864
30036
  import sharp from "sharp";
29865
30037
 
29866
30038
  // apps/cli/src/wechat-error.ts
@@ -29879,46 +30051,402 @@ var WechatCliError = class extends Error {
29879
30051
  fields;
29880
30052
  };
29881
30053
 
30054
+ // apps/cli/src/publishing-image-optimization.ts
30055
+ var MAXIMUM_INPUT_BYTES = 32 * 1024 * 1024;
30056
+ var INLINE_IMAGE_LIMIT = 1024 * 1024 - 1;
30057
+ var INLINE_IMAGE_TARGET = 950 * 1024;
30058
+ var COVER_IMAGE_LIMIT = 5 * 1024 * 1024;
30059
+ var COVER_IMAGE_TARGET = 4500 * 1024;
30060
+ var VISUAL_PLAN_MAXIMUM_BYTES = 128 * 1024;
30061
+ var VISUAL_PLAN_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
30062
+ function sha256(value) {
30063
+ return createHash4("sha256").update(value).digest("hex");
30064
+ }
30065
+ function safeDerivativeBase(value) {
30066
+ return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80) || "image";
30067
+ }
30068
+ function recordValue(value) {
30069
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
30070
+ }
30071
+ function portablePath(value, label) {
30072
+ if (typeof value !== "string" || value.trim().length === 0 || path5.isAbsolute(value) || value.includes("\\") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
30073
+ throw new WechatCliError(
30074
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30075
+ `${label} \u5FC5\u987B\u662F\u9879\u76EE\u5185\u4F7F\u7528 / \u5206\u9694\u7684\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002`
30076
+ );
30077
+ }
30078
+ return value;
30079
+ }
30080
+ function isWithin(target, directory) {
30081
+ const relative = path5.relative(directory, target);
30082
+ return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== "..";
30083
+ }
30084
+ async function regularImage(target) {
30085
+ let handle;
30086
+ try {
30087
+ handle = await open(target, fileConstants.O_RDONLY | fileConstants.O_NOFOLLOW);
30088
+ const metadata = await handle.stat();
30089
+ if (!metadata.isFile() || metadata.size <= 0 || metadata.size > MAXIMUM_INPUT_BYTES) {
30090
+ throw new Error("invalid image");
30091
+ }
30092
+ return await handle.readFile();
30093
+ } catch {
30094
+ throw new WechatCliError(
30095
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30096
+ `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u56FE\u7247 ${path5.basename(target)}\uFF1B\u6587\u4EF6\u5FC5\u987B\u662F\u5C0F\u4E8E ${MAXIMUM_INPUT_BYTES} bytes \u7684\u666E\u901A\u6587\u4EF6\u3002`
30097
+ );
30098
+ } finally {
30099
+ await handle?.close();
30100
+ }
30101
+ }
30102
+ async function requirePreservedAlpha(bytes) {
30103
+ const metadata = await sharp(bytes, {
30104
+ failOn: "error",
30105
+ limitInputPixels: 8e7
30106
+ }).metadata();
30107
+ if (metadata.format !== "png" || metadata.hasAlpha !== true) {
30108
+ throw new WechatCliError(
30109
+ "DXC_PUBLISHING_IMAGE_ALPHA_LOST",
30110
+ "\u900F\u660E\u56FE\u7247\u4F18\u5316\u540E\u4E22\u5931\u900F\u660E\u901A\u9053\uFF1B\u5DF2\u963B\u65AD\u4E14\u4E0D\u4F1A\u8986\u76D6\u539F\u56FE\u3002"
30111
+ );
30112
+ }
30113
+ }
30114
+ async function decodedImageMetadata(source, sourcePath) {
30115
+ let metadata;
30116
+ try {
30117
+ metadata = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30118
+ } catch {
30119
+ throw new WechatCliError(
30120
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30121
+ "\u56FE\u7247\u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002"
30122
+ );
30123
+ }
30124
+ const extension = path5.extname(sourcePath).toLowerCase();
30125
+ const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
30126
+ if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
30127
+ throw new WechatCliError(
30128
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30129
+ "\u56FE\u7247\u6269\u5C55\u540D\u3001\u771F\u5B9E\u683C\u5F0F\u6216\u5C3A\u5BF8\u65E0\u6548\uFF1B\u4EC5\u652F\u6301\u6269\u5C55\u540D\u4E0E\u5185\u5BB9\u4E00\u81F4\u7684 PNG/JPEG\u3002"
30130
+ );
30131
+ }
30132
+ return {
30133
+ format: expectedFormat,
30134
+ hasAlpha: metadata.hasAlpha === true,
30135
+ height: metadata.height,
30136
+ width: metadata.width
30137
+ };
30138
+ }
30139
+ async function writeExclusiveOrVerify(target, bytes) {
30140
+ await mkdir3(path5.dirname(target), { recursive: true, mode: 448 });
30141
+ try {
30142
+ await writeFile2(target, bytes, { flag: "wx", mode: 384 });
30143
+ return true;
30144
+ } catch (error) {
30145
+ if (error.code !== "EEXIST") {
30146
+ throw error;
30147
+ }
30148
+ const existing = await regularImage(target);
30149
+ if (sha256(existing) !== sha256(bytes)) {
30150
+ throw new WechatCliError(
30151
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30152
+ `\u4F18\u5316\u76EE\u6807 ${path5.basename(target)} \u5DF2\u5B58\u5728\u4F46\u5185\u5BB9\u4E0D\u540C\uFF1B\u672A\u8986\u76D6\u4EFB\u4F55\u6587\u4EF6\u3002`
30153
+ );
30154
+ }
30155
+ return false;
30156
+ }
30157
+ }
30158
+ async function safeRealPathWithin(target, directory, label) {
30159
+ const resolved = await realpath(target).catch(() => void 0);
30160
+ if (resolved === void 0 || !isWithin(resolved, directory)) {
30161
+ throw new WechatCliError(
30162
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30163
+ `${label} \u7684\u771F\u5B9E\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u7684\u9879\u76EE\u76EE\u5F55\u5185\u3002`
30164
+ );
30165
+ }
30166
+ return resolved;
30167
+ }
30168
+ async function ensureSafeOptimizedDirectory(assetsDirectory, realAssetsDirectory) {
30169
+ const optimizedDirectory = path5.join(assetsDirectory, "optimized");
30170
+ await mkdir3(optimizedDirectory, { recursive: true, mode: 448 });
30171
+ const metadata = await lstat3(optimizedDirectory).catch(() => void 0);
30172
+ if (metadata === void 0 || !metadata.isDirectory() || metadata.isSymbolicLink()) {
30173
+ throw new WechatCliError(
30174
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30175
+ "\u4F18\u5316\u76EE\u5F55\u5FC5\u987B\u662F\u7D20\u6750\u76EE\u5F55\u5185\u7684\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u662F\u7B26\u53F7\u94FE\u63A5\u3002"
30176
+ );
30177
+ }
30178
+ await safeRealPathWithin(optimizedDirectory, realAssetsDirectory, "\u4F18\u5316\u76EE\u5F55");
30179
+ return optimizedDirectory;
30180
+ }
30181
+ async function writePlanAtomically(target, expectedSource, source) {
30182
+ const temporary = `${target}.${randomUUID3()}.tmp`;
30183
+ try {
30184
+ await writeFile2(temporary, source, { encoding: "utf8", flag: "wx", mode: 384 });
30185
+ if (await readFile2(target, "utf8") !== expectedSource) {
30186
+ throw new WechatCliError(
30187
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30188
+ "\u89C6\u89C9\u8BA1\u5212\u5728\u56FE\u7247\u4F18\u5316\u671F\u95F4\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u4FEE\u6539\uFF1B\u672A\u8986\u76D6\u65B0\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u8BD5\u3002"
30189
+ );
30190
+ }
30191
+ await rename2(temporary, target);
30192
+ } finally {
30193
+ await unlink2(temporary).catch(() => void 0);
30194
+ }
30195
+ }
30196
+ function candidateWidths(width) {
30197
+ return [.../* @__PURE__ */ new Set([width, 2400, 2e3, 1600, 1280, 1080, 900, 720])].filter((candidate) => candidate > 0 && candidate <= width).sort((left, right) => right - left);
30198
+ }
30199
+ async function optimizedImage(source, targetBytes, metadata) {
30200
+ const hasAlpha = metadata.hasAlpha;
30201
+ for (const width of candidateWidths(metadata.width)) {
30202
+ if (hasAlpha) {
30203
+ const lossless = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
30204
+ if (lossless.byteLength <= targetBytes) {
30205
+ await requirePreservedAlpha(lossless);
30206
+ return { alphaPreserved: true, bytes: lossless, extension: ".png" };
30207
+ }
30208
+ for (const colours of [256, 192, 128, 96, 64]) {
30209
+ const quantized = await sharp(source, {
30210
+ failOn: "error",
30211
+ limitInputPixels: 8e7
30212
+ }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ colours, compressionLevel: 9, effort: 10, palette: true }).toBuffer();
30213
+ if (quantized.byteLength <= targetBytes) {
30214
+ await requirePreservedAlpha(quantized);
30215
+ return { alphaPreserved: true, bytes: quantized, extension: ".png" };
30216
+ }
30217
+ }
30218
+ continue;
30219
+ }
30220
+ for (const quality of [88, 82, 76, 70, 64, 58]) {
30221
+ const jpeg = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).jpeg({ chromaSubsampling: "4:4:4", mozjpeg: true, progressive: true, quality }).toBuffer();
30222
+ if (jpeg.byteLength <= targetBytes) {
30223
+ return { alphaPreserved: true, bytes: jpeg, extension: ".jpg" };
30224
+ }
30225
+ }
30226
+ }
30227
+ throw new WechatCliError(
30228
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_LIMIT_UNREACHABLE",
30229
+ `\u65E0\u6CD5\u5728\u4E0D\u4E22\u5931\u900F\u660E\u901A\u9053\u6216\u4F4E\u4E8E\u6700\u5C0F\u5C3A\u5BF8\u7B56\u7565\u7684\u524D\u63D0\u4E0B\u538B\u5230 ${targetBytes} bytes\uFF1B\u8BF7\u66F4\u6362\u6216\u88C1\u526A\u56FE\u7247\u3002`
30230
+ );
30231
+ }
30232
+ async function optimizePublishingImages(input) {
30233
+ const projectDirectory = path5.resolve(input.projectDirectory);
30234
+ const visualPlanPath = path5.resolve(input.visualPlanFile);
30235
+ if (!isWithin(visualPlanPath, projectDirectory)) {
30236
+ throw new WechatCliError(
30237
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30238
+ "\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5185\u5BB9\u9879\u76EE\u5185\u3002"
30239
+ );
30240
+ }
30241
+ const projectMetadata = await lstat3(projectDirectory).catch(() => void 0);
30242
+ const visualPlanMetadata = await lstat3(visualPlanPath).catch(() => void 0);
30243
+ if (projectMetadata === void 0 || !projectMetadata.isDirectory() || projectMetadata.isSymbolicLink() || visualPlanMetadata === void 0 || !visualPlanMetadata.isFile() || visualPlanMetadata.isSymbolicLink() || visualPlanMetadata.size > VISUAL_PLAN_MAXIMUM_BYTES) {
30244
+ throw new WechatCliError(
30245
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30246
+ "\u5185\u5BB9\u9879\u76EE\u548C\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u662F\u975E\u7B26\u53F7\u94FE\u63A5\u7684\u666E\u901A\u76EE\u5F55\u4E0E\u6587\u4EF6\u3002"
30247
+ );
30248
+ }
30249
+ const realProjectDirectory = await realpath(projectDirectory);
30250
+ await safeRealPathWithin(visualPlanPath, realProjectDirectory, "\u89C6\u89C9\u8BA1\u5212");
30251
+ const source = await readFile2(visualPlanPath, "utf8").catch(() => "");
30252
+ if (Buffer.byteLength(source, "utf8") > VISUAL_PLAN_MAXIMUM_BYTES) {
30253
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30254
+ }
30255
+ const match = VISUAL_PLAN_FRONTMATTER.exec(source);
30256
+ if (match?.[1] === void 0 || match[2] === void 0) {
30257
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30258
+ }
30259
+ let frontmatter2;
30260
+ try {
30261
+ frontmatter2 = recordValue((0, import_yaml2.parse)(match[1], { maxAliasCount: 10, uniqueKeys: true }));
30262
+ } catch {
30263
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30264
+ }
30265
+ if (frontmatter2 === void 0) {
30266
+ throw new WechatCliError(
30267
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30268
+ "\u89C6\u89C9\u8BA1\u5212\u7684 YAML \u5934\u65E0\u6CD5\u8BFB\u53D6\u3002"
30269
+ );
30270
+ }
30271
+ const assetsRelative = frontmatter2["assetsDirectory"] === void 0 ? "assets" : portablePath(frontmatter2["assetsDirectory"], "assetsDirectory");
30272
+ frontmatter2["assetsDirectory"] = assetsRelative;
30273
+ frontmatter2["inlineImagesSupported"] = true;
30274
+ const assetsDirectory = path5.resolve(projectDirectory, assetsRelative);
30275
+ if (!isWithin(assetsDirectory, projectDirectory)) {
30276
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30277
+ }
30278
+ const assetsMetadata = await lstat3(assetsDirectory).catch(() => void 0);
30279
+ if (assetsMetadata === void 0 || !assetsMetadata.isDirectory() || assetsMetadata.isSymbolicLink()) {
30280
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30281
+ }
30282
+ const realAssetsDirectory = await safeRealPathWithin(
30283
+ assetsDirectory,
30284
+ realProjectDirectory,
30285
+ "\u7D20\u6750\u76EE\u5F55"
30286
+ );
30287
+ const inlineAssets = Array.isArray(frontmatter2["inlineAssets"]) ? frontmatter2["inlineAssets"] : [];
30288
+ const entries = [
30289
+ {
30290
+ assetId: "cover",
30291
+ limit: COVER_IMAGE_LIMIT,
30292
+ record: { path: frontmatter2["coverAsset"] },
30293
+ role: "cover",
30294
+ target: COVER_IMAGE_TARGET
30295
+ },
30296
+ ...inlineAssets.map((asset, index) => ({
30297
+ assetId: `inline-${String(index + 1).padStart(2, "0")}`,
30298
+ limit: INLINE_IMAGE_LIMIT,
30299
+ record: recordValue(asset),
30300
+ role: "inline",
30301
+ target: INLINE_IMAGE_TARGET
30302
+ }))
30303
+ ];
30304
+ const createdFiles = [];
30305
+ const mutations = [];
30306
+ let unchangedCount = 0;
30307
+ let optimizedDirectory;
30308
+ try {
30309
+ for (const entry of entries) {
30310
+ const assetPath = portablePath(entry.record?.["path"], `${entry.role} \u56FE\u7247\u8DEF\u5F84`);
30311
+ const absolutePath = path5.resolve(projectDirectory, assetPath);
30312
+ if (!isWithin(absolutePath, assetsDirectory)) {
30313
+ throw new WechatCliError(
30314
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30315
+ `${assetPath} \u4E0D\u5728\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u5185\u3002`
30316
+ );
30317
+ }
30318
+ const assetMetadata = await lstat3(absolutePath).catch((error) => {
30319
+ if (error.code === "ENOENT") {
30320
+ return void 0;
30321
+ }
30322
+ throw error;
30323
+ });
30324
+ if (assetMetadata === void 0) {
30325
+ throw new WechatCliError(
30326
+ "DXC_PUBLISHING_VISUAL_ASSET_MISSING",
30327
+ `\u89C6\u89C9\u7D20\u6750 ${entry.assetId} \u7F3A\u5931\uFF1B\u8BF7\u5C06\u771F\u5B9E\u56FE\u7247\u5199\u5165 CLI \u5206\u914D\u7684\u4F4D\u7F6E\u540E\u91CD\u8BD5\u3002`,
30328
+ false,
30329
+ { assetId: entry.assetId, role: entry.role }
30330
+ );
30331
+ }
30332
+ await safeRealPathWithin(absolutePath, realAssetsDirectory, `${assetPath} \u56FE\u7247`);
30333
+ const bytes = await regularImage(absolutePath);
30334
+ const metadata = await decodedImageMetadata(bytes, assetPath);
30335
+ const beforeSha256 = sha256(bytes);
30336
+ let finalPath = assetPath;
30337
+ let finalBytes = bytes;
30338
+ let alphaPreserved = true;
30339
+ if (bytes.byteLength > entry.limit) {
30340
+ const optimized = await optimizedImage(bytes, entry.target, metadata);
30341
+ optimizedDirectory ??= await ensureSafeOptimizedDirectory(
30342
+ assetsDirectory,
30343
+ realAssetsDirectory
30344
+ );
30345
+ const baseName = safeDerivativeBase(path5.basename(assetPath, path5.extname(assetPath)));
30346
+ const optimizedSha256 = sha256(optimized.bytes);
30347
+ const optimizedName = `${baseName}-${entry.role}-${beforeSha256.slice(0, 12)}-${optimizedSha256.slice(0, 12)}${optimized.extension}`;
30348
+ const optimizedAbsolute = path5.join(optimizedDirectory, optimizedName);
30349
+ const created = await writeExclusiveOrVerify(optimizedAbsolute, optimized.bytes);
30350
+ if (created) {
30351
+ createdFiles.push(optimizedAbsolute);
30352
+ }
30353
+ finalPath = path5.relative(projectDirectory, optimizedAbsolute).split(path5.sep).join("/");
30354
+ finalBytes = optimized.bytes;
30355
+ alphaPreserved = optimized.alphaPreserved;
30356
+ mutations.push({
30357
+ alphaPreserved,
30358
+ afterBytes: finalBytes.byteLength,
30359
+ afterSha256: sha256(finalBytes),
30360
+ beforeBytes: bytes.byteLength,
30361
+ beforeSha256,
30362
+ from: assetPath,
30363
+ role: entry.role,
30364
+ to: finalPath
30365
+ });
30366
+ } else {
30367
+ unchangedCount += 1;
30368
+ }
30369
+ if (entry.role === "cover") {
30370
+ frontmatter2["coverAsset"] = finalPath;
30371
+ frontmatter2["coverSha256"] = sha256(finalBytes);
30372
+ } else if (entry.record !== void 0) {
30373
+ if (finalPath !== assetPath && entry.record["sourcePath"] === void 0) {
30374
+ entry.record["sourcePath"] = assetPath;
30375
+ }
30376
+ entry.record["path"] = finalPath;
30377
+ entry.record["sha256"] = sha256(finalBytes);
30378
+ }
30379
+ }
30380
+ frontmatter2["inlineAssets"] = inlineAssets;
30381
+ const normalizedSource = `---
30382
+ ${(0, import_yaml2.stringify)(frontmatter2, { lineWidth: 0 }).trimEnd()}
30383
+ ---
30384
+ ${match[2]}`;
30385
+ await writePlanAtomically(visualPlanPath, source, normalizedSource);
30386
+ } catch (error) {
30387
+ await Promise.all(createdFiles.map((target) => unlink2(target).catch(() => void 0)));
30388
+ if (error instanceof WechatCliError) {
30389
+ throw error;
30390
+ }
30391
+ throw new WechatCliError(
30392
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_FAILED",
30393
+ "\u56FE\u7247\u4F18\u5316\u672A\u5B8C\u6210\uFF1B\u539F\u56FE\u548C\u89C6\u89C9\u8BA1\u5212\u5747\u672A\u88AB\u8986\u76D6\u3002"
30394
+ );
30395
+ }
30396
+ return {
30397
+ command: "wechat.draft.optimize-images",
30398
+ data: { mutations, unchangedCount, visualPlanPath },
30399
+ ok: true
30400
+ };
30401
+ }
30402
+
29882
30403
  // apps/cli/src/publishing-assets.ts
29883
30404
  var MAXIMUM_ARTICLE_BYTES = 512 * 1024;
29884
- var MAXIMUM_IMAGE_BYTES = 5 * 1024 * 1024;
29885
- var MAXIMUM_INLINE_IMAGE_BYTES = 1024 * 1024 - 1;
29886
- var MAXIMUM_INLINE_IMAGES = 20;
30405
+ var PUBLISHING_COVER_MAXIMUM_BYTES = 5 * 1024 * 1024;
30406
+ var PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES = 1024 * 1024 - 1;
30407
+ var PUBLISHING_OPTIMIZABLE_IMAGE_MAXIMUM_BYTES = 32 * 1024 * 1024;
30408
+ var PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT = 20;
30409
+ var MAXIMUM_IMAGE_BYTES = PUBLISHING_COVER_MAXIMUM_BYTES;
30410
+ var MAXIMUM_INLINE_IMAGE_BYTES = PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES;
30411
+ var MAXIMUM_INLINE_IMAGES = PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT;
29887
30412
  var SUPPORTED_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpeg", ".jpg", ".png"]);
29888
- var VISUAL_PLAN_MAXIMUM_BYTES = 128 * 1024;
30413
+ var VISUAL_PLAN_MAXIMUM_BYTES2 = 128 * 1024;
29889
30414
  var ARTICLE_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
29890
- var VISUAL_PLAN_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
30415
+ var VISUAL_PLAN_FRONTMATTER2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
29891
30416
  var LOCAL_WORKFLOW_FRONTMATTER_KEYS = ["dxc", "goldenLinesPath", "goldenLinesSha256"];
29892
- function recordValue(value) {
30417
+ var WORKFLOW_VISUAL_SIDECAR_FILE = ".dxc/visual-assets.json";
30418
+ var WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE = ".dxc/publishing-visual-plan.md";
30419
+ var WORKFLOW_LOCAL_PREVIEW_FILE = ".dxc/previews/current.html";
30420
+ function recordValue2(value) {
29893
30421
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
29894
30422
  }
29895
30423
  async function validatePublishingImage(bytes, target) {
29896
30424
  let metadata;
29897
30425
  try {
29898
- metadata = await sharp(bytes, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30426
+ metadata = await sharp2(bytes, { failOn: "error", limitInputPixels: 8e7 }).metadata();
29899
30427
  } catch {
29900
30428
  throw new WechatCliError(
29901
30429
  "DXC_PUBLISHING_INPUT_INVALID",
29902
- `\u56FE\u7247 ${path5.basename(target)} \u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002`
30430
+ `\u56FE\u7247 ${path6.basename(target)} \u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002`
29903
30431
  );
29904
30432
  }
29905
- const extension = path5.extname(target).toLowerCase();
30433
+ const extension = path6.extname(target).toLowerCase();
29906
30434
  const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
29907
30435
  if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
29908
30436
  throw new WechatCliError(
29909
30437
  "DXC_PUBLISHING_INPUT_INVALID",
29910
- `\u56FE\u7247 ${path5.basename(target)} \u7684\u6269\u5C55\u540D\u4E0E\u771F\u5B9E PNG/JPEG \u683C\u5F0F\u4E0D\u4E00\u81F4\u3002`
30438
+ `\u56FE\u7247 ${path6.basename(target)} \u7684\u6269\u5C55\u540D\u4E0E\u771F\u5B9E PNG/JPEG \u683C\u5F0F\u4E0D\u4E00\u81F4\u3002`
29911
30439
  );
29912
30440
  }
29913
30441
  }
29914
30442
  async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29915
- const target = path5.resolve(inputPath);
30443
+ const target = path6.resolve(inputPath);
29916
30444
  let handle;
29917
30445
  try {
29918
- handle = await open(target, fileConstants.O_RDONLY | fileConstants.O_NOFOLLOW);
30446
+ handle = await open2(target, fileConstants2.O_RDONLY | fileConstants2.O_NOFOLLOW);
29919
30447
  const metadata = await handle.stat();
29920
30448
  if (metadata.isFile() && metadata.size > maximumBytes) {
29921
- const fileName = path5.basename(target);
30449
+ const fileName = path6.basename(target);
29922
30450
  if (role === "inline-image") {
29923
30451
  throw new WechatCliError(
29924
30452
  "DXC_PUBLISHING_INLINE_IMAGE_TOO_LARGE",
@@ -29939,7 +30467,7 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29939
30467
  if (!metadata.isFile() || metadata.size <= 0) {
29940
30468
  throw new WechatCliError(
29941
30469
  "DXC_PUBLISHING_INPUT_INVALID",
29942
- `\u6587\u4EF6 ${path5.basename(target)} \u4E0D\u662F\u53EF\u8BFB\u53D6\u7684\u975E\u7A7A\u666E\u901A\u6587\u4EF6\u3002`
30470
+ `\u6587\u4EF6 ${path6.basename(target)} \u4E0D\u662F\u53EF\u8BFB\u53D6\u7684\u975E\u7A7A\u666E\u901A\u6587\u4EF6\u3002`
29943
30471
  );
29944
30472
  }
29945
30473
  const bytes = await handle.readFile();
@@ -29953,7 +30481,7 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29953
30481
  }
29954
30482
  throw new WechatCliError(
29955
30483
  "DXC_PUBLISHING_INPUT_INVALID",
29956
- `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 ${path5.basename(target)}\uFF1B\u8BF7\u786E\u8BA4\u8DEF\u5F84\u3001\u6743\u9650\u4E14\u6587\u4EF6\u4E0D\u662F\u7B26\u53F7\u94FE\u63A5\u3002`
30484
+ `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 ${path6.basename(target)}\uFF1B\u8BF7\u786E\u8BA4\u8DEF\u5F84\u3001\u6743\u9650\u4E14\u6587\u4EF6\u4E0D\u662F\u7B26\u53F7\u94FE\u63A5\u3002`
29957
30485
  );
29958
30486
  } finally {
29959
30487
  await handle?.close();
@@ -29962,16 +30490,16 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29962
30490
  async function readContainedPublishingInput(inputPath, maximumBytes, allowedDirectories, role) {
29963
30491
  try {
29964
30492
  const [target, ...directories] = await Promise.all([
29965
- realpath(path5.resolve(inputPath)),
29966
- ...allowedDirectories.map((directory) => realpath(path5.resolve(directory)))
30493
+ realpath2(path6.resolve(inputPath)),
30494
+ ...allowedDirectories.map((directory) => realpath2(path6.resolve(directory)))
29967
30495
  ]);
29968
30496
  const contained = directories.some(
29969
- (directory) => target !== directory && target.startsWith(`${directory}${path5.sep}`)
30497
+ (directory) => target !== directory && target.startsWith(`${directory}${path6.sep}`)
29970
30498
  );
29971
30499
  if (!contained) {
29972
30500
  throw new WechatCliError(
29973
30501
  "DXC_PUBLISHING_INPUT_INVALID",
29974
- `\u81EA\u52A8\u89E3\u6790\u7684\u7D20\u6750 ${path5.basename(inputPath)} \u4E0D\u5728\u6587\u7AE0\u76EE\u5F55\u6216\u663E\u5F0F\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30502
+ `\u81EA\u52A8\u89E3\u6790\u7684\u7D20\u6750 ${path6.basename(inputPath)} \u4E0D\u5728\u6587\u7AE0\u76EE\u5F55\u6216\u663E\u5F0F\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
29975
30503
  );
29976
30504
  }
29977
30505
  } catch (error) {
@@ -29980,13 +30508,13 @@ async function readContainedPublishingInput(inputPath, maximumBytes, allowedDire
29980
30508
  }
29981
30509
  throw new WechatCliError(
29982
30510
  "DXC_PUBLISHING_INPUT_INVALID",
29983
- `\u65E0\u6CD5\u5B89\u5168\u89E3\u6790\u7D20\u6750 ${path5.basename(inputPath)}\u3002`
30511
+ `\u65E0\u6CD5\u5B89\u5168\u89E3\u6790\u7D20\u6750 ${path6.basename(inputPath)}\u3002`
29984
30512
  );
29985
30513
  }
29986
30514
  return readPublishingInput(inputPath, maximumBytes, role);
29987
30515
  }
29988
30516
  function publishingImageMediaType(inputPath) {
29989
- const extension = path5.extname(inputPath).toLowerCase();
30517
+ const extension = path6.extname(inputPath).toLowerCase();
29990
30518
  if (extension === ".png") {
29991
30519
  return "image/png";
29992
30520
  }
@@ -29999,13 +30527,13 @@ function safeFileName(value) {
29999
30527
  return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80);
30000
30528
  }
30001
30529
  function isWithinDirectory(target, directory) {
30002
- const relative = path5.relative(directory, target);
30003
- return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== "..";
30530
+ const relative = path6.relative(directory, target);
30531
+ return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
30004
30532
  }
30005
30533
  function commonDirectory(left, right) {
30006
- let directory = path5.resolve(left);
30534
+ let directory = path6.resolve(left);
30007
30535
  while (!isWithinDirectory(right, directory)) {
30008
- const parent = path5.dirname(directory);
30536
+ const parent = path6.dirname(directory);
30009
30537
  if (parent === directory) {
30010
30538
  return directory;
30011
30539
  }
@@ -30017,7 +30545,7 @@ async function collectedImages(directoryInput, articleDirectory) {
30017
30545
  if (directoryInput === void 0) {
30018
30546
  return [];
30019
30547
  }
30020
- const directory = path5.resolve(directoryInput);
30548
+ const directory = path6.resolve(directoryInput);
30021
30549
  let entries;
30022
30550
  try {
30023
30551
  const metadata = await lstat4(directory);
@@ -30029,12 +30557,12 @@ async function collectedImages(directoryInput, articleDirectory) {
30029
30557
  }
30030
30558
  const sharedDirectory = commonDirectory(articleDirectory, directory);
30031
30559
  const [resolvedDirectory, resolvedSharedDirectory] = await Promise.all([
30032
- realpath(directory),
30033
- realpath(sharedDirectory)
30560
+ realpath2(directory),
30561
+ realpath2(sharedDirectory)
30034
30562
  ]);
30035
- const expectedDirectory = path5.resolve(
30563
+ const expectedDirectory = path6.resolve(
30036
30564
  resolvedSharedDirectory,
30037
- path5.relative(sharedDirectory, directory)
30565
+ path6.relative(sharedDirectory, directory)
30038
30566
  );
30039
30567
  if (resolvedDirectory !== expectedDirectory) {
30040
30568
  throw new WechatCliError(
@@ -30050,8 +30578,8 @@ async function collectedImages(directoryInput, articleDirectory) {
30050
30578
  throw new WechatCliError("DXC_PUBLISHING_ASSETS_DIRECTORY_INVALID");
30051
30579
  }
30052
30580
  return entries.filter(
30053
- (entry) => entry.isFile() && SUPPORTED_IMAGE_EXTENSIONS.has(path5.extname(entry.name).toLowerCase())
30054
- ).map((entry) => path5.join(directory, entry.name)).sort((left, right) => left.localeCompare(right, "zh-CN"));
30581
+ (entry) => entry.isFile() && SUPPORTED_IMAGE_EXTENSIONS.has(path6.extname(entry.name).toLowerCase())
30582
+ ).map((entry) => path6.join(directory, entry.name)).sort((left, right) => left.localeCompare(right, "zh-CN"));
30055
30583
  }
30056
30584
  function injectCollectedImages(source, images) {
30057
30585
  const diagnosticText = (value, maximum = 120) => {
@@ -30081,8 +30609,14 @@ function injectCollectedImages(source, images) {
30081
30609
  }
30082
30610
  const insertions = /* @__PURE__ */ new Map();
30083
30611
  for (const [index, image] of images.entries()) {
30084
- let headingLine = headingLines[index];
30085
- if (image.placementAnchor !== void 0) {
30612
+ let headingLine = image.headingIndex === void 0 ? headingLines[index] : headingLines[image.headingIndex];
30613
+ if (image.headingIndex !== void 0 && headingLine === void 0) {
30614
+ throw new WechatCliError(
30615
+ "DXC_PUBLISHING_VISUAL_ANCHOR_INVALID",
30616
+ `\u89C6\u89C9\u8BA1\u5212\u7AE0\u8282\u5E8F\u53F7 ${image.headingIndex + 1} \u4E0D\u5B58\u5728\u3002`
30617
+ );
30618
+ }
30619
+ if (image.headingIndex === void 0 && image.placementAnchor !== void 0) {
30086
30620
  const match = /^after-heading:(.+)$/u.exec(image.placementAnchor);
30087
30621
  const requestedHeading = match?.[1]?.trim().replace(/^##\s+/u, "").replace(/\s+#+\s*$/u, "").trim();
30088
30622
  const matches = requestedHeading === void 0 ? void 0 : headingLineByText.get(requestedHeading);
@@ -30112,12 +30646,12 @@ function injectCollectedImages(source, images) {
30112
30646
  return output.join("\n");
30113
30647
  }
30114
30648
  function parsedVisualPlan(source) {
30115
- const yamlSource = VISUAL_PLAN_FRONTMATTER.exec(source)?.[1];
30649
+ const yamlSource = VISUAL_PLAN_FRONTMATTER2.exec(source)?.[1];
30116
30650
  if (yamlSource === void 0) {
30117
30651
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30118
30652
  }
30119
30653
  try {
30120
- const plan = recordValue((0, import_yaml2.parse)(yamlSource, { maxAliasCount: 10, uniqueKeys: true }));
30654
+ const plan = recordValue2((0, import_yaml3.parse)(yamlSource, { maxAliasCount: 10, uniqueKeys: true }));
30121
30655
  if (plan === void 0) {
30122
30656
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30123
30657
  }
@@ -30129,29 +30663,299 @@ function parsedVisualPlan(source) {
30129
30663
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30130
30664
  }
30131
30665
  }
30132
- function validateVisualPlanPlacementAnchors(articleSource, visualPlanSource) {
30133
- const plan = parsedVisualPlan(visualPlanSource);
30134
- const inlineAssets = Array.isArray(plan["inlineAssets"]) ? plan["inlineAssets"] : [];
30135
- const images = inlineAssets.map((rawAsset) => {
30136
- const asset = recordValue(rawAsset);
30137
- if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string") {
30666
+ function parsedWorkflowVisualAssetsSidecar(value) {
30667
+ const sidecar = recordValue2(value);
30668
+ const assets = sidecar?.["assets"];
30669
+ if (sidecar?.["version"] !== 1 || typeof sidecar["articleSha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(sidecar["articleSha256"]) || typeof sidecar["visualPlanSha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(sidecar["visualPlanSha256"]) || typeof sidecar["localPreviewPath"] !== "string" || typeof sidecar["publishingVisualPlanPath"] !== "string" || !Array.isArray(assets) || assets.length < 1 || assets.length > MAXIMUM_INLINE_IMAGES + 1) {
30670
+ throw new WechatCliError(
30671
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30672
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6548\u3002"
30673
+ );
30674
+ }
30675
+ const parsedAssets = assets.map((entry, index) => {
30676
+ const asset = recordValue2(entry);
30677
+ const expectedAssetId = index === 0 ? "cover" : `inline-${String(index).padStart(2, "0")}`;
30678
+ const relativePath = asset?.["relativePath"];
30679
+ const sectionId = asset?.["sectionId"];
30680
+ if (asset?.["assetId"] !== expectedAssetId || asset["role"] !== (index === 0 ? "cover" : "inline") || typeof relativePath !== "string" || relativePath.includes("\\") || path6.posix.isAbsolute(relativePath) || relativePath.split("/").some((segment) => segment === "" || segment === "." || segment === "..") || typeof asset["sha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(asset["sha256"]) || index > 0 && (typeof sectionId !== "string" || !/^sec_[0-9]{2}_[a-f0-9]{8}$/u.test(sectionId)) || index === 0 && sectionId !== void 0) {
30138
30681
  throw new WechatCliError(
30139
30682
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30140
- "\u6B63\u6587\u914D\u56FE\u9700\u8981 path \u548C placementAnchor\u3002"
30683
+ `\u89C6\u89C9\u7D20\u6750 ${expectedAssetId} \u7684 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6548\u3002`
30141
30684
  );
30142
30685
  }
30686
+ const parsedSectionId = typeof sectionId === "string" ? sectionId : void 0;
30143
30687
  return {
30144
- alt: "\u89C6\u89C9\u8BA1\u5212\u951A\u70B9\u6821\u9A8C",
30145
- logicalPath: asset["path"],
30146
- placementAnchor: asset["placementAnchor"]
30688
+ assetId: expectedAssetId,
30689
+ relativePath,
30690
+ role: index === 0 ? "cover" : "inline",
30691
+ ...parsedSectionId === void 0 ? {} : { sectionId: parsedSectionId },
30692
+ sha256: asset["sha256"]
30147
30693
  };
30148
30694
  });
30149
- injectCollectedImages(articleSource, images);
30695
+ return {
30696
+ articleSha256: sidecar["articleSha256"],
30697
+ assets: parsedAssets,
30698
+ localPreviewPath: sidecar["localPreviewPath"],
30699
+ publishingVisualPlanPath: sidecar["publishingVisualPlanPath"],
30700
+ version: 1,
30701
+ visualPlanSha256: sidecar["visualPlanSha256"]
30702
+ };
30703
+ }
30704
+ function workflowSha256(value) {
30705
+ return createHash5("sha256").update(value).digest("hex");
30150
30706
  }
30151
- async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30707
+ function semanticVisualPlan(source, sections) {
30708
+ const plan = parsedVisualPlan(source);
30709
+ const cover = recordValue2(plan["cover"]);
30710
+ const inline = Array.isArray(plan["inline"]) ? plan["inline"] : [];
30711
+ if (typeof cover?.["purpose"] !== "string" || cover["purpose"].trim().length === 0) {
30712
+ throw new WechatCliError(
30713
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30714
+ "\u89C6\u89C9\u8BA1\u5212\u9700\u8981\u8BF4\u660E\u5C01\u9762\u7684\u5185\u5BB9\u76EE\u7684\u3002"
30715
+ );
30716
+ }
30717
+ const sectionIds = new Set(sections.map((section) => section.sectionId));
30718
+ if (inline.length > MAXIMUM_INLINE_IMAGES) {
30719
+ throw new WechatCliError("DXC_PUBLISHING_TOO_MANY_INLINE_IMAGES");
30720
+ }
30721
+ const seenSections = /* @__PURE__ */ new Set();
30722
+ const parsedInline = inline.map((entry) => {
30723
+ const item = recordValue2(entry);
30724
+ if (typeof item?.["sectionId"] !== "string" || !sectionIds.has(item["sectionId"]) || typeof item["purpose"] !== "string" || item["purpose"].trim().length === 0 || seenSections.has(item["sectionId"])) {
30725
+ throw new WechatCliError(
30726
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30727
+ "\u6B63\u6587\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4E3A\u6709\u6548\u4E14\u552F\u4E00\u7684 sectionId \u586B\u5199\u56FE\u7247\u76EE\u7684\u3002"
30728
+ );
30729
+ }
30730
+ seenSections.add(item["sectionId"]);
30731
+ return { purpose: item["purpose"].trim(), sectionId: item["sectionId"] };
30732
+ });
30733
+ return { cover: { purpose: cover["purpose"].trim() }, inline: parsedInline };
30734
+ }
30735
+ function parseWorkflowArticleSections(articleSource) {
30736
+ const sourceHash = workflowSha256(articleSource);
30737
+ const lines = articleSource.split(/\r?\n/u);
30738
+ const sections = [];
30739
+ let inFence = false;
30740
+ for (const line of lines) {
30741
+ if (/^\s*```/u.test(line)) {
30742
+ inFence = !inFence;
30743
+ continue;
30744
+ }
30745
+ if (inFence || !/^##\s+\S/u.test(line)) {
30746
+ continue;
30747
+ }
30748
+ const heading = line.replace(/^##\s+/u, "").replace(/\s+#+\s*$/u, "").trim();
30749
+ const ordinal = sections.length + 1;
30750
+ const identity = workflowSha256(`${sourceHash}:${ordinal}:${heading}`).slice(0, 8);
30751
+ sections.push({
30752
+ heading,
30753
+ sectionId: `sec_${String(ordinal).padStart(2, "0")}_${identity}`
30754
+ });
30755
+ }
30756
+ return sections;
30757
+ }
30758
+ function validateWorkflowSemanticVisualPlan(input) {
30759
+ return semanticVisualPlan(input.source, input.sections);
30760
+ }
30761
+ function workflowVisualAssetAssignments(input) {
30762
+ const plan = semanticVisualPlan(input.visualPlanSource, input.sections);
30763
+ const directory = path6.join(
30764
+ input.assetsDirectory ?? path6.join(input.projectDirectory, "assets"),
30765
+ "visuals"
30766
+ );
30767
+ return [
30768
+ { assetId: "cover", path: path6.join(directory, "cover.png"), role: "cover" },
30769
+ ...plan.inline.map((item, index) => ({
30770
+ assetId: `inline-${String(index + 1).padStart(2, "0")}`,
30771
+ path: path6.join(directory, `inline-${String(index + 1).padStart(2, "0")}.png`),
30772
+ role: "inline",
30773
+ sectionId: item.sectionId
30774
+ }))
30775
+ ];
30776
+ }
30777
+ function localWorkflowPreviewHtml(prepared) {
30778
+ const imageSources = new Map(
30779
+ prepared.inlineAssets.map((asset) => [
30780
+ asset.logicalPath,
30781
+ `data:${asset.mediaType};base64,${asset.bytes.toString("base64")}`
30782
+ ])
30783
+ );
30784
+ const article = rewriteWechatAssetSources(prepared.localRender.html, imageSources);
30785
+ const coverMediaType = publishingImageMediaType(prepared.cover.path);
30786
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="referrer" content="no-referrer"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'"><title>\u672C\u5730\u5E26\u56FE\u9884\u89C8</title></head><body><main><p>\u672C\u5730\u5185\u5BB9\u4E0E\u914D\u56FE\u9884\u89C8\uFF1B\u6700\u7EC8\u4EA4\u4ED8\u4EE5\u4E91\u7AEF\u4E0D\u53EF\u53D8\u9884\u89C8\u4E3A\u51C6\u3002</p><img alt="\u5C01\u9762" style="max-width:100%" src="data:${coverMediaType};base64,${prepared.cover.bytes.toString("base64")}"><article>${article}</article></main></body></html>`;
30787
+ }
30788
+ async function finalizeWorkflowVisualAssets(input) {
30789
+ const plan = semanticVisualPlan(input.visualPlanSource, input.sections);
30790
+ const expected = workflowVisualAssetAssignments(input);
30791
+ if (expected.length !== input.assignments.length || expected.some(
30792
+ (asset, index) => asset.assetId !== input.assignments[index]?.assetId || path6.resolve(asset.path) !== path6.resolve(input.assignments[index]?.path)
30793
+ )) {
30794
+ throw new WechatCliError(
30795
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30796
+ "\u89C6\u89C9\u7D20\u6750\u5206\u914D\u4E0E\u5F53\u524D\u5DE5\u4F5C\u9879\u4E0D\u4E00\u81F4\u3002"
30797
+ );
30798
+ }
30799
+ await mkdir4(path6.join(input.assetsDirectory, "visuals"), { recursive: true });
30800
+ const sectionIndex = new Map(input.sections.map((section, index) => [section.sectionId, index]));
30801
+ const publishingPlanPath = path6.join(
30802
+ input.projectDirectory,
30803
+ WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE
30804
+ );
30805
+ const relative = (target) => path6.relative(input.projectDirectory, target).split(path6.sep).join("/");
30806
+ const publishingPlan = {
30807
+ assetsDirectory: relative(input.assetsDirectory),
30808
+ coverAsset: relative(expected[0].path),
30809
+ inlineAssets: expected.slice(1).map((asset, index) => ({
30810
+ assetId: asset.assetId,
30811
+ path: relative(asset.path),
30812
+ placementHeadingIndex: sectionIndex.get(asset.sectionId ?? ""),
30813
+ purpose: plan.inline[index]?.purpose,
30814
+ source: "agent-generated"
30815
+ }))
30816
+ };
30817
+ await writePrivateBuffer(
30818
+ publishingPlanPath,
30819
+ Buffer.from(`---
30820
+ ${(0, import_yaml3.stringify)(publishingPlan, { lineWidth: 0 }).trimEnd()}
30821
+ ---
30822
+ `, "utf8")
30823
+ );
30824
+ await optimizePublishingImages({
30825
+ projectDirectory: input.projectDirectory,
30826
+ visualPlanFile: publishingPlanPath
30827
+ });
30828
+ const prepared = await preparePublishingDraft({
30829
+ articleFile: input.articleFile,
30830
+ assetsDirectory: input.assetsDirectory,
30831
+ coverFile: "auto",
30832
+ visualPlanFile: publishingPlanPath
30833
+ });
30834
+ const localPreviewPath = path6.join(input.projectDirectory, WORKFLOW_LOCAL_PREVIEW_FILE);
30835
+ await writePrivateBuffer(
30836
+ localPreviewPath,
30837
+ Buffer.from(localWorkflowPreviewHtml(prepared), "utf8")
30838
+ );
30839
+ const materializedPlan = parsedVisualPlan(await readFile3(publishingPlanPath, "utf8"));
30840
+ const materializedInline = Array.isArray(materializedPlan["inlineAssets"]) ? materializedPlan["inlineAssets"] : [];
30841
+ const materializedCover = String(materializedPlan["coverAsset"]);
30842
+ const materialized = [
30843
+ { assetId: "cover", path: materializedCover, role: "cover" },
30844
+ ...materializedInline.map((entry, index) => {
30845
+ const item = recordValue2(entry);
30846
+ const sectionId = expected[index + 1]?.sectionId;
30847
+ return {
30848
+ assetId: expected[index + 1]?.assetId ?? `inline-${index + 1}`,
30849
+ path: String(item?.["path"]),
30850
+ role: "inline",
30851
+ ...sectionId === void 0 ? {} : { sectionId }
30852
+ };
30853
+ })
30854
+ ];
30855
+ const sidecar = {
30856
+ articleSha256: workflowSha256(input.articleSource),
30857
+ assets: await Promise.all(
30858
+ materialized.map(async (asset) => {
30859
+ const bytes = await readFile3(path6.join(input.projectDirectory, asset.path));
30860
+ return {
30861
+ assetId: asset.assetId,
30862
+ relativePath: asset.path,
30863
+ role: asset.role,
30864
+ ...asset.sectionId === void 0 ? {} : { sectionId: asset.sectionId },
30865
+ sha256: workflowSha256(bytes)
30866
+ };
30867
+ })
30868
+ ),
30869
+ localPreviewPath,
30870
+ publishingVisualPlanPath: publishingPlanPath,
30871
+ version: 1,
30872
+ visualPlanSha256: workflowSha256(input.visualPlanSource)
30873
+ };
30874
+ await writePrivateJson(path6.join(input.projectDirectory, WORKFLOW_VISUAL_SIDECAR_FILE), sidecar);
30875
+ return sidecar;
30876
+ }
30877
+ async function readWorkflowVisualAssetsSidecar(projectDirectory) {
30878
+ const input = await readPublishingInput(
30879
+ path6.join(projectDirectory, WORKFLOW_VISUAL_SIDECAR_FILE),
30880
+ VISUAL_PLAN_MAXIMUM_BYTES2
30881
+ );
30882
+ try {
30883
+ return parsedWorkflowVisualAssetsSidecar(JSON.parse(input.bytes.toString("utf8")));
30884
+ } catch (error) {
30885
+ if (error instanceof WechatCliError) {
30886
+ throw error;
30887
+ }
30888
+ throw new WechatCliError(
30889
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30890
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6CD5\u89E3\u6790\u3002"
30891
+ );
30892
+ }
30893
+ }
30894
+ async function workflowVisualAssetsFingerprint(projectDirectory) {
30895
+ try {
30896
+ const sidecar = await readWorkflowVisualAssetsSidecar(projectDirectory);
30897
+ const assets = await Promise.all(
30898
+ sidecar.assets.map(async (asset) => {
30899
+ const target = path6.resolve(projectDirectory, asset.relativePath);
30900
+ if (!isWithinDirectory(target, projectDirectory)) {
30901
+ return { assetId: asset.assetId, state: "invalid" };
30902
+ }
30903
+ try {
30904
+ const input = await readPublishingInput(
30905
+ target,
30906
+ asset.role === "cover" ? MAXIMUM_IMAGE_BYTES : MAXIMUM_INLINE_IMAGE_BYTES,
30907
+ asset.role === "cover" ? "cover" : "inline-image"
30908
+ );
30909
+ return { assetId: asset.assetId, sha256: workflowSha256(input.bytes) };
30910
+ } catch {
30911
+ return { assetId: asset.assetId, state: "invalid" };
30912
+ }
30913
+ })
30914
+ );
30915
+ return workflowSha256(JSON.stringify({ assets, sidecar }));
30916
+ } catch {
30917
+ return workflowSha256("visual-assets:missing-or-invalid");
30918
+ }
30919
+ }
30920
+ async function validateWorkflowVisualAssets(input) {
30921
+ const sidecar = await readWorkflowVisualAssetsSidecar(input.projectDirectory);
30922
+ if (sidecar.articleSha256 !== workflowSha256(input.articleSource) || sidecar.visualPlanSha256 !== workflowSha256(input.visualPlanSource) || path6.resolve(sidecar.localPreviewPath) !== path6.resolve(input.projectDirectory, WORKFLOW_LOCAL_PREVIEW_FILE) || path6.resolve(sidecar.publishingVisualPlanPath) !== path6.resolve(input.projectDirectory, WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE)) {
30923
+ throw new WechatCliError(
30924
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30925
+ "\u6587\u7AE0\u3001\u89C6\u89C9\u8BA1\u5212\u6216\u672C\u5730\u9884\u89C8\u4E0E\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u4E0D\u4E00\u81F4\u3002"
30926
+ );
30927
+ }
30928
+ for (const asset of sidecar.assets) {
30929
+ const target = path6.resolve(input.projectDirectory, asset.relativePath);
30930
+ if (!isWithinDirectory(target, path6.resolve(input.assetsDirectory))) {
30931
+ throw new WechatCliError(
30932
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30933
+ `\u89C6\u89C9\u7D20\u6750 ${asset.assetId} \u4E0D\u5728\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30934
+ );
30935
+ }
30936
+ const materialized = await readPublishingInput(
30937
+ target,
30938
+ asset.role === "cover" ? MAXIMUM_IMAGE_BYTES : MAXIMUM_INLINE_IMAGE_BYTES,
30939
+ asset.role === "cover" ? "cover" : "inline-image"
30940
+ );
30941
+ if (workflowSha256(materialized.bytes) !== asset.sha256) {
30942
+ throw new WechatCliError(
30943
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30944
+ `\u89C6\u89C9\u7D20\u6750 ${asset.assetId} \u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u5B8C\u6210 visual-plan \u9636\u6BB5\u3002`
30945
+ );
30946
+ }
30947
+ }
30948
+ await readPublishingInput(
30949
+ sidecar.publishingVisualPlanPath,
30950
+ VISUAL_PLAN_MAXIMUM_BYTES2,
30951
+ "visual-plan"
30952
+ );
30953
+ return sidecar;
30954
+ }
30955
+ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximumCoverBytes = MAXIMUM_IMAGE_BYTES, maximumInlineBytes = MAXIMUM_INLINE_IMAGE_BYTES) {
30152
30956
  let source;
30153
30957
  try {
30154
- source = (await readPublishingInput(visualPlanFile, VISUAL_PLAN_MAXIMUM_BYTES, "visual-plan")).bytes.toString("utf8");
30958
+ source = (await readPublishingInput(visualPlanFile, VISUAL_PLAN_MAXIMUM_BYTES2, "visual-plan")).bytes.toString("utf8");
30155
30959
  } catch {
30156
30960
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30157
30961
  }
@@ -30167,12 +30971,12 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30167
30971
  "\u4F7F\u7528\u89C6\u89C9\u8BA1\u5212\u65F6\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B\u5176\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u3002"
30168
30972
  );
30169
30973
  }
30170
- const assetsDirectory = path5.resolve(assetsDirectoryInput);
30974
+ const assetsDirectory = path6.resolve(assetsDirectoryInput);
30171
30975
  const projectDirectory = commonDirectory(
30172
- path5.dirname(path5.resolve(visualPlanFile)),
30976
+ path6.dirname(path6.resolve(visualPlanFile)),
30173
30977
  assetsDirectory
30174
30978
  );
30175
- const declaredAssetsDirectory = typeof plan["assetsDirectory"] === "string" ? path5.resolve(projectDirectory, plan["assetsDirectory"]) : assetsDirectory;
30979
+ const declaredAssetsDirectory = typeof plan["assetsDirectory"] === "string" ? path6.resolve(projectDirectory, plan["assetsDirectory"]) : assetsDirectory;
30176
30980
  if (!isWithinDirectory(assetsDirectory, declaredAssetsDirectory)) {
30177
30981
  throw new WechatCliError(
30178
30982
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
@@ -30180,7 +30984,7 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30180
30984
  );
30181
30985
  }
30182
30986
  const resolvePlannedAsset = (assetPath) => {
30183
- const target = path5.resolve(projectDirectory, assetPath);
30987
+ const target = path6.resolve(projectDirectory, assetPath);
30184
30988
  if (!isWithinDirectory(target, declaredAssetsDirectory) || !isWithinDirectory(target, assetsDirectory)) {
30185
30989
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30186
30990
  }
@@ -30189,7 +30993,7 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30189
30993
  const coverAbsolutePath = resolvePlannedAsset(coverAsset);
30190
30994
  await readContainedPublishingInput(
30191
30995
  coverAbsolutePath,
30192
- MAXIMUM_IMAGE_BYTES,
30996
+ maximumCoverBytes,
30193
30997
  [assetsDirectory],
30194
30998
  "cover"
30195
30999
  );
@@ -30197,23 +31001,24 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30197
31001
  coverAbsolutePath,
30198
31002
  inline: await Promise.all(
30199
31003
  inlineAssets.map(async (rawAsset) => {
30200
- const asset = recordValue(rawAsset);
30201
- if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string") {
31004
+ const asset = recordValue2(rawAsset);
31005
+ const headingIndex = asset?.["placementHeadingIndex"];
31006
+ if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string" && (!Number.isInteger(headingIndex) || Number(headingIndex) < 0)) {
30202
31007
  throw new WechatCliError(
30203
31008
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30204
- "\u6B63\u6587\u914D\u56FE\u9700\u8981 path \u548C placementAnchor\u3002"
31009
+ "\u6B63\u6587\u914D\u56FE\u9700\u8981\u5B89\u5168\u8DEF\u5F84\u548C\u6709\u6548\u7AE0\u8282\u5F15\u7528\u3002"
30205
31010
  );
30206
31011
  }
30207
31012
  const absolutePath = resolvePlannedAsset(asset["path"]);
30208
31013
  if (!isWithinDirectory(absolutePath, assetsDirectory)) {
30209
31014
  throw new WechatCliError(
30210
31015
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30211
- `\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750 ${path5.posix.basename(asset["path"])} \u4E0D\u5728\u6307\u5B9A\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
31016
+ `\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750 ${path6.posix.basename(asset["path"])} \u4E0D\u5728\u6307\u5B9A\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30212
31017
  );
30213
31018
  }
30214
31019
  await readContainedPublishingInput(
30215
31020
  absolutePath,
30216
- MAXIMUM_INLINE_IMAGE_BYTES,
31021
+ maximumInlineBytes,
30217
31022
  [assetsDirectory],
30218
31023
  "inline-image"
30219
31024
  );
@@ -30224,13 +31029,14 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput) {
30224
31029
  "\u6B63\u6587\u914D\u56FE\u7684\u539F\u59CB\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002"
30225
31030
  );
30226
31031
  }
30227
- const digest = createHash4("sha256").update(absolutePath).digest("hex").slice(0, 12);
30228
- const fileName = safeFileName(path5.basename(absolutePath)) || `visual-${digest}.png`;
31032
+ const digest = createHash5("sha256").update(absolutePath).digest("hex").slice(0, 12);
31033
+ const fileName = safeFileName(path6.basename(absolutePath)) || `visual-${digest}.png`;
30229
31034
  return {
30230
31035
  absolutePath,
30231
- alt: typeof asset["purpose"] === "string" ? asset["purpose"] : path5.basename(absolutePath, path5.extname(absolutePath)),
31036
+ alt: typeof asset["purpose"] === "string" ? asset["purpose"] : path6.basename(absolutePath, path6.extname(absolutePath)),
30232
31037
  logicalPath: `assets/dxc-visuals/${digest}-${fileName}`,
30233
- placementAnchor: asset["placementAnchor"],
31038
+ ...typeof headingIndex === "number" ? { headingIndex } : {},
31039
+ ...typeof asset["placementAnchor"] === "string" ? { placementAnchor: asset["placementAnchor"] } : {},
30234
31040
  plannedPath: asset["path"],
30235
31041
  ...sourceLogicalPath === void 0 ? {} : { sourceLogicalPath }
30236
31042
  };
@@ -30248,7 +31054,7 @@ function publishingArticleSource(source, digestOverride) {
30248
31054
  if (yamlSource === void 0 || body === void 0) {
30249
31055
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30250
31056
  }
30251
- const document = (0, import_yaml2.parseDocument)(yamlSource, { uniqueKeys: true });
31057
+ const document = (0, import_yaml3.parseDocument)(yamlSource, { uniqueKeys: true });
30252
31058
  if (document.errors.length > 0) {
30253
31059
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30254
31060
  }
@@ -30273,21 +31079,21 @@ async function preparePublishingDraft(input) {
30273
31079
  } catch {
30274
31080
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30275
31081
  }
30276
- const articleDirectory = path5.dirname(articleInput.path);
31082
+ const articleDirectory = path6.dirname(articleInput.path);
30277
31083
  const availableImages = await collectedImages(input.assetsDirectory, articleDirectory);
30278
31084
  const availableByFileName = new Map(
30279
- availableImages.map((candidate) => [path5.basename(candidate), candidate])
31085
+ availableImages.map((candidate) => [path6.basename(candidate), candidate])
30280
31086
  );
30281
- const projectDirectory = input.assetsDirectory === void 0 ? articleDirectory : commonDirectory(articleDirectory, path5.resolve(input.assetsDirectory));
30282
- const resolveDeclaredAsset = (logicalPath) => availableByFileName.get(path5.posix.basename(logicalPath)) ?? path5.resolve(projectDirectory, logicalPath);
31087
+ const projectDirectory = input.assetsDirectory === void 0 ? articleDirectory : commonDirectory(articleDirectory, path6.resolve(input.assetsDirectory));
31088
+ const resolveDeclaredAsset = (logicalPath) => availableByFileName.get(path6.posix.basename(logicalPath)) ?? path6.resolve(projectDirectory, logicalPath);
30283
31089
  const referenced = new Set(logicalInlinePaths(originalBundle));
30284
31090
  const automaticAssetDirectories = [
30285
31091
  articleDirectory,
30286
- ...input.assetsDirectory === void 0 ? [] : [path5.resolve(input.assetsDirectory)]
31092
+ ...input.assetsDirectory === void 0 ? [] : [path6.resolve(input.assetsDirectory)]
30287
31093
  ];
30288
31094
  const articleCoverCandidate = originalBundle.coverImage !== void 0 && isSafeLogicalAssetPath(originalBundle.coverImage) ? resolveDeclaredAsset(originalBundle.coverImage) : void 0;
30289
31095
  const namedCoverCandidate = availableImages.find(
30290
- (candidate) => /^cover(?:[._-]|$)/iu.test(path5.basename(candidate))
31096
+ (candidate) => /^cover(?:[._-]|$)/iu.test(path6.basename(candidate))
30291
31097
  );
30292
31098
  const explicitCover = input.coverFile !== void 0 && input.coverFile !== "auto" ? input.coverFile : void 0;
30293
31099
  const planned = input.visualPlanFile === void 0 ? void 0 : await plannedVisualAssets(input.visualPlanFile, input.assetsDirectory);
@@ -30313,16 +31119,16 @@ async function preparePublishingDraft(input) {
30313
31119
  const coverCandidate = explicitCover ?? planned?.coverAbsolutePath ?? articleCoverCandidate ?? namedCoverCandidate;
30314
31120
  const coverMode = explicitCover !== void 0 ? "explicit-file" : planned?.coverAbsolutePath !== void 0 ? "visual-plan" : articleCoverCandidate !== void 0 ? "article-frontmatter" : namedCoverCandidate !== void 0 ? "assets-directory" : void 0;
30315
31121
  const pickupCandidates = availableImages.filter(
30316
- (candidate) => candidate !== namedCoverCandidate && candidate !== articleCoverCandidate && !referencedFiles.has(path5.resolve(candidate)) && input.visualPlanFile === void 0
31122
+ (candidate) => candidate !== namedCoverCandidate && candidate !== articleCoverCandidate && !referencedFiles.has(path6.resolve(candidate)) && input.visualPlanFile === void 0
30317
31123
  );
30318
31124
  const injected = [
30319
31125
  ...plannedInlineForInjection,
30320
31126
  ...pickupCandidates.slice(0, Math.max(0, MAXIMUM_INLINE_IMAGES - referenced.size)).map((candidate) => {
30321
- const digest = createHash4("sha256").update(candidate).digest("hex").slice(0, 12);
30322
- const fileName = safeFileName(path5.basename(candidate)) || `visual-${digest}.png`;
31127
+ const digest = createHash5("sha256").update(candidate).digest("hex").slice(0, 12);
31128
+ const fileName = safeFileName(path6.basename(candidate)) || `visual-${digest}.png`;
30323
31129
  return {
30324
31130
  absolutePath: candidate,
30325
- alt: path5.basename(candidate, path5.extname(candidate)),
31131
+ alt: path6.basename(candidate, path6.extname(candidate)),
30326
31132
  logicalPath: `assets/dxc-visuals/${digest}-${fileName}`
30327
31133
  };
30328
31134
  }).filter((asset) => !referenced.has(asset.logicalPath))
@@ -30366,7 +31172,7 @@ async function preparePublishingDraft(input) {
30366
31172
  );
30367
31173
  const inlineAssets = await Promise.all(
30368
31174
  logicalInlinePaths(bundle).map(async (logicalPath) => {
30369
- const target = injectedByLogicalPath.get(logicalPath) ?? referencedFilesByLogicalPath.get(logicalPath) ?? path5.resolve(articleDirectory, logicalPath);
31175
+ const target = injectedByLogicalPath.get(logicalPath) ?? referencedFilesByLogicalPath.get(logicalPath) ?? path6.resolve(articleDirectory, logicalPath);
30370
31176
  const asset = await readContainedPublishingInput(
30371
31177
  target,
30372
31178
  MAXIMUM_INLINE_IMAGE_BYTES,
@@ -30407,354 +31213,6 @@ async function preparePublishingDraft(input) {
30407
31213
  };
30408
31214
  }
30409
31215
 
30410
- // apps/cli/src/publishing-image-optimization.ts
30411
- var import_yaml3 = __toESM(require_dist(), 1);
30412
- import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
30413
- import { constants as fileConstants2 } from "node:fs";
30414
- import {
30415
- lstat as lstat5,
30416
- mkdir as mkdir3,
30417
- open as open2,
30418
- readFile as readFile2,
30419
- realpath as realpath2,
30420
- rename as rename3,
30421
- unlink,
30422
- writeFile
30423
- } from "node:fs/promises";
30424
- import path6 from "node:path";
30425
- import sharp2 from "sharp";
30426
- var MAXIMUM_INPUT_BYTES = 32 * 1024 * 1024;
30427
- var INLINE_IMAGE_LIMIT = 1024 * 1024 - 1;
30428
- var INLINE_IMAGE_TARGET = 950 * 1024;
30429
- var COVER_IMAGE_LIMIT = 5 * 1024 * 1024;
30430
- var COVER_IMAGE_TARGET = 4500 * 1024;
30431
- var VISUAL_PLAN_MAXIMUM_BYTES2 = 128 * 1024;
30432
- var VISUAL_PLAN_FRONTMATTER2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
30433
- function sha256(value) {
30434
- return createHash5("sha256").update(value).digest("hex");
30435
- }
30436
- function safeDerivativeBase(value) {
30437
- return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80) || "image";
30438
- }
30439
- function recordValue2(value) {
30440
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
30441
- }
30442
- function portablePath(value, label) {
30443
- if (typeof value !== "string" || value.trim().length === 0 || path6.isAbsolute(value) || value.includes("\\") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
30444
- throw new WechatCliError(
30445
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30446
- `${label} \u5FC5\u987B\u662F\u9879\u76EE\u5185\u4F7F\u7528 / \u5206\u9694\u7684\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002`
30447
- );
30448
- }
30449
- return value;
30450
- }
30451
- function isWithin(target, directory) {
30452
- const relative = path6.relative(directory, target);
30453
- return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
30454
- }
30455
- async function regularImage(target) {
30456
- let handle;
30457
- try {
30458
- handle = await open2(target, fileConstants2.O_RDONLY | fileConstants2.O_NOFOLLOW);
30459
- const metadata = await handle.stat();
30460
- if (!metadata.isFile() || metadata.size <= 0 || metadata.size > MAXIMUM_INPUT_BYTES) {
30461
- throw new Error("invalid image");
30462
- }
30463
- return await handle.readFile();
30464
- } catch {
30465
- throw new WechatCliError(
30466
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30467
- `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u56FE\u7247 ${path6.basename(target)}\uFF1B\u6587\u4EF6\u5FC5\u987B\u662F\u5C0F\u4E8E ${MAXIMUM_INPUT_BYTES} bytes \u7684\u666E\u901A\u6587\u4EF6\u3002`
30468
- );
30469
- } finally {
30470
- await handle?.close();
30471
- }
30472
- }
30473
- async function requirePreservedAlpha(bytes) {
30474
- const metadata = await sharp2(bytes, {
30475
- failOn: "error",
30476
- limitInputPixels: 8e7
30477
- }).metadata();
30478
- if (metadata.format !== "png" || metadata.hasAlpha !== true) {
30479
- throw new WechatCliError(
30480
- "DXC_PUBLISHING_IMAGE_ALPHA_LOST",
30481
- "\u900F\u660E\u56FE\u7247\u4F18\u5316\u540E\u4E22\u5931\u900F\u660E\u901A\u9053\uFF1B\u5DF2\u963B\u65AD\u4E14\u4E0D\u4F1A\u8986\u76D6\u539F\u56FE\u3002"
30482
- );
30483
- }
30484
- }
30485
- async function decodedImageMetadata(source, sourcePath) {
30486
- let metadata;
30487
- try {
30488
- metadata = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30489
- } catch {
30490
- throw new WechatCliError(
30491
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30492
- "\u56FE\u7247\u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002"
30493
- );
30494
- }
30495
- const extension = path6.extname(sourcePath).toLowerCase();
30496
- const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
30497
- if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
30498
- throw new WechatCliError(
30499
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30500
- "\u56FE\u7247\u6269\u5C55\u540D\u3001\u771F\u5B9E\u683C\u5F0F\u6216\u5C3A\u5BF8\u65E0\u6548\uFF1B\u4EC5\u652F\u6301\u6269\u5C55\u540D\u4E0E\u5185\u5BB9\u4E00\u81F4\u7684 PNG/JPEG\u3002"
30501
- );
30502
- }
30503
- return {
30504
- format: expectedFormat,
30505
- hasAlpha: metadata.hasAlpha === true,
30506
- height: metadata.height,
30507
- width: metadata.width
30508
- };
30509
- }
30510
- async function writeExclusiveOrVerify(target, bytes) {
30511
- await mkdir3(path6.dirname(target), { recursive: true, mode: 448 });
30512
- try {
30513
- await writeFile(target, bytes, { flag: "wx", mode: 384 });
30514
- return true;
30515
- } catch (error) {
30516
- if (error.code !== "EEXIST") {
30517
- throw error;
30518
- }
30519
- const existing = await regularImage(target);
30520
- if (sha256(existing) !== sha256(bytes)) {
30521
- throw new WechatCliError(
30522
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30523
- `\u4F18\u5316\u76EE\u6807 ${path6.basename(target)} \u5DF2\u5B58\u5728\u4F46\u5185\u5BB9\u4E0D\u540C\uFF1B\u672A\u8986\u76D6\u4EFB\u4F55\u6587\u4EF6\u3002`
30524
- );
30525
- }
30526
- return false;
30527
- }
30528
- }
30529
- async function safeRealPathWithin(target, directory, label) {
30530
- const resolved = await realpath2(target).catch(() => void 0);
30531
- if (resolved === void 0 || !isWithin(resolved, directory)) {
30532
- throw new WechatCliError(
30533
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30534
- `${label} \u7684\u771F\u5B9E\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u7684\u9879\u76EE\u76EE\u5F55\u5185\u3002`
30535
- );
30536
- }
30537
- return resolved;
30538
- }
30539
- async function ensureSafeOptimizedDirectory(assetsDirectory, realAssetsDirectory) {
30540
- const optimizedDirectory = path6.join(assetsDirectory, "optimized");
30541
- await mkdir3(optimizedDirectory, { recursive: true, mode: 448 });
30542
- const metadata = await lstat5(optimizedDirectory).catch(() => void 0);
30543
- if (metadata === void 0 || !metadata.isDirectory() || metadata.isSymbolicLink()) {
30544
- throw new WechatCliError(
30545
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30546
- "\u4F18\u5316\u76EE\u5F55\u5FC5\u987B\u662F\u7D20\u6750\u76EE\u5F55\u5185\u7684\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u662F\u7B26\u53F7\u94FE\u63A5\u3002"
30547
- );
30548
- }
30549
- await safeRealPathWithin(optimizedDirectory, realAssetsDirectory, "\u4F18\u5316\u76EE\u5F55");
30550
- return optimizedDirectory;
30551
- }
30552
- async function writePlanAtomically(target, expectedSource, source) {
30553
- const temporary = `${target}.${randomUUID3()}.tmp`;
30554
- try {
30555
- await writeFile(temporary, source, { encoding: "utf8", flag: "wx", mode: 384 });
30556
- if (await readFile2(target, "utf8") !== expectedSource) {
30557
- throw new WechatCliError(
30558
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30559
- "\u89C6\u89C9\u8BA1\u5212\u5728\u56FE\u7247\u4F18\u5316\u671F\u95F4\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u4FEE\u6539\uFF1B\u672A\u8986\u76D6\u65B0\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u8BD5\u3002"
30560
- );
30561
- }
30562
- await rename3(temporary, target);
30563
- } finally {
30564
- await unlink(temporary).catch(() => void 0);
30565
- }
30566
- }
30567
- function candidateWidths(width) {
30568
- return [.../* @__PURE__ */ new Set([width, 2400, 2e3, 1600, 1280, 1080, 900, 720])].filter((candidate) => candidate > 0 && candidate <= width).sort((left, right) => right - left);
30569
- }
30570
- async function optimizedImage(source, targetBytes, metadata) {
30571
- const hasAlpha = metadata.hasAlpha;
30572
- for (const width of candidateWidths(metadata.width)) {
30573
- if (hasAlpha) {
30574
- const lossless = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
30575
- if (lossless.byteLength <= targetBytes) {
30576
- await requirePreservedAlpha(lossless);
30577
- return { alphaPreserved: true, bytes: lossless, extension: ".png" };
30578
- }
30579
- for (const colours of [256, 192, 128, 96, 64]) {
30580
- const quantized = await sharp2(source, {
30581
- failOn: "error",
30582
- limitInputPixels: 8e7
30583
- }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ colours, compressionLevel: 9, effort: 10, palette: true }).toBuffer();
30584
- if (quantized.byteLength <= targetBytes) {
30585
- await requirePreservedAlpha(quantized);
30586
- return { alphaPreserved: true, bytes: quantized, extension: ".png" };
30587
- }
30588
- }
30589
- continue;
30590
- }
30591
- for (const quality of [88, 82, 76, 70, 64, 58]) {
30592
- const jpeg = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).jpeg({ chromaSubsampling: "4:4:4", mozjpeg: true, progressive: true, quality }).toBuffer();
30593
- if (jpeg.byteLength <= targetBytes) {
30594
- return { alphaPreserved: true, bytes: jpeg, extension: ".jpg" };
30595
- }
30596
- }
30597
- }
30598
- throw new WechatCliError(
30599
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_LIMIT_UNREACHABLE",
30600
- `\u65E0\u6CD5\u5728\u4E0D\u4E22\u5931\u900F\u660E\u901A\u9053\u6216\u4F4E\u4E8E\u6700\u5C0F\u5C3A\u5BF8\u7B56\u7565\u7684\u524D\u63D0\u4E0B\u538B\u5230 ${targetBytes} bytes\uFF1B\u8BF7\u66F4\u6362\u6216\u88C1\u526A\u56FE\u7247\u3002`
30601
- );
30602
- }
30603
- async function optimizePublishingImages(input) {
30604
- const projectDirectory = path6.resolve(input.projectDirectory);
30605
- const visualPlanPath = path6.resolve(input.visualPlanFile);
30606
- if (!isWithin(visualPlanPath, projectDirectory)) {
30607
- throw new WechatCliError(
30608
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30609
- "\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5185\u5BB9\u9879\u76EE\u5185\u3002"
30610
- );
30611
- }
30612
- const projectMetadata = await lstat5(projectDirectory).catch(() => void 0);
30613
- const visualPlanMetadata = await lstat5(visualPlanPath).catch(() => void 0);
30614
- if (projectMetadata === void 0 || !projectMetadata.isDirectory() || projectMetadata.isSymbolicLink() || visualPlanMetadata === void 0 || !visualPlanMetadata.isFile() || visualPlanMetadata.isSymbolicLink() || visualPlanMetadata.size > VISUAL_PLAN_MAXIMUM_BYTES2) {
30615
- throw new WechatCliError(
30616
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30617
- "\u5185\u5BB9\u9879\u76EE\u548C\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u662F\u975E\u7B26\u53F7\u94FE\u63A5\u7684\u666E\u901A\u76EE\u5F55\u4E0E\u6587\u4EF6\u3002"
30618
- );
30619
- }
30620
- const realProjectDirectory = await realpath2(projectDirectory);
30621
- await safeRealPathWithin(visualPlanPath, realProjectDirectory, "\u89C6\u89C9\u8BA1\u5212");
30622
- const source = await readFile2(visualPlanPath, "utf8").catch(() => "");
30623
- if (Buffer.byteLength(source, "utf8") > VISUAL_PLAN_MAXIMUM_BYTES2) {
30624
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30625
- }
30626
- const match = VISUAL_PLAN_FRONTMATTER2.exec(source);
30627
- if (match?.[1] === void 0 || match[2] === void 0) {
30628
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30629
- }
30630
- let frontmatter2;
30631
- try {
30632
- frontmatter2 = recordValue2((0, import_yaml3.parse)(match[1], { maxAliasCount: 10, uniqueKeys: true }));
30633
- } catch {
30634
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30635
- }
30636
- if (frontmatter2 === void 0) {
30637
- throw new WechatCliError(
30638
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30639
- "\u89C6\u89C9\u8BA1\u5212\u7684 YAML \u5934\u65E0\u6CD5\u8BFB\u53D6\u3002"
30640
- );
30641
- }
30642
- const assetsRelative = frontmatter2["assetsDirectory"] === void 0 ? "assets" : portablePath(frontmatter2["assetsDirectory"], "assetsDirectory");
30643
- frontmatter2["assetsDirectory"] = assetsRelative;
30644
- frontmatter2["inlineImagesSupported"] = true;
30645
- const assetsDirectory = path6.resolve(projectDirectory, assetsRelative);
30646
- if (!isWithin(assetsDirectory, projectDirectory)) {
30647
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30648
- }
30649
- const assetsMetadata = await lstat5(assetsDirectory).catch(() => void 0);
30650
- if (assetsMetadata === void 0 || !assetsMetadata.isDirectory() || assetsMetadata.isSymbolicLink()) {
30651
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30652
- }
30653
- const realAssetsDirectory = await safeRealPathWithin(
30654
- assetsDirectory,
30655
- realProjectDirectory,
30656
- "\u7D20\u6750\u76EE\u5F55"
30657
- );
30658
- const inlineAssets = Array.isArray(frontmatter2["inlineAssets"]) ? frontmatter2["inlineAssets"] : [];
30659
- const entries = [
30660
- {
30661
- limit: COVER_IMAGE_LIMIT,
30662
- record: { path: frontmatter2["coverAsset"] },
30663
- role: "cover",
30664
- target: COVER_IMAGE_TARGET
30665
- },
30666
- ...inlineAssets.map((asset) => ({
30667
- limit: INLINE_IMAGE_LIMIT,
30668
- record: recordValue2(asset),
30669
- role: "inline",
30670
- target: INLINE_IMAGE_TARGET
30671
- }))
30672
- ];
30673
- const createdFiles = [];
30674
- const mutations = [];
30675
- let unchangedCount = 0;
30676
- let optimizedDirectory;
30677
- try {
30678
- for (const entry of entries) {
30679
- const assetPath = portablePath(entry.record?.["path"], `${entry.role} \u56FE\u7247\u8DEF\u5F84`);
30680
- const absolutePath = path6.resolve(projectDirectory, assetPath);
30681
- if (!isWithin(absolutePath, assetsDirectory)) {
30682
- throw new WechatCliError(
30683
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30684
- `${assetPath} \u4E0D\u5728\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u5185\u3002`
30685
- );
30686
- }
30687
- await safeRealPathWithin(absolutePath, realAssetsDirectory, `${assetPath} \u56FE\u7247`);
30688
- const bytes = await regularImage(absolutePath);
30689
- const metadata = await decodedImageMetadata(bytes, assetPath);
30690
- const beforeSha256 = sha256(bytes);
30691
- let finalPath = assetPath;
30692
- let finalBytes = bytes;
30693
- let alphaPreserved = true;
30694
- if (bytes.byteLength > entry.limit) {
30695
- const optimized = await optimizedImage(bytes, entry.target, metadata);
30696
- optimizedDirectory ??= await ensureSafeOptimizedDirectory(
30697
- assetsDirectory,
30698
- realAssetsDirectory
30699
- );
30700
- const baseName = safeDerivativeBase(path6.basename(assetPath, path6.extname(assetPath)));
30701
- const optimizedSha256 = sha256(optimized.bytes);
30702
- const optimizedName = `${baseName}-${entry.role}-${beforeSha256.slice(0, 12)}-${optimizedSha256.slice(0, 12)}${optimized.extension}`;
30703
- const optimizedAbsolute = path6.join(optimizedDirectory, optimizedName);
30704
- const created = await writeExclusiveOrVerify(optimizedAbsolute, optimized.bytes);
30705
- if (created) {
30706
- createdFiles.push(optimizedAbsolute);
30707
- }
30708
- finalPath = path6.relative(projectDirectory, optimizedAbsolute).split(path6.sep).join("/");
30709
- finalBytes = optimized.bytes;
30710
- alphaPreserved = optimized.alphaPreserved;
30711
- mutations.push({
30712
- alphaPreserved,
30713
- afterBytes: finalBytes.byteLength,
30714
- afterSha256: sha256(finalBytes),
30715
- beforeBytes: bytes.byteLength,
30716
- beforeSha256,
30717
- from: assetPath,
30718
- role: entry.role,
30719
- to: finalPath
30720
- });
30721
- } else {
30722
- unchangedCount += 1;
30723
- }
30724
- if (entry.role === "cover") {
30725
- frontmatter2["coverAsset"] = finalPath;
30726
- frontmatter2["coverSha256"] = sha256(finalBytes);
30727
- } else if (entry.record !== void 0) {
30728
- if (finalPath !== assetPath && entry.record["sourcePath"] === void 0) {
30729
- entry.record["sourcePath"] = assetPath;
30730
- }
30731
- entry.record["path"] = finalPath;
30732
- entry.record["sha256"] = sha256(finalBytes);
30733
- }
30734
- }
30735
- frontmatter2["inlineAssets"] = inlineAssets;
30736
- const normalizedSource = `---
30737
- ${(0, import_yaml3.stringify)(frontmatter2, { lineWidth: 0 }).trimEnd()}
30738
- ---
30739
- ${match[2]}`;
30740
- await writePlanAtomically(visualPlanPath, source, normalizedSource);
30741
- } catch (error) {
30742
- await Promise.all(createdFiles.map((target) => unlink(target).catch(() => void 0)));
30743
- if (error instanceof WechatCliError) {
30744
- throw error;
30745
- }
30746
- throw new WechatCliError(
30747
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_FAILED",
30748
- "\u56FE\u7247\u4F18\u5316\u672A\u5B8C\u6210\uFF1B\u539F\u56FE\u548C\u89C6\u89C9\u8BA1\u5212\u5747\u672A\u88AB\u8986\u76D6\u3002"
30749
- );
30750
- }
30751
- return {
30752
- command: "wechat.draft.optimize-images",
30753
- data: { mutations, unchangedCount, visualPlanPath },
30754
- ok: true
30755
- };
30756
- }
30757
-
30758
31216
  // apps/cli/src/wechat.ts
30759
31217
  var DEFAULT_API_BASE_URL = "https://content.deployxai.com";
30760
31218
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -30868,19 +31326,19 @@ function localPreviewOutputConflict() {
30868
31326
  async function ensureLocalPreviewDirectory(directory) {
30869
31327
  let metadata;
30870
31328
  try {
30871
- metadata = await lstat6(directory);
31329
+ metadata = await lstat5(directory);
30872
31330
  } catch (error) {
30873
31331
  if (fileSystemErrorCode(error) !== "ENOENT") {
30874
31332
  throw error;
30875
31333
  }
30876
31334
  try {
30877
- await mkdir4(directory, { mode: 448 });
31335
+ await mkdir5(directory, { mode: 448 });
30878
31336
  } catch (mkdirError) {
30879
31337
  if (fileSystemErrorCode(mkdirError) !== "EEXIST") {
30880
31338
  throw mkdirError;
30881
31339
  }
30882
31340
  }
30883
- metadata = await lstat6(directory);
31341
+ metadata = await lstat5(directory);
30884
31342
  }
30885
31343
  if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
30886
31344
  throw localPreviewOutputConflict();
@@ -30895,7 +31353,7 @@ async function safeLocalPreviewOutput(options) {
30895
31353
  throw localPreviewOutputConflict();
30896
31354
  }
30897
31355
  try {
30898
- const projectMetadata = await lstat6(projectDirectory);
31356
+ const projectMetadata = await lstat5(projectDirectory);
30899
31357
  if (!projectMetadata.isDirectory() || projectMetadata.isSymbolicLink()) {
30900
31358
  throw localPreviewOutputConflict();
30901
31359
  }
@@ -30906,7 +31364,7 @@ async function safeLocalPreviewOutput(options) {
30906
31364
  await ensureLocalPreviewDirectory(directory);
30907
31365
  }
30908
31366
  try {
30909
- const outputMetadata = await lstat6(outputFile);
31367
+ const outputMetadata = await lstat5(outputFile);
30910
31368
  if (!outputMetadata.isFile() || outputMetadata.isSymbolicLink()) {
30911
31369
  throw localPreviewOutputConflict();
30912
31370
  }
@@ -31047,17 +31505,17 @@ async function readProtectedStateFile(target, invalidCode) {
31047
31505
  }
31048
31506
  }
31049
31507
  async function saveSession(stateDirectory, session) {
31050
- await mkdir4(stateDirectory, { mode: 448, recursive: true });
31508
+ await mkdir5(stateDirectory, { mode: 448, recursive: true });
31051
31509
  await chmod(stateDirectory, 448);
31052
31510
  const target = sessionPath(stateDirectory);
31053
31511
  const temporary = `${target}.${process.pid}.tmp`;
31054
- await writeFile2(temporary, `${JSON.stringify(session)}
31512
+ await writeFile3(temporary, `${JSON.stringify(session)}
31055
31513
  `, {
31056
31514
  encoding: "utf8",
31057
31515
  mode: 384
31058
31516
  });
31059
31517
  await chmod(temporary, 384);
31060
- await rename4(temporary, target);
31518
+ await rename3(temporary, target);
31061
31519
  await chmod(target, 384);
31062
31520
  }
31063
31521
  async function readSessionValue(stateDirectory) {
@@ -31180,17 +31638,17 @@ async function loadOrCreateDeviceIdentity(stateDirectory) {
31180
31638
  publicKeyFingerprint: keyPair.publicKeyFingerprint,
31181
31639
  publicKeyPem: keyPair.publicKeyPem
31182
31640
  };
31183
- await mkdir4(stateDirectory, { mode: 448, recursive: true });
31641
+ await mkdir5(stateDirectory, { mode: 448, recursive: true });
31184
31642
  await chmod(stateDirectory, 448);
31185
31643
  const target = deviceIdentityPath(stateDirectory);
31186
31644
  const temporary = `${target}.${process.pid}.tmp`;
31187
- await writeFile2(temporary, `${JSON.stringify(identity)}
31645
+ await writeFile3(temporary, `${JSON.stringify(identity)}
31188
31646
  `, {
31189
31647
  encoding: "utf8",
31190
31648
  mode: 384
31191
31649
  });
31192
31650
  await chmod(temporary, 384);
31193
- await rename4(temporary, target);
31651
+ await rename3(temporary, target);
31194
31652
  await chmod(target, 384);
31195
31653
  return { created: true, identity };
31196
31654
  }
@@ -31811,6 +32269,17 @@ var WechatCli = class {
31811
32269
  );
31812
32270
  return result;
31813
32271
  }
32272
+ async publishingDraftDiagnostics(intentId, output) {
32273
+ const session = await loadSession(this.#stateDirectory);
32274
+ const { response } = await this.#authenticatedFetch(
32275
+ session,
32276
+ `${session.apiBaseUrl}/api/v1/draft-intents/${encodeURIComponent(intentId)}/diagnostics`
32277
+ );
32278
+ const result = publishingDraftAttentionDiagnosticsSchema.parse(await checkedJson(response));
32279
+ output.writeStdout(`${JSON.stringify({ diagnostics: result })}
32280
+ `);
32281
+ return result;
32282
+ }
31814
32283
  async reverifyDraft(intentId, output) {
31815
32284
  const session = await loadSession(this.#stateDirectory);
31816
32285
  const { response } = await this.#authenticatedFetch(
@@ -32255,28 +32724,36 @@ ${session.authorizationUrl}
32255
32724
  };
32256
32725
 
32257
32726
  // apps/cli/src/workflow-delivery.ts
32258
- import { createHash as createHash7 } from "node:crypto";
32727
+ import { createHash as createHash8 } from "node:crypto";
32259
32728
  import path9 from "node:path";
32260
32729
 
32261
32730
  // apps/cli/src/workflow.ts
32262
- import { lstat as lstat7, readFile as readFile3 } from "node:fs/promises";
32731
+ import { createHash as createHash7 } from "node:crypto";
32732
+ import { lstat as lstat6, readFile as readFile4 } from "node:fs/promises";
32263
32733
  import path8 from "node:path";
32264
32734
  var import_yaml4 = __toESM(require_dist(), 1);
32265
32735
  var WORKFLOW_STATE_FILE = ".dxc/workflow.json";
32266
32736
  var WORKFLOW_STATE_MAXIMUM_BYTES = 16 * 1024;
32267
32737
  var ARTIFACT_MAXIMUM_BYTES = 5 * 1024 * 1024;
32268
32738
  var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
32739
+ var WORK_ITEM_PATTERN = /^wi1_([A-Za-z0-9_-]+)\.([a-f0-9]{32})$/u;
32740
+ var SCAFFOLD_MARKER = "dxcScaffold: true";
32269
32741
  var WorkflowCliError = class extends Error {
32270
- constructor(code, message) {
32742
+ constructor(code, message, details) {
32271
32743
  super(message);
32272
32744
  this.code = code;
32745
+ this.details = details;
32273
32746
  this.name = "WorkflowCliError";
32274
32747
  }
32275
32748
  code;
32749
+ details;
32276
32750
  };
32277
32751
  function isRecord(value) {
32278
32752
  return typeof value === "object" && value !== null && !Array.isArray(value);
32279
32753
  }
32754
+ function sha2562(value) {
32755
+ return createHash7("sha256").update(value).digest("hex");
32756
+ }
32280
32757
  function workflowState(value, projectId) {
32281
32758
  if (!isRecord(value) || value["version"] !== 1 || value["projectId"] !== projectId) {
32282
32759
  return void 0;
@@ -32297,11 +32774,7 @@ function statePath(directory) {
32297
32774
  return path8.join(directory, WORKFLOW_STATE_FILE);
32298
32775
  }
32299
32776
  function projectSummary(location) {
32300
- return {
32301
- directory: location.directory,
32302
- projectId: location.manifest.projectId,
32303
- title: location.manifest.title
32304
- };
32777
+ return { projectId: location.manifest.projectId, title: location.manifest.title };
32305
32778
  }
32306
32779
  function nextStep(lastCompletedStep) {
32307
32780
  const nextIndex = lastCompletedStep === null ? 0 : CONTENT_ARTIFACT_KINDS.indexOf(lastCompletedStep) + 1;
@@ -32312,23 +32785,16 @@ function previousStep(step) {
32312
32785
  return index <= 0 ? null : CONTENT_ARTIFACT_KINDS[index - 1] ?? null;
32313
32786
  }
32314
32787
  function requestedStep(value) {
32315
- const step = value;
32316
- if (!CONTENT_ARTIFACT_KINDS.includes(step)) {
32788
+ if (!CONTENT_ARTIFACT_KINDS.includes(value)) {
32317
32789
  throw new WorkflowCliError(
32318
32790
  "DXC_WORKFLOW_STEP_INVALID",
32319
- `\u672A\u77E5\u9636\u6BB5 ${step}\u3002\u8BF7\u4F7F\u7528 ${CONTENT_ARTIFACT_KINDS.join("\u3001")}\u3002`
32791
+ `\u672A\u77E5\u9636\u6BB5 ${value}\u3002\u8BF7\u4F7F\u7528 ${CONTENT_ARTIFACT_KINDS.join("\u3001")}\u3002`
32320
32792
  );
32321
32793
  }
32322
- return step;
32323
- }
32324
- function inputPaths(directory, manifest, step) {
32325
- const inputs = CONTENT_STAGE_INPUTS[step];
32326
- return [...inputs.required, ...inputs.optional].map(
32327
- (kind) => path8.join(directory, manifest.artifacts[kind])
32328
- );
32794
+ return value;
32329
32795
  }
32330
32796
  function requiresStageConfirmation(policy, step) {
32331
- if (step === null || step === "delivery" || policy === "auto-until-delivery") {
32797
+ if (step === "delivery" || policy === "auto-until-delivery") {
32332
32798
  return false;
32333
32799
  }
32334
32800
  if (policy === "review-every-stage") {
@@ -32348,71 +32814,264 @@ function stageLabel(step) {
32348
32814
  "visual-plan": "\u56FE\u7247\u548C\u89C6\u89C9\u7ED3\u679C"
32349
32815
  }[step];
32350
32816
  }
32351
- function workItem(location, state, confirmationPolicy) {
32352
- const step = nextStep(state.lastCompletedStep);
32353
- return {
32354
- confirmationPolicy,
32355
- done: step === null,
32356
- inputs: step === null ? [] : inputPaths(location.directory, location.manifest, step),
32357
- output: step === null ? null : path8.join(location.directory, location.manifest.artifacts[step]),
32358
- project: projectSummary(location),
32359
- requiresConfirmation: requiresStageConfirmation(confirmationPolicy, step),
32360
- step
32361
- };
32817
+ function frontmatter(source) {
32818
+ const yaml = FRONTMATTER.exec(source)?.[1];
32819
+ if (yaml === void 0) {
32820
+ return {};
32821
+ }
32822
+ try {
32823
+ const value = (0, import_yaml4.parse)(yaml, { maxAliasCount: 10, uniqueKeys: true });
32824
+ return isRecord(value) ? value : {};
32825
+ } catch {
32826
+ return {};
32827
+ }
32362
32828
  }
32363
- async function readArtifact(directory, relativePath) {
32364
- const target = path8.resolve(directory, relativePath);
32365
- const relative = path8.relative(directory, target);
32829
+ function selectedTitle(value) {
32830
+ const title = value["selectedTitle"];
32831
+ return typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0;
32832
+ }
32833
+ function artifactPath(location, kind) {
32834
+ return path8.join(location.directory, location.manifest.artifacts[kind]);
32835
+ }
32836
+ async function readArtifact(location, kind) {
32837
+ const target = path8.resolve(location.directory, location.manifest.artifacts[kind]);
32838
+ const relative = path8.relative(location.directory, target);
32366
32839
  if (relative.startsWith("..") || path8.isAbsolute(relative)) {
32367
- throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u6B65\u9AA4\u4EA7\u7269\u8DEF\u5F84\u4E0D\u5728\u9879\u76EE\u76EE\u5F55\u4E2D\u3002");
32840
+ throw new WorkflowCliError(
32841
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
32842
+ `${kind} \u4EA7\u7269\u8DEF\u5F84\u4E0D\u5728\u9879\u76EE\u76EE\u5F55\u4E2D\u3002`,
32843
+ { path: target, stage: kind }
32844
+ );
32368
32845
  }
32369
32846
  try {
32370
- const metadata = await lstat7(target);
32847
+ const metadata = await lstat6(target);
32371
32848
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0 || metadata.size > ARTIFACT_MAXIMUM_BYTES) {
32372
32849
  throw new WorkflowCliError(
32373
32850
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32374
- "\u5F53\u524D\u6B65\u9AA4\u7684\u4EA7\u7269\u4E3A\u7A7A\u3001\u8FC7\u5927\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002"
32851
+ `${kind} \u4EA7\u7269\u4E3A\u7A7A\u3001\u8FC7\u5927\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002`,
32852
+ { path: target, stage: kind }
32375
32853
  );
32376
32854
  }
32377
- return await readFile3(target, "utf8");
32855
+ return await readFile4(target, "utf8");
32378
32856
  } catch (error) {
32379
32857
  if (error instanceof WorkflowCliError) {
32380
32858
  throw error;
32381
32859
  }
32382
- throw new WorkflowCliError(
32383
- "DXC_WORKFLOW_ARTIFACT_MISSING",
32384
- `\u5F53\u524D\u6B65\u9AA4\u5C1A\u672A\u5199\u5165 ${relativePath}\u3002`
32385
- );
32860
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_MISSING", `\u7F3A\u5C11\u547D\u540D\u8F93\u5165\u6216\u4EA7\u7269 ${kind}\u3002`, {
32861
+ path: target,
32862
+ stage: kind
32863
+ });
32386
32864
  }
32387
32865
  }
32388
- function frontmatter(source) {
32389
- const yaml = FRONTMATTER.exec(source)?.[1];
32390
- if (yaml === void 0) {
32391
- return {};
32866
+ async function artifactAvailability(location, kind) {
32867
+ const target = artifactPath(location, kind);
32868
+ try {
32869
+ const metadata = await lstat6(target);
32870
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size <= 0) {
32871
+ return { availability: "missing" };
32872
+ }
32873
+ const source = await readFile4(target);
32874
+ return { availability: "present", hash: sha2562(source), path: target };
32875
+ } catch (error) {
32876
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
32877
+ return { availability: "missing" };
32878
+ }
32879
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u547D\u540D\u8F93\u5165 ${kind}\u3002`, {
32880
+ path: target,
32881
+ stage: kind
32882
+ });
32883
+ }
32884
+ }
32885
+ function stageScaffold(stage, title) {
32886
+ switch (stage) {
32887
+ case "article":
32888
+ return `---
32889
+ title: ""
32890
+ author: ""
32891
+ digest: ""
32892
+ tags: []
32893
+ ${SCAFFOLD_MARKER}
32894
+ ---
32895
+
32896
+ # ${title}
32897
+ `;
32898
+ case "titles":
32899
+ return `---
32900
+ selectedTitle: ""
32901
+ ${SCAFFOLD_MARKER}
32902
+ ---
32903
+
32904
+ # \u6807\u9898\u5019\u9009
32905
+ `;
32906
+ case "visual-plan":
32907
+ return `---
32908
+ cover:
32909
+ purpose: ""
32910
+ inline: []
32911
+ ${SCAFFOLD_MARKER}
32912
+ ---
32913
+
32914
+ # \u89C6\u89C9\u8BA1\u5212
32915
+ `;
32916
+ case "quality-review":
32917
+ return `---
32918
+ verdict: block
32919
+ ${SCAFFOLD_MARKER}
32920
+ ---
32921
+
32922
+ # \u5BA1\u6821\u7ED3\u679C
32923
+ `;
32924
+ case "delivery":
32925
+ return "# \u5FAE\u4FE1\u8349\u7A3F\u4EA4\u4ED8\n";
32926
+ default:
32927
+ return `---
32928
+ ${SCAFFOLD_MARKER}
32929
+ ---
32930
+
32931
+ # ${stageLabel(stage)}
32932
+ `;
32392
32933
  }
32934
+ }
32935
+ async function ensureStageScaffold(location, stage) {
32936
+ if (stage === "delivery") {
32937
+ return "not-applicable";
32938
+ }
32939
+ const target = artifactPath(location, stage);
32393
32940
  try {
32394
- const value = (0, import_yaml4.parse)(yaml, { maxAliasCount: 10, uniqueKeys: true });
32395
- return isRecord(value) ? value : {};
32396
- } catch {
32397
- return {};
32941
+ const metadata = await lstat6(target);
32942
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
32943
+ throw new WorkflowCliError(
32944
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
32945
+ `${stage} \u8F93\u51FA\u4F4D\u7F6E\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002`,
32946
+ { path: target, stage }
32947
+ );
32948
+ }
32949
+ if (metadata.size > 0) {
32950
+ return "existing";
32951
+ }
32952
+ } catch (error) {
32953
+ if (error instanceof WorkflowCliError || !(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) {
32954
+ throw error;
32955
+ }
32398
32956
  }
32957
+ await writePrivateBuffer(target, Buffer.from(stageScaffold(stage, location.manifest.title)));
32958
+ return "created";
32399
32959
  }
32400
- function selectedTitle(value) {
32401
- const title = value["selectedTitle"];
32402
- return typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0;
32960
+ async function workflowSections(location, stage) {
32961
+ if (stage !== "visual-plan") {
32962
+ return void 0;
32963
+ }
32964
+ const article = await artifactAvailability(location, "article");
32965
+ if (article.availability === "missing") {
32966
+ return [];
32967
+ }
32968
+ return parseWorkflowArticleSections(await readFile4(article.path, "utf8"));
32969
+ }
32970
+ function workflowConstraints(stage) {
32971
+ if (stage === "article" || stage === "titles") {
32972
+ return {
32973
+ authorMaxCharacters: WECHAT_AUTHOR_CHARACTER_LIMIT,
32974
+ digestMaxCharacters: WECHAT_DIGEST_CHARACTER_LIMIT,
32975
+ titleMaxCharacters: WECHAT_TITLE_CHARACTER_LIMIT
32976
+ };
32977
+ }
32978
+ if (stage === "visual-plan" || stage === "delivery") {
32979
+ return {
32980
+ coverMaxBytes: PUBLISHING_COVER_MAXIMUM_BYTES,
32981
+ inlineImageMaxBytes: PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES,
32982
+ inlineImageMaxCount: PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT,
32983
+ supportedImageTypes: ["image/jpeg", "image/png"]
32984
+ };
32985
+ }
32986
+ return {};
32987
+ }
32988
+ async function namedResources(location, stage, scaffold) {
32989
+ const definition = CONTENT_STAGE_DEFINITIONS[stage];
32990
+ const inputs = {};
32991
+ for (const kind of [...definition.inputs.required, ...definition.inputs.optional]) {
32992
+ const availability = await artifactAvailability(location, kind);
32993
+ inputs[kind] = availability.availability === "missing" ? { availability: "missing" } : { availability: "present", path: availability.path };
32994
+ }
32995
+ return {
32996
+ inputs,
32997
+ output: { path: artifactPath(location, definition.output), scaffold }
32998
+ };
32999
+ }
33000
+ async function inputFingerprint(location, stage) {
33001
+ const definition = CONTENT_STAGE_DEFINITIONS[stage];
33002
+ const inputs = {};
33003
+ for (const kind of [...definition.inputs.required, ...definition.inputs.optional]) {
33004
+ const availability = await artifactAvailability(location, kind);
33005
+ inputs[kind] = availability.availability === "missing" ? { availability: "missing" } : { availability: "present", hash: availability.hash };
33006
+ }
33007
+ if (stage === "delivery") {
33008
+ inputs["visual-assets"] = await workflowVisualAssetsFingerprint(location.directory);
33009
+ }
33010
+ return sha2562(JSON.stringify(inputs));
32403
33011
  }
32404
- async function validateStageResult(step, source, location) {
32405
- if (source.trim().length === 0) {
32406
- throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u5F53\u524D\u6B65\u9AA4\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5185\u5BB9\u3002");
33012
+ function cursorFingerprint(state) {
33013
+ return sha2562(JSON.stringify(state));
33014
+ }
33015
+ function encodeWorkItem(payload) {
33016
+ const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
33017
+ return `wi1_${encoded}.${sha2562(encoded).slice(0, 32)}`;
33018
+ }
33019
+ function decodeWorkItem(workItemId) {
33020
+ const match = WORK_ITEM_PATTERN.exec(workItemId);
33021
+ if (match === null || match[1] === void 0 || match[2] === void 0) {
33022
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u683C\u5F0F\u65E0\u6548\u3002");
33023
+ }
33024
+ if (sha2562(match[1]).slice(0, 32) !== match[2]) {
33025
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25\u3002");
32407
33026
  }
32408
- if (step === "article") {
33027
+ try {
33028
+ const value = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
33029
+ if (!isRecord(value) || value["version"] !== 1 || typeof value["projectId"] !== "string" || typeof value["stage"] !== "string" || !CONTENT_ARTIFACT_KINDS.includes(value["stage"]) || typeof value["action"] !== "string" || ![
33030
+ "completed",
33031
+ "delivery-confirm",
33032
+ "delivery-preview",
33033
+ "stage",
33034
+ "stage-confirm",
33035
+ "visual-assets"
33036
+ ].includes(value["action"]) || typeof value["cursorFingerprint"] !== "string" || typeof value["inputFingerprint"] !== "string" || value["outputFingerprint"] !== void 0 && typeof value["outputFingerprint"] !== "string" || value["bindingFingerprint"] !== void 0 && typeof value["bindingFingerprint"] !== "string") {
33037
+ throw new TypeError("invalid work item payload");
33038
+ }
33039
+ return value;
33040
+ } catch (error) {
33041
+ if (error instanceof WorkflowCliError) {
33042
+ throw error;
33043
+ }
33044
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u8F7D\u8377\u65E0\u6548\u3002");
33045
+ }
33046
+ }
33047
+ async function validateRequiredInputs(location, stage) {
33048
+ for (const kind of CONTENT_STAGE_DEFINITIONS[stage].inputs.required) {
33049
+ if ((await artifactAvailability(location, kind)).availability === "missing") {
33050
+ throw new WorkflowCliError(
33051
+ "DXC_WORKFLOW_INPUT_MISSING",
33052
+ `\u5F53\u524D ${stage} \u9636\u6BB5\u7F3A\u5C11\u547D\u540D\u8F93\u5165 ${kind}\u3002`,
33053
+ { input: kind, stage }
33054
+ );
33055
+ }
33056
+ }
33057
+ }
33058
+ async function validateStageResult(stage, source, location) {
33059
+ if (source.trim().length === 0 || source.includes(SCAFFOLD_MARKER)) {
33060
+ throw new WorkflowCliError(
33061
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
33062
+ `${stage} \u9636\u6BB5\u4ECD\u662F\u672A\u586B\u5199\u7684 scaffold\uFF08\u811A\u624B\u67B6\uFF09\u3002`,
33063
+ { path: artifactPath(location, stage), stage }
33064
+ );
33065
+ }
33066
+ if (stage === "article") {
32409
33067
  let article;
32410
33068
  try {
32411
33069
  article = parseArticleDocument(source);
32412
33070
  } catch {
32413
33071
  throw new WorkflowCliError(
32414
33072
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32415
- "\u6B63\u6587\u9700\u8981\u5305\u542B\u53EF\u53D1\u5E03\u7684\u6807\u9898\u3001\u4F5C\u8005\u548C Markdown \u6B63\u6587\u3002"
33073
+ "\u6B63\u6587\u9700\u8981\u5305\u542B\u53EF\u53D1\u5E03\u7684\u6807\u9898\u3001\u4F5C\u8005\u548C Markdown \u6B63\u6587\u3002",
33074
+ { path: artifactPath(location, stage), stage }
32416
33075
  );
32417
33076
  }
32418
33077
  const lengths = {
@@ -32424,56 +33083,41 @@ async function validateStageResult(step, source, location) {
32424
33083
  if (invalid !== void 0) {
32425
33084
  throw new WorkflowCliError(
32426
33085
  "DXC_WORKFLOW_ARTICLE_FIELD_INVALID",
32427
- `${invalid} \u4E2A Unicode \u5B57\u7B26\uFF1B\u8BF7\u5728\u6B63\u6587\u9636\u6BB5\u4FEE\u6B63\u540E\u518D\u7EE7\u7EED\u3002`
33086
+ `${invalid} \u4E2A Unicode \u5B57\u7B26\uFF1B\u8BF7\u5728\u6B63\u6587\u9636\u6BB5\u4FEE\u6B63\u540E\u518D\u7EE7\u7EED\u3002`,
33087
+ { path: artifactPath(location, stage), stage }
32428
33088
  );
32429
33089
  }
32430
33090
  }
32431
- if (step === "titles") {
33091
+ if (stage === "titles") {
32432
33092
  const title = selectedTitle(frontmatter(source));
32433
- if (title === void 0 || [...title].length > 32) {
33093
+ if (title === void 0 || [...title].length > WECHAT_TITLE_CHARACTER_LIMIT) {
32434
33094
  throw new WorkflowCliError(
32435
33095
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32436
- "\u6807\u9898\u6B65\u9AA4\u9700\u8981\u5728 selectedTitle \u4E2D\u4FDD\u5B58\u4E00\u4E2A\u4E0D\u8D85\u8FC7 32 \u5B57\u7684\u6700\u7EC8\u6807\u9898\u3002"
33096
+ `\u6807\u9898\u6B65\u9AA4\u9700\u8981\u5728 selectedTitle \u4E2D\u4FDD\u5B58\u4E00\u4E2A\u4E0D\u8D85\u8FC7 ${WECHAT_TITLE_CHARACTER_LIMIT} \u5B57\u7684\u6700\u7EC8\u6807\u9898\u3002`,
33097
+ { path: artifactPath(location, stage), stage }
32437
33098
  );
32438
33099
  }
32439
33100
  }
32440
- if (step === "visual-plan") {
32441
- const value = frontmatter(source);
32442
- if (typeof value["coverAsset"] !== "string") {
32443
- throw new WorkflowCliError(
32444
- "DXC_WORKFLOW_ARTIFACT_INVALID",
32445
- "\u89C6\u89C9\u6B65\u9AA4\u9700\u8981\u4FDD\u5B58\u771F\u5B9E\u5C01\u9762\u8DEF\u5F84 coverAsset\u3002"
32446
- );
32447
- }
32448
- const articleSource = await readArtifact(
32449
- location.directory,
32450
- location.manifest.artifacts.article
32451
- );
32452
- try {
32453
- validateVisualPlanPlacementAnchors(articleSource, source);
32454
- } catch (error) {
32455
- if (error instanceof WechatCliError) {
32456
- throw new WorkflowCliError(error.code, error.guidance ?? error.message);
32457
- }
32458
- throw error;
32459
- }
33101
+ if (stage === "visual-plan") {
33102
+ const articleSource = await readArtifact(location, "article");
33103
+ validateWorkflowSemanticVisualPlan({
33104
+ sections: parseWorkflowArticleSections(articleSource),
33105
+ source
33106
+ });
32460
33107
  }
32461
- if (step === "quality-review" && frontmatter(source)["verdict"] !== "pass") {
33108
+ if (stage === "quality-review" && frontmatter(source)["verdict"] !== "pass") {
32462
33109
  throw new WorkflowCliError(
32463
33110
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32464
- "\u5BA1\u6821\u6B65\u9AA4\u53EA\u6709 verdict: pass \u65F6\u624D\u80FD\u7EE7\u7EED\u4EA4\u4ED8\u3002"
33111
+ "\u5BA1\u6821\u6B65\u9AA4\u53EA\u6709 verdict: pass \u65F6\u624D\u80FD\u7EE7\u7EED\u4EA4\u4ED8\u3002",
33112
+ { path: artifactPath(location, stage), stage }
32465
33113
  );
32466
33114
  }
32467
33115
  }
32468
33116
  var WorkflowCli = class {
32469
- #currentDirectory;
32470
- #homeDirectory;
32471
33117
  #now;
32472
33118
  #profile;
32473
33119
  #projects;
32474
33120
  constructor(input) {
32475
- this.#currentDirectory = input.currentDirectory;
32476
- this.#homeDirectory = input.homeDirectory;
32477
33121
  this.#now = input.now ?? (() => /* @__PURE__ */ new Date());
32478
33122
  this.#profile = new ProfileCli({
32479
33123
  currentDirectory: input.currentDirectory,
@@ -32490,15 +33134,6 @@ var WorkflowCli = class {
32490
33134
  const status = await this.#profile.status();
32491
33135
  return status.data.configured ? status.data.profile.user.confirmationPolicy : "guided";
32492
33136
  }
32493
- async #locate(directory, projectId) {
32494
- if (directory !== void 0 && projectId !== void 0) {
32495
- throw new WorkflowCliError(
32496
- "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
32497
- "\u9879\u76EE\u76EE\u5F55\u548C\u9879\u76EE ID \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A\u3002"
32498
- );
32499
- }
32500
- return projectId === void 0 ? this.#projects.locate(directory) : this.#projects.locateByProjectId(projectId);
32501
- }
32502
33137
  async #loadState(location, persistMigration = true, migrationUpdatedAt = this.#now().toISOString()) {
32503
33138
  try {
32504
33139
  const raw = await readPrivateFile(
@@ -32553,79 +33188,328 @@ var WorkflowCli = class {
32553
33188
  }
32554
33189
  return migrated;
32555
33190
  }
33191
+ #nextTimestamp(previous) {
33192
+ const current = this.#now().getTime();
33193
+ const previousTime = previous === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(previous);
33194
+ return new Date(Math.max(current, previousTime + 1)).toISOString();
33195
+ }
33196
+ async #basePayload(location, state, stage, action) {
33197
+ return {
33198
+ action,
33199
+ cursorFingerprint: cursorFingerprint(state),
33200
+ inputFingerprint: await inputFingerprint(location, stage),
33201
+ projectId: location.manifest.projectId,
33202
+ stage,
33203
+ version: 1
33204
+ };
33205
+ }
33206
+ async #stagePayload(location, state) {
33207
+ const stage = nextStep(state.lastCompletedStep);
33208
+ if (stage === null) {
33209
+ return this.#basePayload(location, state, "delivery", "completed");
33210
+ }
33211
+ return this.#basePayload(
33212
+ location,
33213
+ state,
33214
+ stage,
33215
+ stage === "delivery" ? "delivery-preview" : "stage"
33216
+ );
33217
+ }
33218
+ async #confirmationPayload(location, state, stage) {
33219
+ const source = await readArtifact(location, stage);
33220
+ await validateStageResult(stage, source, location);
33221
+ const base = await this.#basePayload(location, state, stage, "stage-confirm");
33222
+ let bindingFingerprint;
33223
+ if (stage === "visual-plan") {
33224
+ bindingFingerprint = await workflowVisualAssetsFingerprint(location.directory);
33225
+ }
33226
+ return {
33227
+ ...base,
33228
+ ...bindingFingerprint === void 0 ? {} : { bindingFingerprint },
33229
+ outputFingerprint: sha2562(source)
33230
+ };
33231
+ }
33232
+ async #visualAssetsPayload(location, state) {
33233
+ const source = await readArtifact(location, "visual-plan");
33234
+ await validateStageResult("visual-plan", source, location);
33235
+ return {
33236
+ ...await this.#basePayload(location, state, "visual-plan", "visual-assets"),
33237
+ outputFingerprint: sha2562(source)
33238
+ };
33239
+ }
33240
+ async #currentWorkItemId(location, state) {
33241
+ return encodeWorkItem(await this.#stagePayload(location, state));
33242
+ }
33243
+ async #stale(location, state) {
33244
+ throw new WorkflowCliError(
33245
+ "DXC_WORK_ITEM_STALE",
33246
+ "\u5DE5\u4F5C\u9879\u5DF2\u8FC7\u671F\uFF1B\u8BF7\u4F7F\u7528\u5F53\u524D\u5DE5\u4F5C\u9879\u7EE7\u7EED\uFF0C\u4E0D\u4F1A\u66FF\u4F60\u9759\u9ED8\u5B8C\u6210\u65E7\u52A8\u4F5C\u3002",
33247
+ { workItemId: await this.#currentWorkItemId(location, state) }
33248
+ );
33249
+ }
33250
+ async #resolve(workItemId) {
33251
+ const payload = decodeWorkItem(workItemId);
33252
+ let location;
33253
+ try {
33254
+ location = await this.#projects.locateByProjectId(payload.projectId);
33255
+ } catch {
33256
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879\u6307\u5411\u7684\u9879\u76EE\u4E0D\u5B58\u5728\u6216\u65E0\u6548\u3002");
33257
+ }
33258
+ const state = await this.#loadState(location);
33259
+ const stage = nextStep(state.lastCompletedStep);
33260
+ if (stage === null) {
33261
+ const expected2 = encodeWorkItem(await this.#stagePayload(location, state));
33262
+ if (payload.action !== "completed" || expected2 !== workItemId) {
33263
+ return this.#stale(location, state);
33264
+ }
33265
+ return { location, payload, state };
33266
+ }
33267
+ if (payload.stage !== stage) {
33268
+ return this.#stale(location, state);
33269
+ }
33270
+ const currentBase = await this.#basePayload(location, state, stage, payload.action);
33271
+ if (payload.cursorFingerprint !== currentBase.cursorFingerprint || payload.inputFingerprint !== currentBase.inputFingerprint) {
33272
+ return this.#stale(location, state);
33273
+ }
33274
+ let expected;
33275
+ switch (payload.action) {
33276
+ case "stage":
33277
+ case "delivery-preview":
33278
+ expected = await this.#stagePayload(location, state);
33279
+ break;
33280
+ case "stage-confirm":
33281
+ expected = await this.#confirmationPayload(location, state, stage);
33282
+ break;
33283
+ case "visual-assets":
33284
+ if (stage !== "visual-plan") {
33285
+ return this.#stale(location, state);
33286
+ }
33287
+ expected = await this.#visualAssetsPayload(location, state);
33288
+ break;
33289
+ case "delivery-confirm": {
33290
+ if (stage !== "delivery") {
33291
+ return this.#stale(location, state);
33292
+ }
33293
+ expected = {
33294
+ ...await this.#basePayload(location, state, stage, "delivery-confirm"),
33295
+ ...payload.bindingFingerprint === void 0 ? {} : { bindingFingerprint: payload.bindingFingerprint }
33296
+ };
33297
+ break;
33298
+ }
33299
+ case "completed":
33300
+ return this.#stale(location, state);
33301
+ }
33302
+ if (encodeWorkItem(expected) !== workItemId) {
33303
+ return this.#stale(location, state);
33304
+ }
33305
+ return { location, payload, state };
33306
+ }
33307
+ async #workItem(resolved, rich, actionOverride) {
33308
+ const { location, payload } = resolved;
33309
+ const definition = CONTENT_STAGE_DEFINITIONS[payload.stage];
33310
+ const workItemId = encodeWorkItem(payload);
33311
+ let nextAction;
33312
+ switch (payload.action) {
33313
+ case "stage":
33314
+ nextAction = { type: "run-stage" };
33315
+ break;
33316
+ case "visual-assets": {
33317
+ const article = await readArtifact(location, "article");
33318
+ const visualPlan = await readArtifact(location, "visual-plan");
33319
+ const sections2 = parseWorkflowArticleSections(article);
33320
+ const assetsDirectory = path8.join(location.directory, location.manifest.assetsDirectory);
33321
+ await ensurePrivateDirectory(path8.join(assetsDirectory, "visuals"));
33322
+ nextAction = {
33323
+ assets: [
33324
+ ...workflowVisualAssetAssignments({
33325
+ assetsDirectory,
33326
+ projectDirectory: location.directory,
33327
+ sections: sections2,
33328
+ visualPlanSource: visualPlan
33329
+ })
33330
+ ],
33331
+ type: "generate-assets"
33332
+ };
33333
+ break;
33334
+ }
33335
+ case "stage-confirm":
33336
+ nextAction = {
33337
+ presentation: { path: artifactPath(location, payload.stage), type: "artifact" },
33338
+ type: "confirm-stage"
33339
+ };
33340
+ break;
33341
+ case "delivery-preview":
33342
+ nextAction = { type: "preview" };
33343
+ break;
33344
+ case "delivery-confirm":
33345
+ nextAction = actionOverride ?? {
33346
+ expiresAt: (/* @__PURE__ */ new Date(0)).toISOString(),
33347
+ type: "confirm-delivery"
33348
+ };
33349
+ break;
33350
+ case "completed":
33351
+ nextAction = { type: "completed" };
33352
+ break;
33353
+ }
33354
+ const base = {
33355
+ nextAction: actionOverride ?? nextAction,
33356
+ skill: definition.skill,
33357
+ stage: payload.stage,
33358
+ workItemId
33359
+ };
33360
+ if (!rich || payload.action === "completed") {
33361
+ return base;
33362
+ }
33363
+ const scaffold = payload.action === "stage" ? await ensureStageScaffold(location, payload.stage) : payload.stage === "delivery" ? "not-applicable" : "existing";
33364
+ const sections = await workflowSections(location, payload.stage);
33365
+ return {
33366
+ ...base,
33367
+ constraints: workflowConstraints(payload.stage),
33368
+ resources: await namedResources(location, payload.stage, scaffold),
33369
+ ...sections === void 0 ? {} : { sections: [...sections] }
33370
+ };
33371
+ }
33372
+ async #result(location, payload, rich = false, actionOverride) {
33373
+ return {
33374
+ command: "workflow.next",
33375
+ data: {
33376
+ project: projectSummary(location),
33377
+ workItem: await this.#workItem(
33378
+ { location, payload, state: await this.#loadState(location) },
33379
+ rich,
33380
+ actionOverride
33381
+ )
33382
+ },
33383
+ ok: true
33384
+ };
33385
+ }
32556
33386
  async start(options) {
32557
- if (options.directory !== void 0 && options.projectId !== void 0) {
33387
+ if (options.projectId !== void 0 && (options.directory !== void 0 || options.title !== void 0)) {
32558
33388
  throw new WorkflowCliError(
32559
33389
  "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
32560
- "\u9879\u76EE\u76EE\u5F55\u548C\u9879\u76EE ID \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A\u3002"
33390
+ "\u5DF2\u6709\u9879\u76EE\u53EA\u80FD\u4F7F\u7528 --project\uFF0C\u4E0D\u63A5\u53D7\u6807\u9898\u6216\u76EE\u5F55\u3002"
32561
33391
  );
32562
33392
  }
32563
- const directory = options.directory === void 0 ? void 0 : resolveUserPath({
32564
- currentDirectory: this.#currentDirectory,
32565
- homeDirectory: this.#homeDirectory,
32566
- input: options.directory
32567
- });
32568
33393
  const requested = options.from === void 0 ? void 0 : requestedStep(options.from);
32569
33394
  let location;
33395
+ let existingState;
32570
33396
  if (options.projectId !== void 0) {
32571
33397
  location = await this.#projects.locateByProjectId(options.projectId);
32572
- } else if (directory !== void 0) {
32573
- try {
32574
- location = await this.#projects.locate(directory);
32575
- } catch (error) {
32576
- if (!(error instanceof ProjectCliError) || error.code !== "DXC_PROJECT_NOT_FOUND") {
32577
- throw error;
32578
- }
32579
- if (options.title === void 0 || options.title.trim().length === 0) {
32580
- throw new WorkflowCliError(
32581
- "DXC_WORKFLOW_TITLE_REQUIRED",
32582
- "\u521B\u5EFA\u65B0\u9879\u76EE\u65F6\u9700\u8981\u63D0\u4F9B\u6587\u7AE0\u4E3B\u9898\u3002"
32583
- );
32584
- }
32585
- const initialized = await this.#projects.initialize(directory, options.title);
32586
- location = { directory: initialized.data.directory, manifest: initialized.data.manifest };
33398
+ existingState = await this.#loadState(location);
33399
+ if (requested === void 0) {
33400
+ return this.#result(location, await this.#stagePayload(location, existingState));
32587
33401
  }
32588
33402
  } else {
32589
33403
  if (options.title === void 0 || options.title.trim().length === 0) {
32590
33404
  throw new WorkflowCliError("DXC_WORKFLOW_TITLE_REQUIRED", "\u521B\u5EFA\u65B0\u9879\u76EE\u65F6\u9700\u8981\u63D0\u4F9B\u6587\u7AE0\u4E3B\u9898\u3002");
32591
33405
  }
32592
- const initialized = await this.#projects.initialize(void 0, options.title);
33406
+ const initialized = await this.#projects.initialize(options.directory, options.title);
32593
33407
  location = { directory: initialized.data.directory, manifest: initialized.data.manifest };
32594
33408
  }
32595
- if (requested === void 0 && options.projectId !== void 0) {
32596
- const state2 = await this.#loadState(location);
32597
- const confirmationPolicy2 = await this.#confirmationPolicy();
32598
- return {
32599
- command: "workflow.next",
32600
- data: { match: "ready", work: workItem(location, state2, confirmationPolicy2) },
32601
- ok: true
32602
- };
32603
- }
32604
- if (requested === void 0 && directory !== void 0) {
32605
- const state2 = await this.#loadState(location);
32606
- if (state2.lastCompletedStep !== null) {
32607
- const confirmationPolicy2 = await this.#confirmationPolicy();
32608
- return {
32609
- command: "workflow.next",
32610
- data: { match: "ready", work: workItem(location, state2, confirmationPolicy2) },
32611
- ok: true
32612
- };
32613
- }
32614
- }
32615
- const step = requested ?? "research";
33409
+ const stage = requested ?? "research";
32616
33410
  const state = {
32617
- lastCompletedStep: previousStep(step),
33411
+ lastCompletedStep: previousStep(stage),
32618
33412
  projectId: location.manifest.projectId,
32619
- updatedAt: this.#now().toISOString(),
33413
+ updatedAt: this.#nextTimestamp(existingState?.updatedAt),
32620
33414
  version: 1
32621
33415
  };
32622
33416
  await writePrivateJson(statePath(location.directory), state);
32623
- const confirmationPolicy = await this.#confirmationPolicy();
32624
- return {
32625
- command: "workflow.next",
32626
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32627
- ok: true
33417
+ return this.#result(location, await this.#stagePayload(location, state));
33418
+ }
33419
+ async next(options) {
33420
+ if (options.projectId === void 0 === (options.workItemId === void 0)) {
33421
+ throw new WorkflowCliError(
33422
+ "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
33423
+ "workflow next \u5FC5\u987B\u4E14\u53EA\u80FD\u6307\u5B9A --project \u6216 --work-item\u3002"
33424
+ );
33425
+ }
33426
+ if (options.workItemId !== void 0) {
33427
+ const resolved = await this.#resolve(options.workItemId);
33428
+ return this.#result(resolved.location, resolved.payload, true);
33429
+ }
33430
+ const location = await this.#projects.locateByProjectId(options.projectId);
33431
+ const state = await this.#loadState(location);
33432
+ return this.#result(location, await this.#stagePayload(location, state));
33433
+ }
33434
+ async complete(workItemId, confirmed = false) {
33435
+ const resolved = await this.#resolve(workItemId);
33436
+ const { location, payload, state } = resolved;
33437
+ if (payload.action === "completed") {
33438
+ return this.#result(location, payload);
33439
+ }
33440
+ if (payload.stage === "delivery" || payload.action.startsWith("delivery-")) {
33441
+ throw new WorkflowCliError(
33442
+ "DXC_WORKFLOW_DELIVERY_REQUIRED",
33443
+ "\u5185\u5BB9\u5DF2\u7ECF\u51C6\u5907\u5B8C\u6210\uFF1B\u8BF7\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u5E76\u786E\u8BA4\u4EA4\u4ED8\u3002"
33444
+ );
33445
+ }
33446
+ if (payload.action === "stage-confirm") {
33447
+ if (!confirmed) {
33448
+ throw new WorkflowCliError(
33449
+ "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
33450
+ `\u8BF7\u660E\u786E\u786E\u8BA4\u91C7\u7528\u5F53\u524D${stageLabel(payload.stage)}\u3002`
33451
+ );
33452
+ }
33453
+ return this.#advance(location, state, payload.stage);
33454
+ }
33455
+ if (confirmed) {
33456
+ throw new WorkflowCliError(
33457
+ "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
33458
+ "\u751F\u4EA7\u5DE5\u4F5C\u9879\u4E0D\u80FD\u76F4\u63A5\u786E\u8BA4\uFF1B\u8BF7\u5148\u5B8C\u6210\u6821\u9A8C\u5E76\u5C55\u793A CLI \u8FD4\u56DE\u7684\u786E\u8BA4\u5DE5\u4F5C\u9879\u3002"
33459
+ );
33460
+ }
33461
+ await validateRequiredInputs(location, payload.stage);
33462
+ const source = await readArtifact(location, payload.stage);
33463
+ await validateStageResult(payload.stage, source, location);
33464
+ if (payload.action === "stage" && payload.stage === "visual-plan") {
33465
+ const visualPayload = await this.#visualAssetsPayload(location, state);
33466
+ return this.#result(location, visualPayload, true);
33467
+ }
33468
+ if (payload.action === "visual-assets") {
33469
+ const articleSource = await readArtifact(location, "article");
33470
+ const sections = parseWorkflowArticleSections(articleSource);
33471
+ const assetsDirectory = path8.join(location.directory, location.manifest.assetsDirectory);
33472
+ const assignments = workflowVisualAssetAssignments({
33473
+ assetsDirectory,
33474
+ projectDirectory: location.directory,
33475
+ sections,
33476
+ visualPlanSource: source
33477
+ });
33478
+ try {
33479
+ await finalizeWorkflowVisualAssets({
33480
+ articleFile: artifactPath(location, "article"),
33481
+ articleSource,
33482
+ assetsDirectory,
33483
+ assignments,
33484
+ projectDirectory: location.directory,
33485
+ sections,
33486
+ visualPlanSource: source
33487
+ });
33488
+ } catch (error) {
33489
+ if (error instanceof WechatCliError) {
33490
+ throw new WorkflowCliError(error.code, error.guidance ?? error.message, {
33491
+ ...error.fields,
33492
+ stage: payload.stage
33493
+ });
33494
+ }
33495
+ throw error;
33496
+ }
33497
+ }
33498
+ const policy = await this.#confirmationPolicy();
33499
+ if (requiresStageConfirmation(policy, payload.stage)) {
33500
+ const confirmationPayload = await this.#confirmationPayload(location, state, payload.stage);
33501
+ return this.#result(location, confirmationPayload, true);
33502
+ }
33503
+ return this.#advance(location, state, payload.stage);
33504
+ }
33505
+ async #advance(location, state, stage) {
33506
+ const nextState = {
33507
+ ...state,
33508
+ lastCompletedStep: stage,
33509
+ updatedAt: this.#nextTimestamp(state.updatedAt)
32628
33510
  };
33511
+ await writePrivateJson(statePath(location.directory), nextState);
33512
+ return this.#result(location, await this.#stagePayload(location, nextState));
32629
33513
  }
32630
33514
  async listProjects() {
32631
33515
  const projects = [];
@@ -32647,6 +33531,7 @@ var WorkflowCli = class {
32647
33531
  projects.push({
32648
33532
  ...projectSummary(indexed.location),
32649
33533
  available: true,
33534
+ directory: indexed.location.directory,
32650
33535
  done: step === null,
32651
33536
  step,
32652
33537
  updatedAt: state.updatedAt
@@ -32655,96 +33540,82 @@ var WorkflowCli = class {
32655
33540
  projects.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
32656
33541
  return { command: "project.list", data: { projects }, ok: true };
32657
33542
  }
32658
- async next(query, directory) {
32659
- const projects = await this.#projects.find(query, directory);
32660
- if (projects.length === 0) {
32661
- return { command: "workflow.next", data: { match: "none", projects: [] }, ok: true };
33543
+ async deliveryInputs(workItemId) {
33544
+ const resolved = await this.#resolve(workItemId);
33545
+ if (resolved.payload.stage !== "delivery" || resolved.payload.action !== "delivery-preview" && resolved.payload.action !== "delivery-confirm") {
33546
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u5F53\u524D\u5DE5\u4F5C\u9879\u4E0D\u662F\u53EF\u4EA4\u4ED8\u5DE5\u4F5C\u9879\u3002");
32662
33547
  }
32663
- if (projects.length > 1) {
32664
- return {
32665
- command: "workflow.next",
32666
- data: { match: "multiple", projects: projects.map(projectSummary) },
32667
- ok: true
32668
- };
33548
+ await validateRequiredInputs(resolved.location, "delivery");
33549
+ const titleSource = await readArtifact(resolved.location, "titles");
33550
+ const title = selectedTitle(frontmatter(titleSource));
33551
+ if (title === void 0) {
33552
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u6807\u9898\u4EA7\u7269\u7F3A\u5C11\u6700\u7EC8\u6807\u9898\u3002");
32669
33553
  }
32670
- const location = projects[0];
32671
- if (location === void 0) {
32672
- throw new ProjectCliError("DXC_PROJECT_NOT_FOUND", "Content project was not found");
33554
+ let sidecar;
33555
+ try {
33556
+ sidecar = await readWorkflowVisualAssetsSidecar(resolved.location.directory);
33557
+ await validateWorkflowVisualAssets({
33558
+ articleSource: await readArtifact(resolved.location, "article"),
33559
+ assetsDirectory: path8.join(
33560
+ resolved.location.directory,
33561
+ resolved.location.manifest.assetsDirectory
33562
+ ),
33563
+ projectDirectory: resolved.location.directory,
33564
+ visualPlanSource: await readArtifact(resolved.location, "visual-plan")
33565
+ });
33566
+ } catch {
33567
+ throw new WorkflowCliError(
33568
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
33569
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u7F3A\u5931\uFF0C\u8BF7\u91CD\u65B0\u5B8C\u6210 visual-plan \u9636\u6BB5\u3002"
33570
+ );
32673
33571
  }
32674
- const state = await this.#loadState(location);
32675
- const confirmationPolicy = await this.#confirmationPolicy();
32676
33572
  return {
32677
- command: "workflow.next",
32678
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32679
- ok: true
33573
+ articleFile: artifactPath(resolved.location, "article"),
33574
+ assetsDirectory: path8.join(
33575
+ resolved.location.directory,
33576
+ resolved.location.manifest.assetsDirectory
33577
+ ),
33578
+ project: projectSummary(resolved.location),
33579
+ projectDirectory: resolved.location.directory,
33580
+ title,
33581
+ visualPlanFile: sidecar.publishingVisualPlanPath
32680
33582
  };
32681
33583
  }
32682
- async complete(directory, projectId, confirmed = false) {
32683
- const location = await this.#locate(directory, projectId);
32684
- const state = await this.#loadState(location);
32685
- const confirmationPolicy = await this.#confirmationPolicy();
32686
- const step = nextStep(state.lastCompletedStep);
32687
- if (step === null) {
32688
- return {
32689
- command: "workflow.next",
32690
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32691
- ok: true
32692
- };
32693
- }
32694
- if (step === "delivery") {
32695
- throw new WorkflowCliError(
32696
- "DXC_WORKFLOW_DELIVERY_REQUIRED",
32697
- "\u5185\u5BB9\u5DF2\u7ECF\u51C6\u5907\u5B8C\u6210\uFF1B\u8BF7\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u5E76\u786E\u8BA4\u4EA4\u4ED8\u3002"
32698
- );
32699
- }
32700
- const source = await readArtifact(location.directory, location.manifest.artifacts[step]);
32701
- await validateStageResult(step, source, location);
32702
- if (requiresStageConfirmation(confirmationPolicy, step) && !confirmed) {
32703
- throw new WorkflowCliError(
32704
- "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
32705
- `\u5F53\u524D\u786E\u8BA4\u7B56\u7565\u8981\u6C42\u7528\u6237\u786E\u8BA4${stageLabel(step)}\uFF1B\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u4EA7\u7269\u540E\uFF0C\u8BF7\u4F7F\u7528 --confirm \u5B8C\u6210\u672C\u9636\u6BB5\u3002`
32706
- );
33584
+ async issueDeliveryConfirmation(previewWorkItemId, bindingFingerprint, expiresAt) {
33585
+ const resolved = await this.#resolve(previewWorkItemId);
33586
+ if (resolved.payload.action !== "delivery-preview") {
33587
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u8BF7\u4ECE\u5F53\u524D delivery \u9884\u89C8\u5DE5\u4F5C\u9879\u5F00\u59CB\u3002");
32707
33588
  }
32708
- const nextState = {
32709
- ...state,
32710
- lastCompletedStep: step,
32711
- updatedAt: this.#now().toISOString()
32712
- };
32713
- await writePrivateJson(statePath(location.directory), nextState);
32714
- return {
32715
- command: "workflow.next",
32716
- data: { match: "ready", work: workItem(location, nextState, confirmationPolicy) },
32717
- ok: true
33589
+ const payload = {
33590
+ ...await this.#basePayload(
33591
+ resolved.location,
33592
+ resolved.state,
33593
+ "delivery",
33594
+ "delivery-confirm"
33595
+ ),
33596
+ bindingFingerprint
32718
33597
  };
33598
+ return this.#result(resolved.location, payload, true, {
33599
+ expiresAt,
33600
+ type: "confirm-delivery"
33601
+ });
32719
33602
  }
32720
- async deliveryInputs(directory, projectId) {
32721
- const location = await this.#locate(directory, projectId);
32722
- const state = await this.#loadState(location);
32723
- if (nextStep(state.lastCompletedStep) !== "delivery") {
33603
+ async resolveDeliveryConfirmation(workItemId, bindingFingerprint) {
33604
+ const resolved = await this.#resolve(workItemId);
33605
+ if (resolved.payload.action !== "delivery-confirm" || resolved.payload.bindingFingerprint !== bindingFingerprint) {
32724
33606
  throw new WorkflowCliError(
32725
- "DXC_WORKFLOW_NOT_READY",
32726
- "\u8BF7\u5148\u5B8C\u6210\u6B63\u6587\u3001\u6807\u9898\u3001\u89C6\u89C9\u548C\u5BA1\u6821\u6B65\u9AA4\uFF0C\u518D\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002"
33607
+ "DXC_WORK_ITEM_STALE",
33608
+ "\u6700\u7EC8\u9884\u89C8\u7ED1\u5B9A\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5E76\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u3002"
32727
33609
  );
32728
33610
  }
32729
- const titleSource = await readArtifact(location.directory, location.manifest.artifacts.titles);
32730
- const title = selectedTitle(frontmatter(titleSource));
32731
- return {
32732
- articleFile: path8.join(location.directory, location.manifest.artifacts.article),
32733
- assetsDirectory: path8.join(location.directory, location.manifest.assetsDirectory),
32734
- project: projectSummary(location),
32735
- projectDirectory: location.directory,
32736
- title,
32737
- visualPlanFile: path8.join(location.directory, location.manifest.artifacts["visual-plan"])
32738
- };
33611
+ return this.deliveryInputs(workItemId);
32739
33612
  }
32740
- async completeDelivery(directory, receipt) {
32741
- const location = await this.#projects.locate(directory);
32742
- const state = await this.#loadState(location);
32743
- const confirmationPolicy = await this.#confirmationPolicy();
32744
- if (nextStep(state.lastCompletedStep) !== "delivery") {
32745
- throw new WorkflowCliError("DXC_WORKFLOW_NOT_READY", "\u5F53\u524D\u6587\u7AE0\u4E0D\u5728\u5FAE\u4FE1\u4EA4\u4ED8\u6B65\u9AA4\u3002");
33613
+ async completeDelivery(workItemId, bindingFingerprint, receipt) {
33614
+ const resolved = await this.#resolve(workItemId);
33615
+ if (resolved.payload.action !== "delivery-confirm" || resolved.payload.bindingFingerprint !== bindingFingerprint) {
33616
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u4EA4\u4ED8\u786E\u8BA4\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4E0D\u53EF\u53D8\u9884\u89C8\u3002");
32746
33617
  }
32747
- const deliveryFile = path8.join(location.directory, location.manifest.artifacts.delivery);
33618
+ const deliveryFile = artifactPath(resolved.location, "delivery");
32748
33619
  await writePrivateBuffer(
32749
33620
  deliveryFile,
32750
33621
  Buffer.from(
@@ -32752,22 +33623,12 @@ var WorkflowCli = class {
32752
33623
 
32753
33624
  - intentId: ${receipt.intentId}
32754
33625
  - mediaId: ${receipt.mediaId}
32755
- - completedAt: ${this.#now().toISOString()}
33626
+ - completedAt: ${this.#nextTimestamp(resolved.state.updatedAt)}
32756
33627
  `,
32757
33628
  "utf8"
32758
33629
  )
32759
33630
  );
32760
- const nextState = {
32761
- ...state,
32762
- lastCompletedStep: "delivery",
32763
- updatedAt: this.#now().toISOString()
32764
- };
32765
- await writePrivateJson(statePath(location.directory), nextState);
32766
- return {
32767
- command: "workflow.next",
32768
- data: { match: "ready", work: workItem(location, nextState, confirmationPolicy) },
32769
- ok: true
32770
- };
33631
+ return this.#advance(resolved.location, resolved.state, "delivery");
32771
33632
  }
32772
33633
  };
32773
33634
 
@@ -32775,15 +33636,21 @@ var WorkflowCli = class {
32775
33636
  var DELIVERY_STATE_FILE = ".dxc/delivery.json";
32776
33637
  var DELIVERY_STATE_MAXIMUM_BYTES = 32 * 1024;
32777
33638
  var MAXIMUM_POLL_ATTEMPTS = 150;
33639
+ var CHOICE_PATTERN = /^choice1_([A-Za-z0-9_-]+)\.([a-f0-9]{32})$/u;
33640
+ function sha2563(value) {
33641
+ return createHash8("sha256").update(value, "utf8").digest("hex");
33642
+ }
32778
33643
  function selectedDraftIdempotencyKey(projectId, rootSnapshotHash, selectedSnapshotHash, selectionRevision) {
32779
- const selectionDigest = createHash7("sha256").update(`${rootSnapshotHash}:${selectedSnapshotHash}:${selectionRevision}`, "utf8").digest("hex");
33644
+ const selectionDigest = sha2563(
33645
+ `${rootSnapshotHash}:${selectedSnapshotHash}:${selectionRevision}`
33646
+ );
32780
33647
  return `dxc:${projectId}:${selectionDigest}`;
32781
33648
  }
32782
33649
  function isRecord2(value) {
32783
33650
  return typeof value === "object" && value !== null && !Array.isArray(value);
32784
33651
  }
32785
33652
  function deliveryState(value) {
32786
- if (!isRecord2(value) || value["version"] !== 1) {
33653
+ if (!isRecord2(value) || value["version"] !== 1 && value["version"] !== 2) {
32787
33654
  return void 0;
32788
33655
  }
32789
33656
  const required = [
@@ -32803,31 +33670,86 @@ function deliveryState(value) {
32803
33670
  if (value["intentId"] !== void 0 && typeof value["intentId"] !== "string") {
32804
33671
  return void 0;
32805
33672
  }
33673
+ if (value["version"] === 2 && (typeof value["selectedSnapshotHash"] !== "string" || typeof value["selectedSnapshotId"] !== "string" || !Number.isInteger(value["selectionRevision"]) || typeof value["templateId"] !== "string")) {
33674
+ return void 0;
33675
+ }
32806
33676
  return value;
32807
33677
  }
32808
- function accountChoice(account) {
32809
- return { id: account.id, name: account.displayName };
32810
- }
32811
33678
  function availableAccounts(accounts) {
32812
33679
  return accounts.filter(
32813
33680
  (account) => account.status === "ACTIVE" && account.draftCapability === "available" && account.permissions.includes(11)
32814
33681
  );
32815
33682
  }
33683
+ function encodeChoice(workItemId, account) {
33684
+ const payload = Buffer.from(
33685
+ JSON.stringify({ accountId: account.id, workItemFingerprint: sha2563(workItemId) }),
33686
+ "utf8"
33687
+ ).toString("base64url");
33688
+ return `choice1_${payload}.${sha2563(payload).slice(0, 32)}`;
33689
+ }
33690
+ function decodeChoice(choiceId, workItemId) {
33691
+ const match = CHOICE_PATTERN.exec(choiceId);
33692
+ if (match === null || match[1] === void 0 || match[2] === void 0 || sha2563(match[1]).slice(0, 32) !== match[2]) {
33693
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u516C\u4F17\u53F7 choiceId \u65E0\u6548\u3002");
33694
+ }
33695
+ try {
33696
+ const payload = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
33697
+ if (!isRecord2(payload) || typeof payload["accountId"] !== "string" || payload["workItemFingerprint"] !== sha2563(workItemId)) {
33698
+ throw new TypeError("choice binding mismatch");
33699
+ }
33700
+ return payload["accountId"];
33701
+ } catch {
33702
+ throw new WorkflowCliError(
33703
+ "DXC_WORK_ITEM_STALE",
33704
+ "\u516C\u4F17\u53F7 choiceId \u4E0D\u5C5E\u4E8E\u5F53\u524D delivery \u5DE5\u4F5C\u9879\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u3002"
33705
+ );
33706
+ }
33707
+ }
33708
+ function selectedState(state, preview) {
33709
+ return {
33710
+ ...state,
33711
+ idempotencyKey: selectedDraftIdempotencyKey(
33712
+ state.projectId,
33713
+ state.rootSnapshotHash,
33714
+ preview.selectedSnapshotHash,
33715
+ preview.selectionRevision
33716
+ ),
33717
+ selectedSnapshotHash: preview.selectedSnapshotHash,
33718
+ selectedSnapshotId: preview.selectedSnapshotId,
33719
+ selectionRevision: preview.selectionRevision,
33720
+ templateId: preview.templateId,
33721
+ version: 2
33722
+ };
33723
+ }
33724
+ function deliveryBindingFingerprint(state) {
33725
+ return sha2563(
33726
+ JSON.stringify({
33727
+ accountId: state.accountId,
33728
+ projectId: state.projectId,
33729
+ rootSnapshotHash: state.rootSnapshotHash,
33730
+ rootSnapshotId: state.rootSnapshotId,
33731
+ selectedSnapshotHash: state.selectedSnapshotHash,
33732
+ selectedSnapshotId: state.selectedSnapshotId,
33733
+ selectionRevision: state.selectionRevision,
33734
+ templateId: state.templateId
33735
+ })
33736
+ );
33737
+ }
33738
+ function completedVerification(verification) {
33739
+ return verification !== void 0 && verification.authorMatched === true && verification.contentMatched === true && verification.coverMatched === true && verification.digestMatched === true && verification.titleMatched === true && verification.imageMismatches.length === 0;
33740
+ }
32816
33741
  var WorkflowDeliveryCli = class {
32817
33742
  #output;
32818
33743
  #sleep;
32819
33744
  #wechat;
32820
33745
  #workflow;
32821
33746
  constructor(input) {
32822
- this.#output = {
32823
- writeStderr: input.output.writeStderr,
32824
- writeStdout: () => void 0
32825
- };
33747
+ this.#output = { writeStderr: input.output.writeStderr, writeStdout: () => void 0 };
32826
33748
  this.#sleep = input.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
32827
33749
  this.#wechat = input.wechat;
32828
33750
  this.#workflow = input.workflow;
32829
33751
  }
32830
- async #readState(projectDirectory) {
33752
+ async #readStateIfPresent(projectDirectory) {
32831
33753
  try {
32832
33754
  const source = await readPrivateFile(
32833
33755
  path9.join(projectDirectory, DELIVERY_STATE_FILE),
@@ -32839,10 +33761,7 @@ var WorkflowDeliveryCli = class {
32839
33761
  }
32840
33762
  } catch (error) {
32841
33763
  if (error instanceof LocalStateError && error.failure === "missing") {
32842
- throw new WorkflowCliError(
32843
- "DXC_WORKFLOW_PREVIEW_REQUIRED",
32844
- "\u8BF7\u5148\u751F\u6210\u5E76\u67E5\u770B\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8\u3002"
32845
- );
33764
+ return void 0;
32846
33765
  }
32847
33766
  }
32848
33767
  throw new WorkflowCliError(
@@ -32850,11 +33769,64 @@ var WorkflowDeliveryCli = class {
32850
33769
  "\u672C\u5730\u4EA4\u4ED8\u72B6\u6001\u65E0\u6CD5\u8BFB\u53D6\uFF1B\u672A\u521B\u5EFA\u65B0\u7684\u5FAE\u4FE1\u8349\u7A3F\u3002"
32851
33770
  );
32852
33771
  }
33772
+ async #readState(projectDirectory) {
33773
+ const state = await this.#readStateIfPresent(projectDirectory);
33774
+ if (state !== void 0) {
33775
+ return state;
33776
+ }
33777
+ throw new WorkflowCliError(
33778
+ "DXC_WORKFLOW_PREVIEW_REQUIRED",
33779
+ "\u8BF7\u5148\u751F\u6210\u5E76\u67E5\u770B\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8\u3002"
33780
+ );
33781
+ }
32853
33782
  async #writeState(projectDirectory, state) {
32854
33783
  await writePrivateJson(path9.join(projectDirectory, DELIVERY_STATE_FILE), state);
32855
33784
  }
33785
+ async #selectState(projectDirectory, state) {
33786
+ const preview = await this.#wechat.draftPreviewStatus(
33787
+ { snapshotHash: state.rootSnapshotHash, snapshotId: state.rootSnapshotId },
33788
+ this.#output
33789
+ );
33790
+ const selected = selectedState(state, preview);
33791
+ await this.#writeState(projectDirectory, selected);
33792
+ return selected;
33793
+ }
33794
+ #selectionWorkItem(workItemId, accounts) {
33795
+ const nextAction = {
33796
+ choices: accounts.map((account) => ({
33797
+ choiceId: encodeChoice(workItemId, account),
33798
+ label: account.displayName
33799
+ })),
33800
+ purpose: "wechat-account",
33801
+ type: "select-choice"
33802
+ };
33803
+ return {
33804
+ nextAction,
33805
+ skill: "dxc-wechat-publisher",
33806
+ stage: "delivery",
33807
+ workItemId
33808
+ };
33809
+ }
32856
33810
  async preview(options) {
32857
- const inputs = await this.#workflow.deliveryInputs(options.directory, options.projectId);
33811
+ const inputs = await this.#workflow.deliveryInputs(options.workItemId);
33812
+ const existingState = await this.#readStateIfPresent(inputs.projectDirectory);
33813
+ if (existingState?.projectId === inputs.project.projectId && existingState.intentId !== void 0) {
33814
+ await this.#wechat.publishingDraftStatus(existingState.intentId, this.#output);
33815
+ const selected = await this.#selectState(inputs.projectDirectory, existingState);
33816
+ const confirmation2 = await this.#workflow.issueDeliveryConfirmation(
33817
+ options.workItemId,
33818
+ deliveryBindingFingerprint(selected),
33819
+ selected.previewExpiresAt
33820
+ );
33821
+ return {
33822
+ command: "workflow.preview",
33823
+ data: {
33824
+ ...confirmation2.data,
33825
+ status: "needs-attention"
33826
+ },
33827
+ ok: true
33828
+ };
33829
+ }
32858
33830
  const accounts = availableAccounts(await this.#wechat.accounts(this.#output));
32859
33831
  if (accounts.length === 0) {
32860
33832
  throw new WechatCliError(
@@ -32862,9 +33834,10 @@ var WorkflowDeliveryCli = class {
32862
33834
  "\u5F53\u524D\u6CA1\u6709\u53EF\u521B\u5EFA\u8349\u7A3F\u7684\u516C\u4F17\u53F7\uFF0C\u8BF7\u5148\u5B8C\u6210\u516C\u4F17\u53F7\u7ED1\u5B9A\u3002"
32863
33835
  );
32864
33836
  }
32865
- const account = options.accountId === void 0 ? accounts.length === 1 ? accounts[0] : void 0 : accounts.find((candidate) => candidate.id === options.accountId);
33837
+ const accountId = options.choiceId === void 0 ? void 0 : decodeChoice(options.choiceId, options.workItemId);
33838
+ const account = accountId === void 0 ? accounts.length === 1 ? accounts[0] : void 0 : accounts.find((candidate) => candidate.id === accountId);
32866
33839
  if (account === void 0) {
32867
- if (options.accountId !== void 0) {
33840
+ if (accountId !== void 0) {
32868
33841
  throw new WechatCliError(
32869
33842
  "DXC_PUBLISHING_ACCOUNT_UNAVAILABLE",
32870
33843
  "\u9009\u62E9\u7684\u516C\u4F17\u53F7\u5F53\u524D\u4E0D\u53EF\u7528\u4E8E\u521B\u5EFA\u8349\u7A3F\u3002"
@@ -32873,17 +33846,15 @@ var WorkflowDeliveryCli = class {
32873
33846
  return {
32874
33847
  command: "workflow.preview",
32875
33848
  data: {
32876
- accounts: accounts.map(accountChoice),
32877
- status: "account-selection-required"
33849
+ project: inputs.project,
33850
+ status: "account-selection-required",
33851
+ workItem: this.#selectionWorkItem(options.workItemId, accounts)
32878
33852
  },
32879
33853
  ok: true
32880
33854
  };
32881
33855
  }
32882
33856
  await this.#wechat.optimizeDraftImages(
32883
- {
32884
- projectDirectory: inputs.projectDirectory,
32885
- visualPlanFile: inputs.visualPlanFile
32886
- },
33857
+ { projectDirectory: inputs.projectDirectory, visualPlanFile: inputs.visualPlanFile },
32887
33858
  this.#output
32888
33859
  );
32889
33860
  const preview = await this.#wechat.previewDraft(
@@ -32891,14 +33862,14 @@ var WorkflowDeliveryCli = class {
32891
33862
  accountId: account.id,
32892
33863
  articleFile: inputs.articleFile,
32893
33864
  assetsDirectory: inputs.assetsDirectory,
32894
- browserMode: options.browserMode ?? "system",
33865
+ browserMode: "agent",
32895
33866
  coverFile: "auto",
32896
33867
  title: inputs.title,
32897
33868
  visualPlanFile: inputs.visualPlanFile
32898
33869
  },
32899
33870
  this.#output
32900
33871
  );
32901
- await this.#writeState(inputs.projectDirectory, {
33872
+ const state = await this.#selectState(inputs.projectDirectory, {
32902
33873
  accountId: account.id,
32903
33874
  idempotencyKey: `dxc:${inputs.project.projectId}:${preview.snapshot.snapshotHash}`,
32904
33875
  previewExpiresAt: preview.preview.expiresAt,
@@ -32908,14 +33879,14 @@ var WorkflowDeliveryCli = class {
32908
33879
  rootSnapshotId: preview.snapshot.id,
32909
33880
  version: 1
32910
33881
  });
33882
+ const confirmation = await this.#workflow.issueDeliveryConfirmation(
33883
+ options.workItemId,
33884
+ deliveryBindingFingerprint(state),
33885
+ state.previewExpiresAt
33886
+ );
32911
33887
  return {
32912
33888
  command: "workflow.preview",
32913
- data: {
32914
- account: accountChoice(account),
32915
- expiresAt: preview.preview.expiresAt,
32916
- opened: true,
32917
- status: "ready"
32918
- },
33889
+ data: { ...confirmation.data, status: "ready" },
32919
33890
  ok: true
32920
33891
  };
32921
33892
  }
@@ -32930,6 +33901,30 @@ var WorkflowDeliveryCli = class {
32930
33901
  }
32931
33902
  return current;
32932
33903
  }
33904
+ async #diagnostics(result) {
33905
+ try {
33906
+ return await this.#wechat.publishingDraftDiagnostics(result.intent.id, this.#output);
33907
+ } catch {
33908
+ return {
33909
+ ...result.intent.errorCode === void 0 ? {} : { errorCode: result.intent.errorCode },
33910
+ intentId: result.intent.id,
33911
+ reason: {
33912
+ message: result.intent.progress.message,
33913
+ nextAction: result.intent.progress.nextAction,
33914
+ phase: "unknown",
33915
+ retryAllowed: false
33916
+ },
33917
+ status: result.intent.status,
33918
+ ...result.intent.verification === void 0 ? {} : {
33919
+ verification: {
33920
+ ...result.intent.verification,
33921
+ coverMatched: null,
33922
+ imageMismatches: []
33923
+ }
33924
+ }
33925
+ };
33926
+ }
33927
+ }
32933
33928
  async deliver(options) {
32934
33929
  if (!options.confirmed) {
32935
33930
  throw new WechatCliError(
@@ -32937,29 +33932,33 @@ var WorkflowDeliveryCli = class {
32937
33932
  "\u8BF7\u5148\u67E5\u770B\u5FAE\u4FE1\u9884\u89C8\u5E76\u660E\u786E\u786E\u8BA4\u521B\u5EFA\u8349\u7A3F\u3002"
32938
33933
  );
32939
33934
  }
32940
- const inputs = await this.#workflow.deliveryInputs(options.directory, options.projectId);
32941
- let state = await this.#readState(inputs.projectDirectory);
33935
+ const unresolvedInputs = await this.#workflow.deliveryInputs(options.workItemId);
33936
+ let state = await this.#selectState(
33937
+ unresolvedInputs.projectDirectory,
33938
+ await this.#readState(unresolvedInputs.projectDirectory)
33939
+ );
33940
+ const bindingFingerprint = deliveryBindingFingerprint(state);
33941
+ const inputs = await this.#workflow.resolveDeliveryConfirmation(
33942
+ options.workItemId,
33943
+ bindingFingerprint
33944
+ );
32942
33945
  if (state.projectId !== inputs.project.projectId) {
32943
- throw new WorkflowCliError(
32944
- "DXC_WORKFLOW_DELIVERY_STATE_INVALID",
32945
- "\u9884\u89C8\u4E0D\u5C5E\u4E8E\u5F53\u524D\u6587\u7AE0\uFF1B\u8BF7\u91CD\u65B0\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002"
32946
- );
33946
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u9884\u89C8\u4E0D\u5C5E\u4E8E\u5F53\u524D\u6587\u7AE0\uFF1B\u8BF7\u91CD\u65B0\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002");
32947
33947
  }
32948
33948
  const preview = await this.#wechat.draftPreviewStatus(
32949
33949
  { snapshotHash: state.rootSnapshotHash, snapshotId: state.rootSnapshotId },
32950
33950
  this.#output
32951
33951
  );
32952
- const template = { id: preview.templateId, selectionRevision: preview.selectionRevision };
32953
- const selectedIdempotencyKey = selectedDraftIdempotencyKey(
32954
- state.projectId,
32955
- state.rootSnapshotHash,
32956
- preview.selectedSnapshotHash,
32957
- preview.selectionRevision
32958
- );
32959
- if (state.intentId === void 0 && state.idempotencyKey !== selectedIdempotencyKey) {
32960
- state = { ...state, idempotencyKey: selectedIdempotencyKey };
32961
- await this.#writeState(inputs.projectDirectory, state);
33952
+ const current = selectedState(state, preview);
33953
+ if (deliveryBindingFingerprint(current) !== bindingFingerprint) {
33954
+ await this.#writeState(inputs.projectDirectory, current);
33955
+ throw new WorkflowCliError(
33956
+ "DXC_WORK_ITEM_STALE",
33957
+ "\u6A21\u677F\u3001\u8D26\u53F7\u6216\u4E0D\u53EF\u53D8\u5FEB\u7167\u9009\u62E9\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u67E5\u770B\u6700\u7EC8\u9884\u89C8\u3002"
33958
+ );
32962
33959
  }
33960
+ state = current;
33961
+ const template = { id: state.templateId, selectionRevision: state.selectionRevision };
32963
33962
  if (!preview.viewed) {
32964
33963
  const refreshed = await this.#wechat.refreshDraftPreview(
32965
33964
  {
@@ -32971,9 +33970,19 @@ var WorkflowDeliveryCli = class {
32971
33970
  );
32972
33971
  state = { ...state, previewExpiresAt: refreshed.expiresAt };
32973
33972
  await this.#writeState(inputs.projectDirectory, state);
33973
+ const confirmation = await this.#workflow.issueDeliveryConfirmation(
33974
+ (await this.#workflow.next({ projectId: state.projectId })).data.workItem.workItemId,
33975
+ bindingFingerprint,
33976
+ refreshed.expiresAt
33977
+ );
32974
33978
  return {
32975
33979
  command: "workflow.deliver",
32976
- data: { expiresAt: refreshed.expiresAt, status: "preview-required", template },
33980
+ data: {
33981
+ expiresAt: refreshed.expiresAt,
33982
+ status: "preview-required",
33983
+ template,
33984
+ workItem: confirmation.data.workItem
33985
+ },
32977
33986
  ok: true
32978
33987
  };
32979
33988
  }
@@ -32991,7 +34000,8 @@ var WorkflowDeliveryCli = class {
32991
34000
  );
32992
34001
  state = { ...state, intentId: result.intent.id };
32993
34002
  await this.#writeState(inputs.projectDirectory, state);
32994
- if (result.intent.status === "FAILED" && state.retryAttempt === 0) {
34003
+ let diagnostics = result.intent.status === "FAILED" ? await this.#diagnostics(result) : void 0;
34004
+ if (result.intent.status === "FAILED" && state.retryAttempt === 0 && diagnostics?.reason.retryAllowed === true) {
32995
34005
  result = await this.#terminal(
32996
34006
  await this.#wechat.retryFailedDraft(
32997
34007
  {
@@ -33003,32 +34013,57 @@ var WorkflowDeliveryCli = class {
33003
34013
  );
33004
34014
  state = { ...state, intentId: result.intent.id, retryAttempt: 1 };
33005
34015
  await this.#writeState(inputs.projectDirectory, state);
34016
+ diagnostics = void 0;
33006
34017
  }
33007
- if (result.intent.status === "CREATED_UNVERIFIED") {
33008
- result = await this.#terminal(
33009
- await this.#wechat.reverifyDraft(result.intent.id, this.#output)
33010
- );
34018
+ if (result.intent.status === "CREATED_UNVERIFIED" && result.intent.mediaId !== void 0) {
34019
+ try {
34020
+ result = await this.#terminal(
34021
+ await this.#wechat.reverifyDraft(result.intent.id, this.#output)
34022
+ );
34023
+ diagnostics = void 0;
34024
+ } catch {
34025
+ }
33011
34026
  }
33012
34027
  if (result.intent.status === "SUCCEEDED" && result.intent.mediaId !== void 0) {
33013
- await this.#workflow.completeDelivery(inputs.projectDirectory, {
33014
- intentId: result.intent.id,
33015
- mediaId: result.intent.mediaId
33016
- });
33017
- return {
33018
- command: "workflow.deliver",
33019
- data: { mediaId: result.intent.mediaId, status: "completed", template },
33020
- ok: true
34028
+ const completedDiagnostics = await this.#diagnostics(result);
34029
+ if (completedVerification(completedDiagnostics.verification)) {
34030
+ await this.#workflow.completeDelivery(options.workItemId, bindingFingerprint, {
34031
+ intentId: result.intent.id,
34032
+ mediaId: result.intent.mediaId
34033
+ });
34034
+ return {
34035
+ command: "workflow.deliver",
34036
+ data: {
34037
+ mediaId: result.intent.mediaId,
34038
+ status: "completed",
34039
+ template,
34040
+ verification: completedDiagnostics.verification
34041
+ },
34042
+ ok: true
34043
+ };
34044
+ }
34045
+ diagnostics = {
34046
+ ...completedDiagnostics,
34047
+ reason: {
34048
+ message: "\u8349\u7A3F\u5DF2\u521B\u5EFA\uFF0C\u4F46\u4E94\u5B57\u6BB5\u6216\u56FE\u7247\u8EAB\u4EFD\u56DE\u8BFB\u5C1A\u672A\u5168\u90E8\u901A\u8FC7\u3002",
34049
+ nextAction: "\u4FDD\u7559\u539F intent\uFF0C\u53EA\u8BFB\u6838\u9A8C\u540E\u7EE7\u7EED\u3002",
34050
+ phase: "draft-readback",
34051
+ retryAllowed: false
34052
+ }
33021
34053
  };
33022
34054
  }
34055
+ diagnostics ??= await this.#diagnostics(result);
33023
34056
  return {
33024
34057
  command: "workflow.deliver",
33025
34058
  data: {
33026
34059
  ...result.intent.errorCode === void 0 ? {} : { errorCode: result.intent.errorCode },
33027
34060
  intentId: result.intent.id,
33028
- message: result.intent.progress.message,
34061
+ message: diagnostics.reason.message,
34062
+ reason: diagnostics.reason,
33029
34063
  status: "needs-attention",
33030
34064
  template,
33031
- ...result.intent.verification === void 0 ? {} : { verification: result.intent.verification }
34065
+ ...result.intent.verification === void 0 ? {} : { verification: result.intent.verification },
34066
+ ...diagnostics.verification === void 0 ? {} : { verificationDetails: diagnostics.verification }
33032
34067
  },
33033
34068
  ok: true
33034
34069
  };
@@ -33063,12 +34098,6 @@ function positiveInteger(value, name = "value") {
33063
34098
  }
33064
34099
  return parsed;
33065
34100
  }
33066
- function previewBrowserMode(value) {
33067
- if (value !== "agent" && value !== "system") {
33068
- throw new CommanderError(1, "DXC_OPTION_INVALID", "browser must be agent or system");
33069
- }
33070
- return value;
33071
- }
33072
34101
  function skillInstallTarget(value) {
33073
34102
  if (value !== "codex" && value !== "workbuddy") {
33074
34103
  throw new CommanderError(1, "DXC_OPTION_INVALID", "target must be codex or workbuddy");
@@ -33088,10 +34117,20 @@ function actionError(error) {
33088
34117
  };
33089
34118
  }
33090
34119
  if (error instanceof WorkflowCliError || error instanceof ProjectCliError || error instanceof ProfileCliError || error instanceof ContentMemoryCliError || error instanceof SkillInstallerError) {
33091
- return { code: error.code, message: error.message, retryable: false };
34120
+ return {
34121
+ code: error.code,
34122
+ ...error instanceof WorkflowCliError && error.details !== void 0 ? { details: error.details } : {},
34123
+ message: error.message,
34124
+ retryable: false
34125
+ };
33092
34126
  }
33093
34127
  if (error instanceof Error && error.name === "KnowledgeCliError" && "code" in error) {
33094
- return { code: String(error.code), message: error.message, retryable: false };
34128
+ return {
34129
+ code: String(error.code),
34130
+ ..."details" in error && error.details !== void 0 ? { details: error.details } : {},
34131
+ message: error.message,
34132
+ retryable: false
34133
+ };
33095
34134
  }
33096
34135
  return {
33097
34136
  code: "DXC_COMMAND_FAILED",
@@ -33126,7 +34165,7 @@ async function loadKnowledgeModule() {
33126
34165
  }
33127
34166
  };
33128
34167
  try {
33129
- return await import("./chunks/knowledge-YPR7JKUT.js");
34168
+ return await import("./chunks/knowledge-JCYMCMSP.js");
33130
34169
  } finally {
33131
34170
  process.emitWarning = emitWarning;
33132
34171
  }
@@ -33217,24 +34256,27 @@ function createProgram(context) {
33217
34256
  })
33218
34257
  )
33219
34258
  );
33220
- workflow.command("next").description("\u8FD4\u56DE\u552F\u4E00\u9700\u8981\u6267\u884C\u7684\u4E0B\u4E00\u6B65").argument("[query]", "\u6587\u7AE0\u6807\u9898\u6216\u9879\u76EE ID").option("--directory <path>", "\u6307\u5B9A\u9879\u76EE\u76EE\u5F55").option("--json", "\u8F93\u51FA JSON").action(
33221
- async (query, options) => runAction(
34259
+ workflow.command("next").description("\u8FD4\u56DE\u552F\u4E00\u9700\u8981\u6267\u884C\u7684\u4E0B\u4E00\u6B65").option("--project <uuid>", "\u9879\u76EE ID").option("--work-item <id>", "\u5F53\u524D\u5DE5\u4F5C\u9879 ID").option("--json", "\u8F93\u51FA JSON").action(
34260
+ async (options) => runAction(
33222
34261
  context,
33223
34262
  options.json === true,
33224
34263
  () => new WorkflowCli({
33225
34264
  currentDirectory: context.currentDirectory,
33226
34265
  homeDirectory: context.homeDirectory
33227
- }).next(query, options.directory)
34266
+ }).next({
34267
+ ...options.project === void 0 ? {} : { projectId: options.project },
34268
+ ...options.workItem === void 0 ? {} : { workItemId: options.workItem }
34269
+ })
33228
34270
  )
33229
34271
  );
33230
- workflow.command("complete").description("\u4FDD\u5B58\u5F53\u524D\u6B65\u9AA4\u5E76\u8FD4\u56DE\u4E0B\u4E00\u6B65").option("--confirm", "\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u9636\u6BB5\u4EA7\u7269").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--json", "\u8F93\u51FA JSON").action(
34272
+ workflow.command("complete").description("\u4FDD\u5B58\u5F53\u524D\u6B65\u9AA4\u5E76\u8FD4\u56DE\u4E0B\u4E00\u6B65").requiredOption("--work-item <id>", "\u5F53\u524D\u5DE5\u4F5C\u9879 ID").option("--confirm", "\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u9636\u6BB5\u4EA7\u7269").option("--json", "\u8F93\u51FA JSON").action(
33231
34273
  async (options) => runAction(
33232
34274
  context,
33233
34275
  options.json === true,
33234
34276
  () => new WorkflowCli({
33235
34277
  currentDirectory: context.currentDirectory,
33236
34278
  homeDirectory: context.homeDirectory
33237
- }).complete(options.directory, options.project, options.confirm === true)
34279
+ }).complete(options.workItem, options.confirm === true)
33238
34280
  )
33239
34281
  );
33240
34282
  const deliveryAction = async (json, action) => runAction(context, json, () => {
@@ -33250,24 +34292,21 @@ function createProgram(context) {
33250
34292
  })
33251
34293
  );
33252
34294
  });
33253
- workflow.command("preview").description("\u51C6\u5907\u5E76\u6253\u5F00\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8").option("--account <uuid>", "\u591A\u4E2A\u516C\u4F17\u53F7\u65F6\u9009\u62E9\u76EE\u6807\u8D26\u53F7").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--browser <agent|system>", "\u9884\u89C8\u6253\u5F00\u4F4D\u7F6E", "system").option("--json", "\u8F93\u51FA JSON").action(
34295
+ workflow.command("preview").description("\u51C6\u5907\u5E76\u6253\u5F00\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8").requiredOption("--work-item <id>", "\u5F53\u524D delivery \u5DE5\u4F5C\u9879 ID").option("--choice <id>", "\u516C\u4F17\u53F7\u5019\u9009 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33254
34296
  async (options) => deliveryAction(
33255
34297
  options.json === true,
33256
34298
  (delivery) => delivery.preview({
33257
- ...options.account === void 0 ? {} : { accountId: options.account },
33258
- browserMode: previewBrowserMode(options.browser),
33259
- ...options.directory === void 0 ? {} : { directory: options.directory },
33260
- ...options.project === void 0 ? {} : { projectId: options.project }
34299
+ ...options.choice === void 0 ? {} : { choiceId: options.choice },
34300
+ workItemId: options.workItem
33261
34301
  })
33262
34302
  )
33263
34303
  );
33264
- workflow.command("deliver").description("\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u5E76\u521B\u5EFA\u5FAE\u4FE1\u516C\u4F17\u53F7\u8349\u7A3F").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--confirm", "\u786E\u8BA4\u5DF2\u7ECF\u67E5\u770B\u9884\u89C8\u5E76\u521B\u5EFA\u8349\u7A3F").option("--json", "\u8F93\u51FA JSON").action(
34304
+ workflow.command("deliver").description("\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u5E76\u521B\u5EFA\u5FAE\u4FE1\u516C\u4F17\u53F7\u8349\u7A3F").requiredOption("--work-item <id>", "\u6700\u7EC8\u9884\u89C8\u7ED1\u5B9A\u5DE5\u4F5C\u9879 ID").option("--confirm", "\u786E\u8BA4\u5DF2\u7ECF\u67E5\u770B\u9884\u89C8\u5E76\u521B\u5EFA\u8349\u7A3F").option("--json", "\u8F93\u51FA JSON").action(
33265
34305
  async (options) => deliveryAction(
33266
34306
  options.json === true,
33267
34307
  (delivery) => delivery.deliver({
33268
34308
  confirmed: options.confirm === true,
33269
- ...options.directory === void 0 ? {} : { directory: options.directory },
33270
- ...options.project === void 0 ? {} : { projectId: options.project }
34309
+ workItemId: options.workItem
33271
34310
  })
33272
34311
  )
33273
34312
  );
@@ -33301,45 +34340,54 @@ function createProgram(context) {
33301
34340
  })
33302
34341
  );
33303
34342
  const profile = program2.command("profile").description("\u7BA1\u7406\u672C\u5730\u521B\u4F5C\u753B\u50CF");
33304
- profile.command("status").description("\u67E5\u770B\u521B\u4F5C\u753B\u50CF").option("--json", "\u8F93\u51FA JSON").action(
34343
+ profile.command("begin").description("\u5F00\u59CB\u753B\u50CF\u95EE\u7B54").option("--json", "\u8F93\u51FA JSON").action(
33305
34344
  async (options) => runAction(
33306
34345
  context,
33307
34346
  options.json === true,
33308
34347
  () => new ProfileCli({
33309
34348
  currentDirectory: context.currentDirectory,
33310
34349
  homeDirectory: context.homeDirectory
33311
- }).status()
34350
+ }).begin()
33312
34351
  )
33313
34352
  );
33314
- profile.command("save").description("\u4FDD\u5B58\u521B\u4F5C\u753B\u50CF").requiredOption("--input <path>", "\u753B\u50CF JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34353
+ profile.command("answer").description("\u56DE\u7B54\u5F53\u524D\u753B\u50CF\u95EE\u9898").requiredOption("--session <uuid>", "\u753B\u50CF\u4F1A\u8BDD ID").option("--choice <id>", "\u9009\u62E9\u9879 choiceId\uFF0C\u53EF\u91CD\u590D", collectValues, []).option("--text <value>", "\u6587\u672C\u7B54\u6848").option("--note <value>", "\u8865\u5145\u8BF4\u660E").option("--json", "\u8F93\u51FA JSON").action(
33315
34354
  async (options) => runAction(
33316
34355
  context,
33317
34356
  options.json === true,
33318
34357
  () => new ProfileCli({
33319
34358
  currentDirectory: context.currentDirectory,
33320
34359
  homeDirectory: context.homeDirectory
33321
- }).save(options.input)
34360
+ }).answer(options.session, {
34361
+ choices: options.choice,
34362
+ ...options.note === void 0 ? {} : { note: options.note },
34363
+ ...options.text === void 0 ? {} : { text: options.text }
34364
+ })
33322
34365
  )
33323
34366
  );
33324
- profile.command("update").description("\u66F4\u65B0\u521B\u4F5C\u753B\u50CF").requiredOption("--input <path>", "\u753B\u50CF\u66F4\u65B0 JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34367
+ profile.command("status").description("\u67E5\u770B\u521B\u4F5C\u753B\u50CF").option("--json", "\u8F93\u51FA JSON").action(
33325
34368
  async (options) => runAction(
33326
34369
  context,
33327
34370
  options.json === true,
33328
34371
  () => new ProfileCli({
33329
34372
  currentDirectory: context.currentDirectory,
33330
34373
  homeDirectory: context.homeDirectory
33331
- }).update(options.input)
34374
+ }).status()
33332
34375
  )
33333
34376
  );
33334
34377
  const memory = program2.command("memory").description("\u7BA1\u7406\u672C\u5730\u5185\u5BB9\u8BB0\u5FC6");
33335
- memory.command("add").description("\u4FDD\u5B58\u4E00\u6761\u7528\u6237\u786E\u8BA4\u7684\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--input <path>", "\u8BB0\u5FC6 JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34378
+ memory.command("add").description("\u4FDD\u5B58\u4E00\u6761\u7528\u6237\u786E\u8BA4\u7684\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--content <text>", "\u8BB0\u5FC6\u6B63\u6587").requiredOption("--kind <kind>", "\u8BB0\u5FC6\u9886\u57DF\u7C7B\u578B").requiredOption("--scope <scope>", "global \u6216 project").option("--expires-at <datetime>", "\u8FC7\u671F\u65F6\u95F4").option("--json", "\u8F93\u51FA JSON").action(
33336
34379
  async (options) => runAction(
33337
34380
  context,
33338
34381
  options.json === true,
33339
34382
  () => new ContentMemoryCli({
33340
34383
  currentDirectory: context.currentDirectory,
33341
34384
  homeDirectory: context.homeDirectory
33342
- }).add(options.input)
34385
+ }).add({
34386
+ content: options.content,
34387
+ ...options.expiresAt === void 0 ? {} : { expiresAt: options.expiresAt },
34388
+ kind: options.kind,
34389
+ scope: options.scope
34390
+ })
33343
34391
  )
33344
34392
  );
33345
34393
  memory.command("list").description("\u67E5\u770B\u5185\u5BB9\u8BB0\u5FC6").option("--json", "\u8F93\u51FA JSON").action(
@@ -33352,42 +34400,49 @@ function createProgram(context) {
33352
34400
  }).list()
33353
34401
  )
33354
34402
  );
33355
- memory.command("archive").description("\u5F52\u6863\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").argument("<memory-id>", "\u8BB0\u5FC6 ID").option("--json", "\u8F93\u51FA JSON").action(
33356
- async (memoryId, options) => runAction(
34403
+ memory.command("correct").description("\u539F\u5B50\u66F4\u6B63\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--memory <uuid>", "\u88AB\u66F4\u6B63\u7684\u8BB0\u5FC6 ID").requiredOption("--content <text>", "\u66F4\u6B63\u540E\u7684\u6B63\u6587").requiredOption("--kind <kind>", "\u8BB0\u5FC6\u9886\u57DF\u7C7B\u578B").requiredOption("--scope <scope>", "global \u6216 project").option("--expires-at <datetime>", "\u8FC7\u671F\u65F6\u95F4").option("--json", "\u8F93\u51FA JSON").action(
34404
+ async (options) => runAction(
33357
34405
  context,
33358
34406
  options.json === true,
33359
34407
  () => new ContentMemoryCli({
33360
34408
  currentDirectory: context.currentDirectory,
33361
34409
  homeDirectory: context.homeDirectory
33362
- }).archive(memoryId)
34410
+ }).correct(options.memory, {
34411
+ content: options.content,
34412
+ ...options.expiresAt === void 0 ? {} : { expiresAt: options.expiresAt },
34413
+ kind: options.kind,
34414
+ scope: options.scope
34415
+ })
33363
34416
  )
33364
34417
  );
33365
- const knowledge = program2.command("knowledge").description("\u7BA1\u7406\u672C\u5730\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5");
33366
- knowledge.command("status").description("\u67E5\u770B\u77E5\u8BC6\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
33367
- async (options) => withKnowledge(context, "knowledge.status", options.json === true, (cli) => cli.status())
33368
- );
33369
- knowledge.command("import").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u6587\u7AE0").argument("<files...>", "Markdown \u6216\u6587\u672C\u6587\u4EF6").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
33370
- async (files, options) => withKnowledge(
34418
+ memory.command("archive").description("\u5F52\u6863\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--memory <uuid>", "\u8BB0\u5FC6 ID").option("--json", "\u8F93\u51FA JSON").action(
34419
+ async (options) => runAction(
33371
34420
  context,
33372
- "knowledge.import",
33373
34421
  options.json === true,
33374
- (cli) => cli.importArticles({ files, lexicalOnly: options.lexicalOnly === true })
34422
+ () => new ContentMemoryCli({
34423
+ currentDirectory: context.currentDirectory,
34424
+ homeDirectory: context.homeDirectory
34425
+ }).archive(options.memory)
33375
34426
  )
33376
34427
  );
33377
- knowledge.command("import-directory").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u76EE\u5F55").requiredOption("--directory <path>", "\u6587\u7AE0\u76EE\u5F55").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
34428
+ const knowledge = program2.command("knowledge").description("\u7BA1\u7406\u672C\u5730\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5");
34429
+ knowledge.command("status").description("\u67E5\u770B\u77E5\u8BC6\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
34430
+ async (options) => withKnowledge(context, "knowledge.status", options.json === true, (cli) => cli.status())
34431
+ );
34432
+ knowledge.command("import").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u6587\u7AE0").requiredOption("--source <path>", "\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u5355\u6587\u4EF6\u6216\u76EE\u5F55").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--choice <id>", "\u6A21\u578B\u4E0D\u53EF\u7528\u65F6 CLI \u8FD4\u56DE\u7684 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33378
34433
  async (options) => withKnowledge(
33379
34434
  context,
33380
34435
  "knowledge.import",
33381
34436
  options.json === true,
33382
- (cli) => cli.importDirectory(options.directory, options.lexicalOnly === true)
34437
+ (cli) => cli.importSource(options.source, options.lexicalOnly === true, options.choice)
33383
34438
  )
33384
34439
  );
33385
- knowledge.command("search").description("\u68C0\u7D22\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5").argument("<query>", "\u68C0\u7D22\u76EE\u6807").option("--mode <mode>", "hybrid / lexical / semantic", "hybrid").option("--limit <count>", "\u6700\u591A\u8FD4\u56DE\u6761\u6570", "8").option("--json", "\u8F93\u51FA JSON").action(
34440
+ knowledge.command("search").description("\u68C0\u7D22\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5").argument("<query>", "\u68C0\u7D22\u76EE\u6807").option("--mode <mode>", "hybrid / lexical / semantic", "hybrid").option("--limit <count>", "\u6700\u591A\u8FD4\u56DE\u6761\u6570", "8").option("--choice <id>", "\u6A21\u578B\u4E0D\u53EF\u7528\u65F6 CLI \u8FD4\u56DE\u7684 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33386
34441
  async (query, options) => withKnowledge(
33387
34442
  context,
33388
34443
  "knowledge.search",
33389
34444
  options.json === true,
33390
- (cli) => cli.search(query, options.mode, positiveInteger(options.limit, "limit"))
34445
+ (cli) => cli.search(query, options.mode, positiveInteger(options.limit, "limit"), options.choice)
33391
34446
  )
33392
34447
  );
33393
34448
  knowledge.command("remove").description("\u5220\u9664\u4E00\u7BC7\u672C\u5730\u5386\u53F2\u6587\u7AE0").argument("<article-id>", "\u6587\u7AE0 ID").option("--json", "\u8F93\u51FA JSON").action(
@@ -33399,14 +34454,6 @@ function createProgram(context) {
33399
34454
  )
33400
34455
  );
33401
34456
  const quotes = knowledge.command("quotes").description("\u7BA1\u7406\u672C\u5730\u91D1\u53E5");
33402
- quotes.command("init").description("\u521D\u59CB\u5316\u91D1\u53E5\u5E93").option("--json", "\u8F93\u51FA JSON").action(
33403
- async (options) => withKnowledge(
33404
- context,
33405
- "knowledge.quotes.init",
33406
- options.json === true,
33407
- (cli) => cli.initQuotes()
33408
- )
33409
- );
33410
34457
  quotes.command("status").description("\u67E5\u770B\u91D1\u53E5\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
33411
34458
  async (options) => withKnowledge(
33412
34459
  context,
@@ -33461,14 +34508,6 @@ function createProgram(context) {
33461
34508
  (cli) => cli.archiveQuote(quoteId)
33462
34509
  )
33463
34510
  );
33464
- quotes.command("sync").description("\u540C\u6B65\u91D1\u53E5\u5E93\u7D22\u5F15").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
33465
- async (options) => withKnowledge(
33466
- context,
33467
- "knowledge.quotes.sync",
33468
- options.json === true,
33469
- (cli) => cli.syncQuotes(options.lexicalOnly === true)
33470
- )
33471
- );
33472
34511
  const auth = program2.command("auth").description("\u7BA1\u7406\u5F53\u524D\u8BBE\u5907\u767B\u5F55");
33473
34512
  const authAction = (options, action) => runAction(
33474
34513
  context,