linkque-cli-v2 1.1.4 → 1.2.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/linkquejs.js CHANGED
@@ -19550,6 +19550,30 @@ var SEARCH_DATABASE_ID = "linkque";
19550
19550
  var SEARCH_TABLE_ID = "linkque_conversation_message_search";
19551
19551
  var CLIENT_CAPABILITY_DATABASE_ID = "linkque";
19552
19552
  var CLIENT_CAPABILITY_TABLE_ID = "linkque_client_cap_invocations";
19553
+ var A2UI_ACTION_DATABASE_ID = "linkque";
19554
+ var A2UI_ACTION_TABLE_ID = "linkque_a2ui_action_invocations";
19555
+ var A2UI_ACTION_TABLE_COLUMNS = [
19556
+ ...["ownerId", "agentId", "threadId", "actionId", "cardRef", "cardRevision", "surfaceId", "componentId"].map((key) => ({
19557
+ key,
19558
+ type: "varchar",
19559
+ required: true,
19560
+ array: false,
19561
+ size: 128,
19562
+ default: null
19563
+ })),
19564
+ { key: "cardInstanceId", type: "varchar", required: true, array: false, size: 256, default: null },
19565
+ { key: "status", type: "varchar", required: true, array: false, size: 32, default: null },
19566
+ { key: "actionContext", type: "text", required: true, array: false, size: 0, default: null },
19567
+ { key: "tradeNO", type: "varchar", required: false, array: false, size: 64, default: null },
19568
+ { key: "callback", type: "text", required: false, array: false, size: 0, default: null },
19569
+ { key: "verification", type: "text", required: false, array: false, size: 0, default: null },
19570
+ { key: "createdAt", type: "varchar", required: true, array: false, size: 32, default: null },
19571
+ { key: "expiresAt", type: "varchar", required: true, array: false, size: 32, default: null }
19572
+ ];
19573
+ var A2UI_ACTION_TABLE_INDEXES = [
19574
+ { key: "thread_status", type: "key", columns: ["threadId", "status"], orders: ["asc", "asc"] },
19575
+ { key: "expires_at", type: "key", columns: ["expiresAt"], orders: ["asc"] }
19576
+ ];
19553
19577
  var CLIENT_CAPABILITY_TABLE_COLUMNS = [
19554
19578
  ...["ownerId", "agentId", "threadId", "runId", "toolCallId"].map((key) => ({
19555
19579
  key,
@@ -20148,6 +20172,8 @@ var FUNCTION_DEFS = [
20148
20172
  value: CLIENT_CAPABILITY_DATABASE_ID
20149
20173
  },
20150
20174
  { key: "CLIENT_CAPABILITY_TABLE_ID", value: CLIENT_CAPABILITY_TABLE_ID },
20175
+ { key: "A2UI_ACTION_DATABASE_ID", value: A2UI_ACTION_DATABASE_ID },
20176
+ { key: "A2UI_ACTION_TABLE_ID", value: A2UI_ACTION_TABLE_ID },
20151
20177
  { key: "RUN_CHUNK_SIZE", value: "10" },
20152
20178
  { key: "RUN_CANCEL_POLL_INTERVAL_MS", value: "500" },
20153
20179
  // linkquegw 出站地址:线上套(无 x-linkque-env 头时生效)。
@@ -20198,6 +20224,8 @@ var FUNCTION_DEFS = [
20198
20224
  value: CLIENT_CAPABILITY_DATABASE_ID
20199
20225
  },
20200
20226
  { key: "CLIENT_CAPABILITY_TABLE_ID", value: CLIENT_CAPABILITY_TABLE_ID },
20227
+ { key: "A2UI_ACTION_DATABASE_ID", value: A2UI_ACTION_DATABASE_ID },
20228
+ { key: "A2UI_ACTION_TABLE_ID", value: A2UI_ACTION_TABLE_ID },
20201
20229
  {
20202
20230
  key: "LINKQUEGW_MCP_SIGN_ENDPOINT",
20203
20231
  value: LINKQUEGW_MCP_SIGN_ENDPOINT
@@ -20273,6 +20301,16 @@ var TABLES = [
20273
20301
  permissions: [],
20274
20302
  columns: CLIENT_CAPABILITY_TABLE_COLUMNS,
20275
20303
  indexes: CLIENT_CAPABILITY_TABLE_INDEXES
20304
+ },
20305
+ {
20306
+ $id: A2UI_ACTION_TABLE_ID,
20307
+ databaseId: A2UI_ACTION_DATABASE_ID,
20308
+ name: A2UI_ACTION_TABLE_ID,
20309
+ enabled: true,
20310
+ rowSecurity: true,
20311
+ permissions: [],
20312
+ columns: A2UI_ACTION_TABLE_COLUMNS,
20313
+ indexes: A2UI_ACTION_TABLE_INDEXES
20276
20314
  }
20277
20315
  ];
20278
20316
  var buildAppwriteConfigTemplate = (envOverrides = {}) => ({
@@ -21040,6 +21078,7 @@ var AGENT_LOG = ".linkque/logs/agent.log";
21040
21078
  var HEALTH_PATH = "/healthz";
21041
21079
  var RUNS_PATH = "/runs";
21042
21080
  var HOME_DATA_PATH = "/home-data";
21081
+ var A2UI_ACTION_PATH = "/a2ui-action";
21043
21082
  var AGENT_RUNTIME_PROTOCOL = "conversation-runs-v2";
21044
21083
  var API_KEY_ENV = "LINKQUE_AGENT_API_KEY";
21045
21084
  var stopped = (health) => ({
@@ -21307,6 +21346,7 @@ var spawnAgentOwner = (cwd, config2, port, apiKey, statePath) => {
21307
21346
  cwd,
21308
21347
  healthPath: HEALTH_PATH,
21309
21348
  homeDataPath: HOME_DATA_PATH,
21349
+ a2uiActionPath: A2UI_ACTION_PATH,
21310
21350
  logPath: import_path15.default.join(cwd, AGENT_LOG),
21311
21351
  port,
21312
21352
  runsPath: RUNS_PATH,
@@ -25304,6 +25344,7 @@ function analyzeA2UIBindings(protocol) {
25304
25344
  const listRoots = inferListRoots(components, issues);
25305
25345
  for (const component of components) {
25306
25346
  const context = {
25347
+ surfaceId,
25307
25348
  component,
25308
25349
  listRoot: listRoots.get(component.id),
25309
25350
  occurrences,
@@ -25317,6 +25358,7 @@ function analyzeA2UIBindings(protocol) {
25317
25358
  issues.push(issue2(`card.surfaces.${surfaceId}.components.${component.id}.children.path`, "INVALID_BINDING_PATH", `Invalid list path ${children.path}`));
25318
25359
  } else {
25319
25360
  occurrences.push({
25361
+ surfaceId,
25320
25362
  componentId: component.id,
25321
25363
  componentName: component.component,
25322
25364
  propertyPath: "children.path",
@@ -25335,6 +25377,16 @@ function analyzeA2UIBindings(protocol) {
25335
25377
  issues
25336
25378
  };
25337
25379
  }
25380
+ function collectReferencedA2UICardBusinessFieldIds(protocol, fields) {
25381
+ const businessFields = fields.filter((field) => field.source === "business").slice().sort((left, right) => right.path.length - left.path.length);
25382
+ const result = /* @__PURE__ */ new Set();
25383
+ for (const occurrence of analyzeA2UIBindings(protocol).occurrences) {
25384
+ const field = businessFields.find((candidate) => occurrence.canonicalPath === candidate.path || occurrence.canonicalPath.startsWith(`${candidate.path}/*/`) || occurrence.canonicalPath.startsWith(`${candidate.path}/`));
25385
+ if (field)
25386
+ result.add(field.fieldId);
25387
+ }
25388
+ return result;
25389
+ }
25338
25390
  function visitValue(value, propertyPath, context) {
25339
25391
  if (propertyPath === "action.local.path" && typeof value === "string") {
25340
25392
  addOccurrence(value, propertyPath, "local", context);
@@ -25366,6 +25418,7 @@ function addOccurrence(rawPath, propertyPath, usage, context) {
25366
25418
  return;
25367
25419
  }
25368
25420
  context.occurrences.push({
25421
+ surfaceId: context.surfaceId,
25369
25422
  componentId: context.component.id,
25370
25423
  componentName: context.component.component,
25371
25424
  propertyPath,
@@ -26051,12 +26104,15 @@ function validateA2UICardBindings(input, options = {}) {
26051
26104
  readyCandidates.set(candidateKey(candidate.resourceId, candidate.tool.toolName), candidate.tool);
26052
26105
  }
26053
26106
  const bindingByField = new Map(bindings.map((binding) => [binding.fieldId, binding]));
26107
+ const requiredFields = collectReferencedA2UICardBusinessFieldIds(input.draft.card, input.draft.manifest.fields.filter((field) => field.required));
26054
26108
  for (const field of input.draft.manifest.fields) {
26055
26109
  if (field.source !== "business")
26056
26110
  continue;
26057
26111
  if (selectedFields && !selectedFields.has(field.fieldId))
26058
26112
  continue;
26059
26113
  const binding = bindingByField.get(field.fieldId);
26114
+ if (!selectedFields && !requiredFields.has(field.fieldId) && !binding)
26115
+ continue;
26060
26116
  if (!binding) {
26061
26117
  issues.push(issue2(`fields.${field.fieldId}`, "CARD_BINDING_INCOMPLETE", `business field ${field.fieldId} has not been bound`));
26062
26118
  continue;
@@ -26300,6 +26356,192 @@ function candidateKey(resourceId, toolName) {
26300
26356
  return `${resourceId}\0${toolName}`;
26301
26357
  }
26302
26358
 
26359
+ // ../agent-a2ui-card-contract/dist/esm/payment-action.js
26360
+ var A2UI_PAYMENT_ACTION_NAME = "payment.trade-pay";
26361
+ var A2UI_PAYMENT_CALLBACK_MAX_BYTES = 16 * 1024;
26362
+ function getA2UICardActionBindingIssues(value, path22 = "actionBindings") {
26363
+ if (!Array.isArray(value)) {
26364
+ return [issue2(path22, "INVALID_ACTION_BINDINGS", "actionBindings must be an array")];
26365
+ }
26366
+ const issues = [];
26367
+ const actionIds = /* @__PURE__ */ new Set();
26368
+ value.forEach((candidate, index) => {
26369
+ const itemPath = `${path22}[${index}]`;
26370
+ if (!isPlainRecord(candidate)) {
26371
+ issues.push(issue2(itemPath, "INVALID_ACTION_BINDING", "action binding must be an object"));
26372
+ return;
26373
+ }
26374
+ if (!isSafeIdentifier(candidate.actionId)) {
26375
+ issues.push(issue2(`${itemPath}.actionId`, "INVALID_ACTION_ID", "actionId is invalid"));
26376
+ } else if (actionIds.has(candidate.actionId)) {
26377
+ issues.push(issue2(`${itemPath}.actionId`, "DUPLICATE_ACTION_ID", "actionId must be unique"));
26378
+ } else {
26379
+ actionIds.add(candidate.actionId);
26380
+ }
26381
+ if (candidate.kind !== "payment") {
26382
+ issues.push(issue2(`${itemPath}.kind`, "INVALID_ACTION_KIND", "kind must be payment"));
26383
+ return;
26384
+ }
26385
+ rejectExtraKeys(candidate, ["actionId", "kind", "createTrade", "queryTrade"], itemPath, issues);
26386
+ validateToolBinding(candidate.createTrade, `${itemPath}.createTrade`, "create", issues);
26387
+ validateToolBinding(candidate.queryTrade, `${itemPath}.queryTrade`, "query", issues);
26388
+ });
26389
+ return issues;
26390
+ }
26391
+ function getA2UIPaymentActionReferenceIssues(card, bindings, path22 = "a2uiTemplate.content") {
26392
+ const issues = [];
26393
+ const bindingIds = new Set(bindings.map((binding) => binding.actionId));
26394
+ const referenced = /* @__PURE__ */ new Set();
26395
+ card.forEach((instruction, instructionIndex) => {
26396
+ const instructionRecord = instruction;
26397
+ const update = isPlainRecord(instructionRecord.updateComponents) ? instructionRecord.updateComponents : void 0;
26398
+ const components = update && Array.isArray(update.components) ? update.components : [];
26399
+ components.forEach((component, componentIndex) => {
26400
+ if (!isPlainRecord(component))
26401
+ return;
26402
+ collectPaymentActionReferences(component.action, `${path22}[${instructionIndex}].updateComponents.components[${componentIndex}].action`, referenced, issues);
26403
+ });
26404
+ });
26405
+ for (const actionId of referenced) {
26406
+ if (!bindingIds.has(actionId)) {
26407
+ issues.push(issue2(path22, "PAYMENT_ACTION_BINDING_MISSING", `payment action ${actionId} has no action binding`));
26408
+ }
26409
+ }
26410
+ for (const actionId of bindingIds) {
26411
+ if (!referenced.has(actionId)) {
26412
+ issues.push(issue2(path22, "PAYMENT_ACTION_UNUSED", `payment action binding ${actionId} is not referenced`));
26413
+ }
26414
+ }
26415
+ return issues;
26416
+ }
26417
+ function validateToolBinding(value, path22, phase, issues) {
26418
+ if (!isPlainRecord(value)) {
26419
+ issues.push(issue2(path22, "INVALID_PAYMENT_TOOL", `${path22} must be an object`));
26420
+ return;
26421
+ }
26422
+ rejectExtraKeys(value, phase === "create" ? ["mcpResourceId", "toolName", "inputBindings", "tradeNoPath"] : ["mcpResourceId", "toolName", "inputBindings", "statusPath", "paidValues"], path22, issues);
26423
+ if (!isSafeIdentifier(value.mcpResourceId)) {
26424
+ issues.push(issue2(`${path22}.mcpResourceId`, "INVALID_MCP_RESOURCE_ID", "mcpResourceId is invalid"));
26425
+ }
26426
+ if (typeof value.toolName !== "string" || !value.toolName.trim()) {
26427
+ issues.push(issue2(`${path22}.toolName`, "INVALID_TOOL_NAME", "toolName is required"));
26428
+ }
26429
+ validateInputBindings2(value.inputBindings, `${path22}.inputBindings`, phase, issues);
26430
+ if (phase === "create") {
26431
+ if (!isSafeJsonPointer(value.tradeNoPath)) {
26432
+ issues.push(issue2(`${path22}.tradeNoPath`, "INVALID_TRADE_NO_PATH", "tradeNoPath must be an absolute JSON Pointer"));
26433
+ }
26434
+ return;
26435
+ }
26436
+ if (!isSafeJsonPointer(value.statusPath)) {
26437
+ issues.push(issue2(`${path22}.statusPath`, "INVALID_PAYMENT_STATUS_PATH", "statusPath must be an absolute JSON Pointer"));
26438
+ }
26439
+ if (!Array.isArray(value.inputBindings) || !value.inputBindings.some((binding) => isPlainRecord(binding) && isPlainRecord(binding.source) && binding.source.kind === "trade-no")) {
26440
+ issues.push(issue2(`${path22}.inputBindings`, "PAYMENT_TRADE_NO_BINDING_MISSING", "queryTrade must map tradeNO"));
26441
+ }
26442
+ if (!Array.isArray(value.paidValues) || value.paidValues.length === 0 || value.paidValues.some((entry) => !isJsonPrimitive(entry))) {
26443
+ issues.push(issue2(`${path22}.paidValues`, "INVALID_PAID_VALUES", "paidValues must contain JSON primitives"));
26444
+ }
26445
+ }
26446
+ function collectPaymentActionReferences(value, path22, referenced, issues) {
26447
+ if (!isPlainRecord(value))
26448
+ return;
26449
+ if (isPlainRecord(value.client) && value.client.name === A2UI_PAYMENT_ACTION_NAME) {
26450
+ const context = value.client.context;
26451
+ if (!isPlainRecord(context) || !isSafeIdentifier(context.actionId)) {
26452
+ issues.push(issue2(path22, "INVALID_PAYMENT_ACTION", "payment action requires a safe actionId"));
26453
+ return;
26454
+ }
26455
+ rejectExtraKeys(context, ["actionId", "actionContext"], `${path22}.client.context`, issues);
26456
+ if (!isPlainRecord(context.actionContext)) {
26457
+ issues.push(issue2(path22, "INVALID_PAYMENT_ACTION", "payment action requires an object actionContext"));
26458
+ }
26459
+ referenced.add(context.actionId);
26460
+ return;
26461
+ }
26462
+ if (!isPlainRecord(value.select))
26463
+ return;
26464
+ if (Array.isArray(value.select.cases)) {
26465
+ value.select.cases.forEach((entry, index) => {
26466
+ if (isPlainRecord(entry)) {
26467
+ collectPaymentActionReferences(entry.action, `${path22}.select.cases[${index}].action`, referenced, issues);
26468
+ }
26469
+ });
26470
+ }
26471
+ collectPaymentActionReferences(value.select.default, `${path22}.select.default`, referenced, issues);
26472
+ }
26473
+ function validateInputBindings2(value, path22, phase, issues) {
26474
+ if (!Array.isArray(value) || value.length > 128) {
26475
+ issues.push(issue2(path22, "INVALID_PAYMENT_INPUT_BINDINGS", "inputBindings must be an array of at most 128 entries"));
26476
+ return;
26477
+ }
26478
+ const targets = /* @__PURE__ */ new Set();
26479
+ value.forEach((candidate, index) => {
26480
+ const itemPath = `${path22}[${index}]`;
26481
+ if (!isPlainRecord(candidate) || !isSafeJsonPointer(candidate.inputPath)) {
26482
+ issues.push(issue2(itemPath, "INVALID_PAYMENT_INPUT_BINDING", "inputPath must be an absolute JSON Pointer"));
26483
+ return;
26484
+ }
26485
+ rejectExtraKeys(candidate, ["inputPath", "source"], itemPath, issues);
26486
+ if (targets.has(candidate.inputPath)) {
26487
+ issues.push(issue2(`${itemPath}.inputPath`, "DUPLICATE_PAYMENT_INPUT_PATH", "inputPath must be unique"));
26488
+ }
26489
+ targets.add(candidate.inputPath);
26490
+ validateInputSource(candidate.source, `${itemPath}.source`, phase, issues);
26491
+ });
26492
+ }
26493
+ function validateInputSource(value, path22, phase, issues) {
26494
+ if (!isPlainRecord(value) || typeof value.kind !== "string") {
26495
+ issues.push(issue2(path22, "INVALID_PAYMENT_INPUT_SOURCE", "source must be an object"));
26496
+ return;
26497
+ }
26498
+ if (value.kind === "action-context") {
26499
+ rejectExtraKeys(value, ["kind", "path"], path22, issues);
26500
+ if (!isSafeJsonPointer(value.path, true)) {
26501
+ issues.push(issue2(`${path22}.path`, "INVALID_PAYMENT_SOURCE_PATH", "action context path must be a JSON Pointer"));
26502
+ }
26503
+ return;
26504
+ }
26505
+ if (value.kind === "runtime-context") {
26506
+ rejectExtraKeys(value, ["kind", "key"], path22, issues);
26507
+ if (!RUNTIME_CONTEXT_KEYS2.has(String(value.key))) {
26508
+ issues.push(issue2(`${path22}.key`, "INVALID_RUNTIME_CONTEXT_KEY", "runtime context key is unsupported"));
26509
+ }
26510
+ return;
26511
+ }
26512
+ if (value.kind === "literal") {
26513
+ rejectExtraKeys(value, ["kind", "value"], path22, issues);
26514
+ if (!isJsonValue(value.value)) {
26515
+ issues.push(issue2(`${path22}.value`, "INVALID_PAYMENT_LITERAL", "literal must be JSON"));
26516
+ }
26517
+ return;
26518
+ }
26519
+ if (phase === "query" && value.kind === "trade-no") {
26520
+ rejectExtraKeys(value, ["kind"], path22, issues);
26521
+ return;
26522
+ }
26523
+ if (phase === "query" && value.kind === "callback") {
26524
+ rejectExtraKeys(value, ["kind", "path"], path22, issues);
26525
+ if (!isSafeJsonPointer(value.path, true)) {
26526
+ issues.push(issue2(`${path22}.path`, "INVALID_PAYMENT_CALLBACK_PATH", "callback path must be a JSON Pointer"));
26527
+ }
26528
+ return;
26529
+ }
26530
+ issues.push(issue2(path22, "INVALID_PAYMENT_INPUT_SOURCE", `${value.kind} is unavailable for ${phase}`));
26531
+ }
26532
+ function rejectExtraKeys(value, allowed, path22, issues) {
26533
+ const allowedKeys = new Set(allowed);
26534
+ for (const key of Object.keys(value)) {
26535
+ if (!allowedKeys.has(key)) {
26536
+ issues.push(issue2(`${path22}.${key}`, "UNEXPECTED_ACTION_BINDING_FIELD", `${key} is not allowed`));
26537
+ }
26538
+ }
26539
+ }
26540
+ function isJsonPrimitive(value) {
26541
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
26542
+ }
26543
+ var RUNTIME_CONTEXT_KEYS2 = /* @__PURE__ */ new Set(["agentId", "tenantId", "threadId", "userId"]);
26544
+
26303
26545
  // ../agent-a2ui-card-contract/dist/esm/validation.js
26304
26546
  var FIELD_TYPES = [
26305
26547
  "array",
@@ -26315,7 +26557,7 @@ var FIELD_SOURCES = [
26315
26557
  "runtime-context",
26316
26558
  "local"
26317
26559
  ];
26318
- var RUNTIME_CONTEXT_KEYS2 = /* @__PURE__ */ new Set([
26560
+ var RUNTIME_CONTEXT_KEYS3 = /* @__PURE__ */ new Set([
26319
26561
  "agentId",
26320
26562
  "tenantId",
26321
26563
  "threadId",
@@ -26339,11 +26581,14 @@ function getA2UICardDraftBundleIssues(value) {
26339
26581
  }
26340
26582
  const issues = [];
26341
26583
  exactKeys(value, ["manifest", "card", "fixture"], "draft", issues);
26342
- validateManifest(value.manifest, "draft.manifest", issues);
26584
+ const manifest = validateManifest(value.manifest, "draft.manifest", issues);
26343
26585
  const card = validateProtocol(value.card, "draft.card", issues);
26344
26586
  if (card) {
26345
26587
  const analysis = analyzeA2UIBindings(card);
26346
26588
  issues.push(...analysis.issues);
26589
+ if (manifest?.schemaVersion === "3") {
26590
+ issues.push(...getA2UIPaymentActionReferenceIssues(card, manifest.actionBindings ?? [], "draft.card"));
26591
+ }
26347
26592
  }
26348
26593
  return issues;
26349
26594
  }
@@ -26394,9 +26639,9 @@ function getA2UICardResourceConfigIssues(value) {
26394
26639
  validateLooseJsonSchema(value.presentationInputSchema, "config.presentationInputSchema", issues);
26395
26640
  const fields = validateFields(value.fields, "config.fields", issues);
26396
26641
  const resolver = validateResolver(value.resolver, "config.resolver", issues);
26397
- validateTemplate(value.a2uiTemplate, fields, issues);
26642
+ const card = validateTemplate(value.a2uiTemplate, fields, issues);
26398
26643
  if (fields && resolver)
26399
- validateResolverCoverage(fields, resolver, issues);
26644
+ validateResolverCoverage(fields, resolver, issues, card);
26400
26645
  return issues;
26401
26646
  }
26402
26647
  function getA2UICardResourceConfigV3Issues(value) {
@@ -26411,6 +26656,7 @@ function getA2UICardResourceConfigV3Issues(value) {
26411
26656
  "fields",
26412
26657
  "dataSources",
26413
26658
  "bindings",
26659
+ "actionBindings",
26414
26660
  "a2uiTemplate"
26415
26661
  ], "config", issues);
26416
26662
  if (value.schemaVersion !== A2UI_CARD_SCHEMA_VERSION) {
@@ -26429,9 +26675,14 @@ function getA2UICardResourceConfigV3Issues(value) {
26429
26675
  }
26430
26676
  const dataSources = validateDataSources(value.dataSources, "config.dataSources", issues);
26431
26677
  const bindings = validateV3OutputBindings(value.bindings, "config.bindings", issues);
26432
- validateTemplate(value.a2uiTemplate, fields, issues);
26678
+ const card = validateTemplate(value.a2uiTemplate, fields, issues);
26679
+ const actionBindings = value.actionBindings === void 0 ? [] : value.actionBindings;
26680
+ issues.push(...getA2UICardActionBindingIssues(actionBindings, "config.actionBindings"));
26681
+ if (card && Array.isArray(actionBindings)) {
26682
+ issues.push(...getA2UIPaymentActionReferenceIssues(card, actionBindings, "config.a2uiTemplate.content"));
26683
+ }
26433
26684
  if (fields && dataSources && bindings) {
26434
- validateV3Coverage(fields, dataSources, bindings, issues);
26685
+ validateV3Coverage(fields, dataSources, bindings, issues, { card });
26435
26686
  }
26436
26687
  return issues;
26437
26688
  }
@@ -26545,7 +26796,8 @@ function validateV3Coverage(fields, dataSources, bindings, issues, options = {})
26545
26796
  bound.add(binding.fieldId);
26546
26797
  });
26547
26798
  if (options.requireComplete !== false) {
26548
- for (const fieldId of businessFields) {
26799
+ const requiredFields = options.card ? collectReferencedA2UICardBusinessFieldIds(options.card, fields.filter((field) => field.required)) : new Set(fields.filter((field) => field.source === "business" && field.required).map((field) => field.fieldId));
26800
+ for (const fieldId of requiredFields) {
26549
26801
  if (bound.has(fieldId))
26550
26802
  continue;
26551
26803
  issues.push(issue2("config.bindings", "CARD_BINDING_INCOMPLETE", `business field ${fieldId} is not bound`));
@@ -26634,7 +26886,8 @@ function validateManifest(value, path22, issues) {
26634
26886
  "whenToUse",
26635
26887
  "whenNotToUse",
26636
26888
  "fields",
26637
- "bindings"
26889
+ "bindings",
26890
+ "actionBindings"
26638
26891
  ], path22, issues);
26639
26892
  if (!isSafeIdentifier(value.draftId)) {
26640
26893
  issues.push(issue2(`${path22}.draftId`, "INVALID_DRAFT_ID", "draftId is invalid"));
@@ -26654,6 +26907,9 @@ function validateManifest(value, path22, issues) {
26654
26907
  if (value.bindings !== void 0) {
26655
26908
  validateV3BindingSelections(value.bindings, `${path22}.bindings`, fields2, issues);
26656
26909
  }
26910
+ if (value.actionBindings !== void 0) {
26911
+ issues.push(...getA2UICardActionBindingIssues(value.actionBindings, `${path22}.actionBindings`));
26912
+ }
26657
26913
  return fields2 ? value : void 0;
26658
26914
  }
26659
26915
  exactKeys(value, [
@@ -26761,7 +27017,7 @@ function validateFieldSource(candidate, itemPath, issues) {
26761
27017
  issues.push(issue2(`${itemPath}.sourcePath`, "UNEXPECTED_FIELD_SOURCE_PATH", "sourcePath is only allowed for invocation fields"));
26762
27018
  }
26763
27019
  if (candidate.source === "runtime-context") {
26764
- if (!RUNTIME_CONTEXT_KEYS2.has(candidate.runtimeContextKey)) {
27020
+ if (!RUNTIME_CONTEXT_KEYS3.has(candidate.runtimeContextKey)) {
26765
27021
  issues.push(issue2(`${itemPath}.runtimeContextKey`, "INVALID_RUNTIME_CONTEXT", "runtime-context fields require an allowed runtimeContextKey"));
26766
27022
  }
26767
27023
  } else if (candidate.runtimeContextKey !== void 0) {
@@ -26813,11 +27069,11 @@ function validateResolverCalls(value, path22, issues) {
26813
27069
  issues.push(issue2(`${itemPath}.mcpResourceId`, "INVALID_MCP_RESOURCE_ID", "mcpResourceId is invalid"));
26814
27070
  }
26815
27071
  requiredString(candidate.toolName, `${itemPath}.toolName`, issues);
26816
- validateInputBindings2(candidate.inputBindings, `${itemPath}.inputBindings`, issues);
27072
+ validateInputBindings3(candidate.inputBindings, `${itemPath}.inputBindings`, issues);
26817
27073
  });
26818
27074
  return value;
26819
27075
  }
26820
- function validateInputBindings2(value, path22, issues) {
27076
+ function validateInputBindings3(value, path22, issues) {
26821
27077
  if (!Array.isArray(value)) {
26822
27078
  issues.push(issue2(path22, "INVALID_INPUT_BINDINGS", "inputBindings must be an array"));
26823
27079
  return void 0;
@@ -26837,11 +27093,11 @@ function validateInputBindings2(value, path22, issues) {
26837
27093
  } else {
26838
27094
  paths.add(candidate.inputPath);
26839
27095
  }
26840
- validateInputSource(candidate.source, `${itemPath}.source`, issues);
27096
+ validateInputSource2(candidate.source, `${itemPath}.source`, issues);
26841
27097
  });
26842
27098
  return value;
26843
27099
  }
26844
- function validateInputSource(value, path22, issues) {
27100
+ function validateInputSource2(value, path22, issues) {
26845
27101
  if (!isPlainRecord(value)) {
26846
27102
  issues.push(issue2(path22, "INVALID_INPUT_SOURCE", "source must be an object"));
26847
27103
  return;
@@ -26855,7 +27111,7 @@ function validateInputSource(value, path22, issues) {
26855
27111
  }
26856
27112
  if (value.kind === "runtime-context") {
26857
27113
  exactKeys(value, ["kind", "key"], path22, issues);
26858
- if (!RUNTIME_CONTEXT_KEYS2.has(value.key)) {
27114
+ if (!RUNTIME_CONTEXT_KEYS3.has(value.key)) {
26859
27115
  issues.push(issue2(`${path22}.key`, "INVALID_RUNTIME_CONTEXT", "runtime context key is invalid"));
26860
27116
  }
26861
27117
  return;
@@ -26983,13 +27239,13 @@ function validateInstructionShape(instruction, path22, issues) {
26983
27239
  function validateTemplate(value, fields, issues) {
26984
27240
  if (!isPlainRecord(value)) {
26985
27241
  issues.push(issue2("config.a2uiTemplate", "INVALID_A2UI_TEMPLATE", "a2uiTemplate must be an object"));
26986
- return;
27242
+ return void 0;
26987
27243
  }
26988
27244
  exactKeys(value, ["argsSchema", "content"], "config.a2uiTemplate", issues);
26989
27245
  validateLooseJsonSchema(value.argsSchema, "config.a2uiTemplate.argsSchema", issues);
26990
27246
  if (typeof value.content !== "string") {
26991
27247
  issues.push(issue2("config.a2uiTemplate.content", "INVALID_A2UI_CONTENT", "a2uiTemplate.content must be serialized A2UI JSON"));
26992
- return;
27248
+ return void 0;
26993
27249
  }
26994
27250
  try {
26995
27251
  const card = validateProtocol(JSON.parse(value.content), "config.a2uiTemplate.content", issues);
@@ -26997,9 +27253,11 @@ function validateTemplate(value, fields, issues) {
26997
27253
  const analysis = analyzeA2UIBindings(card);
26998
27254
  issues.push(...analysis.issues);
26999
27255
  }
27256
+ return card;
27000
27257
  } catch {
27001
27258
  issues.push(issue2("config.a2uiTemplate.content", "INVALID_A2UI_CONTENT", "a2uiTemplate.content must contain valid JSON"));
27002
27259
  }
27260
+ return void 0;
27003
27261
  }
27004
27262
  function validateLooseJsonSchema(value, path22, issues) {
27005
27263
  const schemaIssues = getJsonSchemaIssues(value, path22, {
@@ -27008,7 +27266,7 @@ function validateLooseJsonSchema(value, path22, issues) {
27008
27266
  issues.push(...schemaIssues);
27009
27267
  return schemaIssues.length === 0;
27010
27268
  }
27011
- function validateResolverCoverage(fields, resolver, issues) {
27269
+ function validateResolverCoverage(fields, resolver, issues, card) {
27012
27270
  const businessFields = new Set(fields.filter((field) => field.source === "business").map((field) => field.fieldId));
27013
27271
  const bound = /* @__PURE__ */ new Set();
27014
27272
  resolver.outputBindings.forEach((binding, index) => {
@@ -27017,7 +27275,8 @@ function validateResolverCoverage(fields, resolver, issues) {
27017
27275
  }
27018
27276
  bound.add(binding.fieldId);
27019
27277
  });
27020
- for (const fieldId of businessFields) {
27278
+ const requiredFields = card ? collectReferencedA2UICardBusinessFieldIds(card, fields.filter((field) => field.required)) : new Set(fields.filter((field) => field.source === "business" && field.required).map((field) => field.fieldId));
27279
+ for (const fieldId of requiredFields) {
27021
27280
  if (!bound.has(fieldId)) {
27022
27281
  issues.push(issue2("config.resolver.outputBindings", "CARD_BINDING_INCOMPLETE", `business field ${fieldId} is not bound`));
27023
27282
  }
@@ -27065,6 +27324,7 @@ function compileA2UICardResource(input) {
27065
27324
  }
27066
27325
  const fieldById = new Map(draft.manifest.fields.map((field) => [field.fieldId, field]));
27067
27326
  const selectionByField = /* @__PURE__ */ new Map();
27327
+ const requiredFields = collectReferencedA2UICardBusinessFieldIds(draft.card, draft.manifest.fields.filter((field) => field.required));
27068
27328
  for (const selection of input.bindings) {
27069
27329
  const field = fieldById.get(selection.fieldId);
27070
27330
  if (!field || field.source !== "business") {
@@ -27078,9 +27338,9 @@ function compileA2UICardResource(input) {
27078
27338
  }
27079
27339
  selectionByField.set(selection.fieldId, cloneSelection(selection));
27080
27340
  }
27081
- for (const field of draft.manifest.fields) {
27082
- if (field.source === "business" && !selectionByField.has(field.fieldId)) {
27083
- throw new A2UICardCompileError("CARD_BINDING_INCOMPLETE", `Business field ${field.fieldId} has not been bound`);
27341
+ for (const fieldId of requiredFields) {
27342
+ if (!selectionByField.has(fieldId)) {
27343
+ throw new A2UICardCompileError("CARD_BINDING_INCOMPLETE", `Required referenced business field ${fieldId} has not been bound`);
27084
27344
  }
27085
27345
  }
27086
27346
  const resolver = compileResolver([...selectionByField.values()]);
@@ -27172,6 +27432,7 @@ function compileV3Resource(draft, revision, bindings, tools) {
27172
27432
  const sourceById = /* @__PURE__ */ new Map();
27173
27433
  const outputBindings = [];
27174
27434
  const boundFields = /* @__PURE__ */ new Set();
27435
+ const requiredFields = collectReferencedA2UICardBusinessFieldIds(draft.card, draft.manifest.fields.filter((field) => field.required));
27175
27436
  for (const binding of bindings) {
27176
27437
  const field = fieldById.get(binding.fieldId);
27177
27438
  if (!field || field.source !== "business" || boundFields.has(binding.fieldId)) {
@@ -27199,9 +27460,9 @@ function compileV3Resource(draft, revision, bindings, tools) {
27199
27460
  transform: binding.transform
27200
27461
  });
27201
27462
  }
27202
- for (const field of draft.manifest.fields) {
27203
- if (field.source === "business" && !boundFields.has(field.fieldId)) {
27204
- throw new A2UICardCompileError("CARD_BINDING_INCOMPLETE", `Business field ${field.fieldId} has not been bound`);
27463
+ for (const fieldId of requiredFields) {
27464
+ if (!boundFields.has(fieldId)) {
27465
+ throw new A2UICardCompileError("CARD_BINDING_INCOMPLETE", `Required referenced business field ${fieldId} has not been bound`);
27205
27466
  }
27206
27467
  }
27207
27468
  const resource = {
@@ -27214,6 +27475,9 @@ function compileV3Resource(draft, revision, bindings, tools) {
27214
27475
  fields: draft.manifest.fields,
27215
27476
  dataSources: [...sourceById.values()].sort((left, right) => left.sourceId.localeCompare(right.sourceId)),
27216
27477
  bindings: outputBindings.sort((left, right) => left.fieldId.localeCompare(right.fieldId)),
27478
+ ...draft.manifest.actionBindings?.length ? {
27479
+ actionBindings: JSON.parse(JSON.stringify(draft.manifest.actionBindings))
27480
+ } : {},
27217
27481
  a2uiTemplate: {
27218
27482
  argsSchema: createA2UICardViewModelSchema(draft.manifest.fields),
27219
27483
  content: stableJsonStringify(draft.card)
@@ -27404,9 +27668,6 @@ function createA2UICardDraftStructureChecksum(draft) {
27404
27668
  delete cloned.manifest.bindings;
27405
27669
  return sha256Hex(stableJsonStringify(cloned));
27406
27670
  }
27407
- function createA2UICardMcpConfigChecksum(resources) {
27408
- return sha256Hex(stableJsonStringify(resources));
27409
- }
27410
27671
  function createA2UICardRevision(draftChecksum2, bindings) {
27411
27672
  const digest = sha256Hex(`${draftChecksum2}
27412
27673
  ${stableJsonStringify(bindings)}`);
@@ -27485,6 +27746,410 @@ var SHA256_CONSTANTS = [
27485
27746
  3329325298
27486
27747
  ];
27487
27748
 
27749
+ // ../agent-a2ui-card-contract/dist/esm/authoring-model.js
27750
+ var INTERNAL_KEYS = /* @__PURE__ */ new Set([
27751
+ "__proto__",
27752
+ "constructor",
27753
+ "prototype",
27754
+ "id",
27755
+ "component",
27756
+ "surfaceId",
27757
+ "version",
27758
+ "child",
27759
+ "children"
27760
+ ]);
27761
+ var MERCHANT_CONTENT_KEYS = /* @__PURE__ */ new Set([
27762
+ "caption",
27763
+ "description",
27764
+ "label",
27765
+ "placeholder",
27766
+ "subtitle",
27767
+ "text",
27768
+ "title"
27769
+ ]);
27770
+ var DEFAULT_AUTHORING_PROJECTION = {
27771
+ directTargets: "content"
27772
+ };
27773
+ var COMPONENT_AUTHORING_PROJECTIONS = {
27774
+ Image: {
27775
+ directTargets: "image-source",
27776
+ fieldProperties: ["src"],
27777
+ groupLabel: "\u56FE\u7247"
27778
+ }
27779
+ };
27780
+ function extractA2UICardAuthoringModel(bundle, options = {}) {
27781
+ const analysis = analyzeA2UIBindings(bundle.card);
27782
+ const bindingByField = new Map((bundle.manifest.bindings ?? []).map((binding) => [
27783
+ binding.fieldId,
27784
+ binding
27785
+ ]));
27786
+ const fields = bundle.manifest.fields.filter((field) => field.source === "business");
27787
+ const registered = options.registeredComponents ? new Set(options.registeredComponents) : void 0;
27788
+ const occurrencesByComponent = groupBindingOccurrences(analysis.occurrences);
27789
+ const allFieldOccurrenceTargets = [];
27790
+ const groups = [];
27791
+ let order = 0;
27792
+ for (const { surfaceId, component } of orderedComponents(bundle)) {
27793
+ const occurrenceId = createA2UICardAuthoringOccurrenceId(surfaceId, component.id);
27794
+ const targets = [];
27795
+ if (!registered || registered.has(component.component)) {
27796
+ const projection = componentAuthoringProjection(component);
27797
+ const componentOccurrences = occurrencesByComponent.get(componentKey(surfaceId, component.id)) ?? [];
27798
+ discoverComponentTargets(component, occurrenceId, projection, targets);
27799
+ allFieldOccurrenceTargets.push(...fieldTargets(component, occurrenceId, fields, componentOccurrences, false));
27800
+ targets.push(...fieldTargets(component, occurrenceId, fields, componentOccurrences, true));
27801
+ targets.push(...actionTargets(component, occurrenceId, bundle.manifest.schemaVersion === "3" ? bundle.manifest.actionBindings : void 0));
27802
+ }
27803
+ if (targets.length === 0) {
27804
+ targets.push({
27805
+ kind: "agent-only",
27806
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "agent-only"),
27807
+ occurrenceId,
27808
+ label: "\u901A\u8FC7 Coding Agent \u4FEE\u6539",
27809
+ reason: registered && !registered.has(component.component) ? "\u7EC4\u4EF6\u672A\u5728\u5F53\u524D Catalog \u6CE8\u518C" : "\u6CA1\u6709\u53EF\u5B89\u5168\u76F4\u63A5\u7F16\u8F91\u7684\u4E1A\u52A1\u5B57\u6BB5"
27810
+ });
27811
+ }
27812
+ groups.push({
27813
+ occurrenceId,
27814
+ surfaceId,
27815
+ componentId: component.id,
27816
+ componentName: component.component,
27817
+ order: order++,
27818
+ label: componentAuthoringProjection(component).groupLabel ?? componentLabel(component),
27819
+ targets: deduplicateTargets(targets),
27820
+ agentOnly: targets.every((target) => target.kind === "agent-only")
27821
+ });
27822
+ }
27823
+ const fieldStates = fields.map((field) => {
27824
+ const occurrences = allFieldOccurrenceTargets.filter((target) => target.fieldId === field.fieldId);
27825
+ const binding = bindingByField.get(field.fieldId);
27826
+ return {
27827
+ field,
27828
+ ...binding ? { binding: cloneBinding(binding) } : {},
27829
+ occurrences,
27830
+ usageCount: new Set(occurrences.map((target) => `${target.occurrenceId}\0${target.propertyPath}`)).size,
27831
+ status: occurrences.length === 0 ? "unused" : binding ? "bound" : field.required ? "unbound-required" : "unbound-optional"
27832
+ };
27833
+ });
27834
+ const diagnostics = getA2UICardAuthoringDiagnostics(bundle, {
27835
+ groups,
27836
+ fields: fieldStates
27837
+ });
27838
+ return {
27839
+ draftId: bundle.manifest.draftId,
27840
+ resourceId: bundle.manifest.resourceId,
27841
+ groups,
27842
+ fields: fieldStates,
27843
+ diagnostics
27844
+ };
27845
+ }
27846
+ function componentAuthoringProjection(component) {
27847
+ return COMPONENT_AUTHORING_PROJECTIONS[component.component] ?? DEFAULT_AUTHORING_PROJECTION;
27848
+ }
27849
+ function discoverComponentTargets(component, occurrenceId, projection, targets) {
27850
+ if (projection.directTargets === "content") {
27851
+ discoverTargets(component, occurrenceId, "", targets);
27852
+ return;
27853
+ }
27854
+ if (typeof component.src !== "string")
27855
+ return;
27856
+ targets.push({
27857
+ kind: "image",
27858
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "src"),
27859
+ occurrenceId,
27860
+ propertyPath: "src",
27861
+ label: "\u56FE\u7247\u5730\u5740",
27862
+ value: component.src,
27863
+ role: "src"
27864
+ });
27865
+ }
27866
+ function getA2UICardAuthoringDiagnostics(bundle, partial) {
27867
+ const diagnostics = getA2UICardDraftBundleIssues(bundle).map((entry) => ({
27868
+ ...entry,
27869
+ severity: "hard-error"
27870
+ }));
27871
+ const model = partial ?? extractA2UICardAuthoringModel(bundle);
27872
+ for (const field of model.fields) {
27873
+ if (field.status === "unbound-required") {
27874
+ diagnostics.push({
27875
+ code: "REQUIRED_BINDING_MISSING",
27876
+ path: `manifest.fields.${field.field.fieldId}`,
27877
+ message: `\u5FC5\u586B\u5B57\u6BB5\u201C${field.field.label}\u201D\u5C1A\u672A\u7ED1\u5B9A`,
27878
+ severity: "hard-error",
27879
+ fieldId: field.field.fieldId
27880
+ });
27881
+ }
27882
+ }
27883
+ for (const group of model.groups) {
27884
+ const component = findComponent(bundle, group.surfaceId, group.componentId);
27885
+ const action = isPlainRecord(component?.action) ? component.action : void 0;
27886
+ const client = action && isPlainRecord(action.client) ? action.client : void 0;
27887
+ if (client?.name === "ui.page.open") {
27888
+ const context = isPlainRecord(client.context) ? client.context : void 0;
27889
+ if (typeof context?.url === "string" && !isSafePageUrl(context.url)) {
27890
+ diagnostics.push({
27891
+ code: "UNSAFE_PAGE_URL",
27892
+ path: `${group.occurrenceId}.action.client.context.url`,
27893
+ message: "\u9875\u9762\u5730\u5740\u5FC5\u987B\u662F\u5B89\u5168\u7684 http(s) \u5730\u5740\u6216\u7AD9\u5185\u7EDD\u5BF9\u8DEF\u5F84",
27894
+ severity: "hard-error",
27895
+ occurrenceId: group.occurrenceId
27896
+ });
27897
+ }
27898
+ } else if (client && typeof client.name === "string" && !["ui.overlay.close", "ui.surface.open"].includes(client.name)) {
27899
+ diagnostics.push({
27900
+ code: "UNREGISTERED_CLIENT_ACTION",
27901
+ path: `${group.occurrenceId}.action.client.name`,
27902
+ message: `\u672A\u6CE8\u518C\u7684 Client Action\uFF1A${client.name}`,
27903
+ severity: "hard-error",
27904
+ occurrenceId: group.occurrenceId
27905
+ });
27906
+ }
27907
+ }
27908
+ return deduplicateDiagnostics(diagnostics);
27909
+ }
27910
+ function validateA2UICardForPublish(bundle) {
27911
+ return getA2UICardAuthoringDiagnostics(bundle).filter((diagnostic) => diagnostic.severity === "hard-error");
27912
+ }
27913
+ function createA2UICardAuthoringOccurrenceId(surfaceId, componentId) {
27914
+ return `${encodeURIComponent(surfaceId)}:${encodeURIComponent(componentId)}`;
27915
+ }
27916
+ function createA2UICardAuthoringTargetId(occurrenceId, propertyPath) {
27917
+ return `${occurrenceId}:${encodeURIComponent(propertyPath)}`;
27918
+ }
27919
+ function isSafePageUrl(url) {
27920
+ const trimmed = url.trim();
27921
+ if (trimmed.length === 0 || trimmed.length > 2e3)
27922
+ return false;
27923
+ const token = /\{\{([^{}]+)\}\}/gu;
27924
+ let tokenCount = 0;
27925
+ for (const match of trimmed.matchAll(token)) {
27926
+ tokenCount++;
27927
+ if (tokenCount > 32)
27928
+ return false;
27929
+ const path22 = match[1]?.trim() ?? "";
27930
+ if (!(path22 === "/" || path22.startsWith("/") || path22.startsWith("./")) || path22.split("/").some((part) => ["..", "__proto__", "constructor", "prototype"].includes(part))) {
27931
+ return false;
27932
+ }
27933
+ }
27934
+ const candidate = trimmed.replace(token, "value");
27935
+ if (candidate.includes("{{") || candidate.includes("}}"))
27936
+ return false;
27937
+ try {
27938
+ return ["https:", "alipays:"].includes(new URL(candidate).protocol);
27939
+ } catch {
27940
+ return false;
27941
+ }
27942
+ }
27943
+ function orderedComponents(bundle) {
27944
+ const result = [];
27945
+ for (const message of bundle.card) {
27946
+ for (const instruction of message.datas) {
27947
+ if (!("updateComponents" in instruction))
27948
+ continue;
27949
+ for (const component of instruction.updateComponents.components) {
27950
+ result.push({
27951
+ surfaceId: instruction.updateComponents.surfaceId,
27952
+ component
27953
+ });
27954
+ }
27955
+ }
27956
+ }
27957
+ return result;
27958
+ }
27959
+ function discoverTargets(value, occurrenceId, propertyPath, targets) {
27960
+ if (Array.isArray(value)) {
27961
+ value.forEach((entry, index) => discoverTargets(entry, occurrenceId, appendPath(propertyPath, String(index)), targets));
27962
+ return;
27963
+ }
27964
+ if (!isPlainRecord(value))
27965
+ return;
27966
+ if (typeof value.path === "string")
27967
+ return;
27968
+ for (const [key, entry] of Object.entries(value)) {
27969
+ const nextPath = appendPath(propertyPath, key);
27970
+ if (INTERNAL_KEYS.has(key) || key === "action")
27971
+ continue;
27972
+ if (Array.isArray(entry) || isPlainRecord(entry)) {
27973
+ discoverTargets(entry, occurrenceId, nextPath, targets);
27974
+ continue;
27975
+ }
27976
+ if (!isJsonValue(entry))
27977
+ continue;
27978
+ if (typeof entry !== "string" || !MERCHANT_CONTENT_KEYS.has(key))
27979
+ continue;
27980
+ targets.push({
27981
+ kind: "literal",
27982
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, nextPath),
27983
+ occurrenceId,
27984
+ propertyPath: nextPath,
27985
+ label: propertyLabel(nextPath),
27986
+ value: entry
27987
+ });
27988
+ }
27989
+ }
27990
+ function actionTargets(component, occurrenceId, actionBindings) {
27991
+ if (!isPlainRecord(component.action))
27992
+ return [];
27993
+ const action = component.action;
27994
+ const event = isPlainRecord(action.event) ? action.event : void 0;
27995
+ if (event) {
27996
+ const message = event["x2-user-message"];
27997
+ if (typeof message === "string" && typeof event.name === "string") {
27998
+ return [
27999
+ {
28000
+ kind: "agent-event",
28001
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "action"),
28002
+ occurrenceId,
28003
+ propertyPath: "action",
28004
+ label: "\u70B9\u51FB\u540E",
28005
+ message,
28006
+ eventName: event.name,
28007
+ hasContext: isPlainRecord(event.context)
28008
+ }
28009
+ ];
28010
+ }
28011
+ return [agentOnlyAction(occurrenceId, "\u52A8\u6001\u6216\u590D\u6742\u4E8B\u4EF6\u901A\u8FC7\u667A\u80FD\u4F53\u4FEE\u6539")];
28012
+ }
28013
+ const client = isPlainRecord(action.client) ? action.client : void 0;
28014
+ if (client?.name === "ui.page.open") {
28015
+ const context = isPlainRecord(client.context) ? client.context : void 0;
28016
+ const url = context?.url;
28017
+ return [
28018
+ {
28019
+ kind: "page-open",
28020
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "action"),
28021
+ occurrenceId,
28022
+ propertyPath: "action",
28023
+ label: "\u6253\u5F00\u9875\u9762",
28024
+ ...typeof url === "string" ? { url } : {},
28025
+ dynamic: isPlainRecord(url) && typeof url.path === "string"
28026
+ }
28027
+ ];
28028
+ }
28029
+ if (client?.name === "payment.trade-pay") {
28030
+ const context = isPlainRecord(client.context) ? client.context : void 0;
28031
+ const actionId = typeof context?.actionId === "string" ? context.actionId : "";
28032
+ const actionContext = isPlainRecord(context?.actionContext) ? context.actionContext : {};
28033
+ const binding = actionBindings?.find((candidate) => candidate.kind === "payment" && candidate.actionId === actionId);
28034
+ return [{
28035
+ kind: "payment",
28036
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "action"),
28037
+ occurrenceId,
28038
+ propertyPath: "action",
28039
+ label: "\u652F\u4ED8",
28040
+ actionId,
28041
+ actionContext,
28042
+ ...binding ? { binding } : {},
28043
+ configured: Boolean(actionId && binding)
28044
+ }];
28045
+ }
28046
+ return [agentOnlyAction(occurrenceId, "\u8BE5\u884C\u4E3A\u53EA\u80FD\u901A\u8FC7 Coding Agent \u4FEE\u6539")];
28047
+ }
28048
+ function fieldTargets(component, occurrenceId, fields, occurrences, projectedOnly) {
28049
+ const projection = componentAuthoringProjection(component);
28050
+ const ordered = [...fields].sort((left, right) => right.path.length - left.path.length);
28051
+ return occurrences.flatMap((occurrence) => {
28052
+ if (projectedOnly && !isProjectedFieldOccurrence(occurrence.propertyPath, projection)) {
28053
+ return [];
28054
+ }
28055
+ const field = ordered.find((candidate) => occurrence.canonicalPath === candidate.path || occurrence.canonicalPath.startsWith(`${candidate.path}/*/`) || occurrence.canonicalPath.startsWith(`${candidate.path}/`));
28056
+ if (!field)
28057
+ return [];
28058
+ const suffix = occurrence.canonicalPath.slice(field.path.length).replace(/^\/\*/u, "");
28059
+ const targetPath = suffix ? suffix.startsWith("/") ? suffix : `/${suffix}` : void 0;
28060
+ return [
28061
+ {
28062
+ kind: "field-occurrence",
28063
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, occurrence.propertyPath),
28064
+ occurrenceId,
28065
+ propertyPath: occurrence.propertyPath,
28066
+ label: fieldOccurrenceLabel(targetPath, occurrence.propertyPath),
28067
+ fieldId: field.fieldId,
28068
+ ...targetPath ? { targetPath } : {}
28069
+ }
28070
+ ];
28071
+ });
28072
+ }
28073
+ function isProjectedFieldOccurrence(propertyPath, projection) {
28074
+ if (propertyPath === "action" || propertyPath.startsWith("action.")) {
28075
+ return false;
28076
+ }
28077
+ return projection.fieldProperties === void 0 || projection.fieldProperties.includes(propertyPath);
28078
+ }
28079
+ function fieldOccurrenceLabel(targetPath, propertyPath) {
28080
+ if (!targetPath)
28081
+ return propertyLabel(propertyPath);
28082
+ const segment = targetPath.split("/").filter(Boolean).at(-1);
28083
+ return segment ? decodeJsonPointerSegment(segment) : propertyLabel(propertyPath);
28084
+ }
28085
+ function decodeJsonPointerSegment(segment) {
28086
+ return segment.replace(/~1/gu, "/").replace(/~0/gu, "~");
28087
+ }
28088
+ function groupBindingOccurrences(occurrences) {
28089
+ const result = /* @__PURE__ */ new Map();
28090
+ for (const occurrence of occurrences) {
28091
+ if (!occurrence.surfaceId)
28092
+ continue;
28093
+ const key = componentKey(occurrence.surfaceId, occurrence.componentId);
28094
+ const entries = result.get(key) ?? [];
28095
+ entries.push(occurrence);
28096
+ result.set(key, entries);
28097
+ }
28098
+ return result;
28099
+ }
28100
+ function findComponent(bundle, surfaceId, componentId) {
28101
+ return orderedComponents(bundle).find((entry) => entry.surfaceId === surfaceId && entry.component.id === componentId)?.component;
28102
+ }
28103
+ function agentOnlyAction(occurrenceId, reason) {
28104
+ return {
28105
+ kind: "agent-only",
28106
+ targetId: createA2UICardAuthoringTargetId(occurrenceId, "action"),
28107
+ occurrenceId,
28108
+ propertyPath: "action",
28109
+ label: "\u901A\u8FC7 Coding Agent \u4FEE\u6539\u884C\u4E3A",
28110
+ reason
28111
+ };
28112
+ }
28113
+ function componentLabel(component) {
28114
+ const candidate = component.label ?? component.title ?? component.text;
28115
+ return typeof candidate === "string" && candidate.trim() ? candidate : component.component;
28116
+ }
28117
+ function propertyLabel(propertyPath) {
28118
+ const key = propertyPath.split(".").at(-1) ?? propertyPath;
28119
+ const labels = {
28120
+ text: "\u6587\u6848",
28121
+ title: "\u6807\u9898",
28122
+ subtitle: "\u526F\u6807\u9898",
28123
+ label: "\u6807\u7B7E",
28124
+ description: "\u8BF4\u660E",
28125
+ value: "\u5185\u5BB9",
28126
+ alt: "\u56FE\u7247\u8BF4\u660E"
28127
+ };
28128
+ return labels[key] ?? key;
28129
+ }
28130
+ function appendPath(base, segment) {
28131
+ return base ? `${base}.${segment}` : segment;
28132
+ }
28133
+ function componentKey(surfaceId, componentId) {
28134
+ return `${surfaceId}\0${componentId}`;
28135
+ }
28136
+ function deduplicateTargets(targets) {
28137
+ return [
28138
+ ...new Map(targets.map((target) => [target.targetId, target])).values()
28139
+ ];
28140
+ }
28141
+ function deduplicateDiagnostics(diagnostics) {
28142
+ return [
28143
+ ...new Map(diagnostics.map((entry) => [
28144
+ `${entry.code}\0${entry.path}\0${entry.fieldId ?? ""}`,
28145
+ entry
28146
+ ])).values()
28147
+ ];
28148
+ }
28149
+ function cloneBinding(binding) {
28150
+ return JSON.parse(JSON.stringify(binding));
28151
+ }
28152
+
27488
28153
  // src/commands/card/index.ts
27489
28154
  var DRAFT_ROOT = ".card-drafts";
27490
28155
  var AGENT_CONFIG_PATH = "agent_config.json";
@@ -27614,36 +28279,20 @@ async function validateCardBindingsFromDisk(cwd, draftId, fieldId) {
27614
28279
  const directory = draftDirectory(cwd, draftId);
27615
28280
  const [bundle, contextValue, configValue] = await Promise.all([
27616
28281
  readBundle(directory, draftId),
27617
- readRequiredJson(import_path22.default.join(directory, "binding-context.json")),
28282
+ readOptionalJson(import_path22.default.join(directory, "binding-context.json")),
27618
28283
  readRequiredJson(import_path22.default.join(cwd, AGENT_CONFIG_PATH))
27619
28284
  ]);
27620
- const contextResult = parseA2UICardBindingContext(contextValue);
27621
- if (!contextResult.success) {
27622
- throw new LinkqueCliError(
27623
- "CARD_BINDING_CONTEXT_INVALID",
27624
- contextResult.issues.map((entry) => entry.message).join("; ")
27625
- );
27626
- }
27627
- const context = contextResult.data;
28285
+ const context = parseOptionalBindingContext(contextValue);
27628
28286
  const draftStructureChecksum = createA2UICardDraftStructureChecksum(bundle);
27629
28287
  const config2 = parseAgentConfig(configValue);
27630
28288
  const mcpResources = normalizedMcpResources(config2);
27631
- if (context.mcpConfigChecksum !== createA2UICardMcpConfigChecksum(mcpResources)) {
27632
- throw new LinkqueCliError(
27633
- "CARD_BINDING_CONTEXT_STALE",
27634
- "MCP \u914D\u7F6E\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77 AI \u8F85\u52A9\u7ED1\u5B9A"
27635
- );
27636
- }
27637
- const issues = validateA2UICardBindings(
27638
- {
27639
- draft: bundle,
27640
- context,
27641
- mountedMcpResourceIds: new Set(
27642
- mcpResources.map((resource) => String(resource.resourceId))
27643
- )
27644
- },
27645
- fieldId ? { fieldIds: /* @__PURE__ */ new Set([fieldId]) } : void 0
28289
+ const mountedMcpResourceIds = new Set(
28290
+ mcpResources.map((resource) => String(resource.resourceId))
27646
28291
  );
28292
+ const issues = context ? validateA2UICardBindings(
28293
+ { draft: bundle, context, mountedMcpResourceIds },
28294
+ fieldId ? { fieldIds: /* @__PURE__ */ new Set([fieldId]) } : void 0
28295
+ ) : validateBindingsWithoutContext(bundle, mountedMcpResourceIds, fieldId);
27647
28296
  if (issues.length > 0) {
27648
28297
  throw new LinkqueCliError(
27649
28298
  "CARD_BINDING_VALIDATION_FAILED",
@@ -27674,15 +28323,9 @@ async function finalizeCardWhileLocked(cwd, draftId, expectedDraftChecksum) {
27674
28323
  }
27675
28324
  const directory = draftDirectory(cwd, draftId);
27676
28325
  const bundle = await readBundle(directory, draftId);
27677
- const contextResult = parseA2UICardBindingContext(
27678
- await readRequiredJson(import_path22.default.join(directory, "binding-context.json"))
28326
+ const context = parseOptionalBindingContext(
28327
+ await readOptionalJson(import_path22.default.join(directory, "binding-context.json"))
27679
28328
  );
27680
- if (!contextResult.success) {
27681
- throw new LinkqueCliError(
27682
- "CARD_BINDING_CONTEXT_INVALID",
27683
- contextResult.issues.map((entry) => entry.message).join("; ")
27684
- );
27685
- }
27686
28329
  const configPath = import_path22.default.join(cwd, AGENT_CONFIG_PATH);
27687
28330
  const oldConfigBytes = await (0, import_promises16.readFile)(configPath);
27688
28331
  const config2 = parseAgentConfig(
@@ -27716,7 +28359,7 @@ async function finalizeCardWhileLocked(cwd, draftId, expectedDraftChecksum) {
27716
28359
  // V3,让 Agent 直接按真实 MCP inputSchema 决定参数;不再把 presentation input
27717
28360
  // 或 inputBindings 固化进 CARD。
27718
28361
  ...bindings.length > 0 ? { targetSchemaVersion: "3" } : {},
27719
- tools: contextResult.data.candidates.flatMap(
28362
+ tools: (context?.candidates ?? []).flatMap(
27720
28363
  (candidate) => candidate.status === "ready" ? [candidate.tool] : []
27721
28364
  )
27722
28365
  });
@@ -28043,6 +28686,47 @@ function normalizedMcpResources(config2) {
28043
28686
  (left, right) => stableJsonStringify(left).localeCompare(stableJsonStringify(right))
28044
28687
  );
28045
28688
  }
28689
+ function parseOptionalBindingContext(value) {
28690
+ if (value === void 0) return void 0;
28691
+ const result = parseA2UICardBindingContext(value);
28692
+ if (!result.success) {
28693
+ throw new LinkqueCliError(
28694
+ "CARD_BINDING_CONTEXT_INVALID",
28695
+ result.issues.map((entry) => entry.message).join("; ")
28696
+ );
28697
+ }
28698
+ return result.data;
28699
+ }
28700
+ function validateBindingsWithoutContext(bundle, mountedMcpResourceIds, fieldId) {
28701
+ const selectedFields = fieldId ? /* @__PURE__ */ new Set([fieldId]) : void 0;
28702
+ const bindings = (bundle.manifest.bindings ?? []).filter(
28703
+ (binding) => !selectedFields || selectedFields.has(binding.fieldId)
28704
+ );
28705
+ const issues = getA2UICardBindingSelectionIssues(
28706
+ bindings,
28707
+ "draft.manifest.bindings",
28708
+ bundle.manifest.fields
28709
+ );
28710
+ for (const binding of bindings) {
28711
+ if (!mountedMcpResourceIds.has(binding.mcpResourceId)) {
28712
+ issues.push({
28713
+ code: "MCP_TOOL_UNAVAILABLE",
28714
+ message: `MCP resource ${binding.mcpResourceId} is not mounted`,
28715
+ path: `draft.manifest.bindings.${binding.fieldId}`
28716
+ });
28717
+ }
28718
+ }
28719
+ if (!selectedFields) {
28720
+ issues.push(
28721
+ ...validateA2UICardForPublish(bundle).map((diagnostic) => ({
28722
+ code: diagnostic.code,
28723
+ message: diagnostic.message,
28724
+ path: diagnostic.path
28725
+ }))
28726
+ );
28727
+ }
28728
+ return issues;
28729
+ }
28046
28730
  function isJsonObject(value) {
28047
28731
  return value !== null && typeof value === "object" && !Array.isArray(value);
28048
28732
  }