@amaster.ai/pi-lark 0.1.2-beta.41 → 0.1.2-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-apps/SKILL.md +5 -3
  3. package/skills/lark-apps/references/lark-apps-automation.md +164 -0
  4. package/skills/lark-apps/references/lark-apps-get.md +43 -0
  5. package/skills/lark-apps/references/lark-apps-html-publish.md +7 -2
  6. package/skills/lark-apps/references/lark-apps-init.md +1 -2
  7. package/skills/lark-apps/references/lark-apps-openapi-key.md +1 -1
  8. package/skills/lark-apps/references/lark-apps-release-create.md +3 -1
  9. package/skills/lark-base/SKILL.md +1 -1
  10. package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +7 -7
  11. package/skills/lark-calendar/references/lark-calendar-create.md +1 -0
  12. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +2 -1
  13. package/skills/lark-drive/SKILL.md +10 -4
  14. package/skills/lark-drive/references/lark-drive-comment-location.md +16 -4
  15. package/skills/lark-drive/references/lark-drive-comments-guide.md +16 -8
  16. package/skills/lark-drive/references/lark-drive-export.md +39 -10
  17. package/skills/lark-drive/references/lark-drive-list-comments.md +125 -0
  18. package/skills/lark-drive/references/lark-drive-member-add.md +1 -1
  19. package/skills/lark-drive/references/lark-drive-pull.md +3 -3
  20. package/skills/lark-drive/references/lark-drive-push.md +1 -1
  21. package/skills/lark-drive/references/lark-drive-status.md +12 -14
  22. package/skills/lark-im/SKILL.md +5 -4
  23. package/skills/lark-im/references/lark-im-messages-reply.md +1 -1
  24. package/skills/lark-im/references/lark-im-messages-send.md +1 -1
  25. package/skills/lark-minutes/SKILL.md +19 -4
  26. package/skills/lark-minutes/references/lark-minutes-todo.md +2 -2
  27. package/skills/lark-shared/SKILL.md +9 -9
  28. package/skills/lark-sheets/SKILL.md +98 -29
  29. package/skills/lark-sheets/references/lark-sheets-batch-update.md +18 -9
  30. package/skills/lark-sheets/references/lark-sheets-changeset.md +105 -0
  31. package/skills/lark-sheets/references/lark-sheets-chart.md +4 -2
  32. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +2 -0
  33. package/skills/lark-sheets/references/lark-sheets-filter-view.md +1 -1
  34. package/skills/lark-sheets/references/lark-sheets-float-image.md +6 -6
  35. package/skills/lark-sheets/references/lark-sheets-formula-translation.md +12 -3
  36. package/skills/lark-sheets/references/lark-sheets-formula-verify.md +77 -0
  37. package/skills/lark-sheets/references/lark-sheets-history.md +93 -0
  38. package/skills/lark-sheets/references/lark-sheets-pivot-table.md +7 -2
  39. package/skills/lark-sheets/references/lark-sheets-range-operations.md +44 -14
  40. package/skills/lark-sheets/references/lark-sheets-read-data.md +3 -3
  41. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +4 -4
  42. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +4 -4
  43. package/skills/lark-sheets/references/lark-sheets-workbook.md +29 -4
  44. package/skills/lark-sheets/references/lark-sheets-write-cells.md +21 -11
  45. package/skills/lark-slides/SKILL.md +29 -18
  46. package/skills/lark-slides/references/asset-planning.md +0 -1
  47. package/skills/lark-slides/references/examples.md +57 -227
  48. package/skills/lark-slides/references/iconpark.md +2 -2
  49. package/skills/lark-slides/references/lark-slides-create.md +21 -2
  50. package/skills/lark-slides/references/lark-slides-media-upload.md +0 -1
  51. package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +89 -0
  52. package/skills/lark-slides/references/lark-slides-replace-pages.md +1 -1
  53. package/skills/lark-slides/references/lark-slides-replace-slide.md +1 -1
  54. package/skills/lark-slides/references/lark-slides-screenshot.md +11 -8
  55. package/skills/lark-slides/references/lark-slides-xml-get.md +100 -0
  56. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +9 -7
  57. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +4 -4
  58. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +12 -10
  59. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +14 -13
  60. package/skills/lark-slides/references/planning-layer.md +1 -1
  61. package/skills/lark-slides/references/troubleshooting.md +7 -25
  62. package/skills/lark-slides/references/validation-checklist.md +18 -9
  63. package/skills/lark-slides/references/visual-planning.md +4 -3
  64. package/skills/lark-slides/references/xml-schema-quick-ref.md +6 -2
  65. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +647 -52
  66. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +529 -0
  67. package/skills/lark-task/SKILL.md +1 -0
  68. package/skills/lark-vc-agent/SKILL.md +11 -4
  69. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md +1 -1
  70. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-leave.md +1 -1
  71. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md +2 -2
  72. package/skills/lark-sheets/references/lark-sheets-core-operations.md +0 -103
  73. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +0 -220
@@ -117,6 +117,58 @@ class XmlTextOverlapLintTest(unittest.TestCase):
117
117
  self.assertIn("attribute", issue["hint"])
118
118
  self.assertIn("a=1&b=2", issue["hint"])
119
119
 
120
+ def test_lint_xml_reports_mismatched_xml_tag(self) -> None:
121
+ result = xml_text_overlap_lint.lint_xml(
122
+ """
123
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
124
+ <data>
125
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
126
+ <content textType="body"><p>Broken XML</content>
127
+ </shape>
128
+ </data>
129
+ </slide>
130
+ """
131
+ )
132
+ issue = result["issues"][0]
133
+ self.assertEqual(result["summary"]["slide_count"], 0)
134
+ self.assertEqual(result["summary"]["error_count"], 1)
135
+ self.assertEqual(issue["code"], "xml_not_well_formed")
136
+ self.assertIsInstance(issue["line"], int)
137
+ self.assertIsInstance(issue["column"], int)
138
+ self.assertIn("Broken XML", issue["context"])
139
+
140
+ def test_lint_xml_rejects_prefixed_sml_tags(self) -> None:
141
+ result = xml_text_overlap_lint.lint_xml(
142
+ """
143
+ <ns0:slide xmlns:ns0="http://www.larkoffice.com/sml/2.0">
144
+ <ns0:data>
145
+ <sml:shape xmlns:sml="http://www.larkoffice.com/sml/2.0" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
146
+ <sml:content textType="body"><sml:p>Prefixed SML</sml:p></sml:content>
147
+ </sml:shape>
148
+ </ns0:data>
149
+ </ns0:slide>
150
+ """
151
+ )
152
+ issues = result["issues"]
153
+ self.assertEqual(result["summary"]["error_count"], 5)
154
+ self.assertEqual([issue["code"] for issue in issues], ["sml_prefixed_tag"] * 5)
155
+ self.assertEqual(issues[0]["tag"], "ns0:slide")
156
+ self.assertIn("default namespace", issues[0]["hint"])
157
+
158
+ def test_lint_xml_allows_unprefixed_tags_without_namespace(self) -> None:
159
+ result = xml_text_overlap_lint.lint_xml(
160
+ """
161
+ <slide>
162
+ <data>
163
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
164
+ <content textType="body"><p>Unprefixed SML</p></content>
165
+ </shape>
166
+ </data>
167
+ </slide>
168
+ """
169
+ )
170
+ self.assertEqual(result["summary"]["error_count"], 0)
171
+
120
172
  def test_lint_xml_accepts_escaped_entities_without_suspicious_entity_warning(self) -> None:
121
173
  result = xml_text_overlap_lint.lint_xml(
122
174
  """
@@ -162,6 +214,291 @@ class XmlTextOverlapLintTest(unittest.TestCase):
162
214
  self.assertEqual(result["summary"]["slide_count"], 1)
163
215
  self.assertEqual(result["summary"]["error_count"], 0)
164
216
 
217
+ def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
218
+ result = xml_text_overlap_lint.lint_xml(
219
+ """
220
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="1280" height="720">
221
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
222
+ <data>
223
+ <shape id="first" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
224
+ <content textType="body"><p>First slide</p></content>
225
+ </shape>
226
+ </data>
227
+ </slide>
228
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
229
+ <data>
230
+ <img id="second" src="tok" topLeftX="100" topLeftY="120" width="240" height="160"/>
231
+ <shape id="skipped" type="text" topLeftX="80" topLeftY="80" width="300">
232
+ <content textType="body"><p>Missing height</p></content>
233
+ </shape>
234
+ </data>
235
+ </slide>
236
+ </presentation>
237
+ """
238
+ )
239
+ self.assertEqual(result["slide_size"], {"width": 1280, "height": 720})
240
+ self.assertEqual(result["summary"]["slide_count"], 2)
241
+ self.assertEqual([slide["slide_number"] for slide in result["slides"]], [1, 2])
242
+ self.assertEqual([slide["element_count"] for slide in result["slides"]], [1, 1])
243
+ self.assertEqual(result["summary"]["error_count"], 0)
244
+ self.assertEqual(result["summary"]["warning_count"], 0)
245
+
246
+ def test_lint_xml_reports_sxsd_unsupported_tag_with_alias_hint(self) -> None:
247
+ cases = [
248
+ ("textbox", '<textbox topLeftX="80" topLeftY="80" width="300" height="60">Text</textbox>', '<shape type="text">'),
249
+ ("image", '<image src="tok" topLeftX="80" topLeftY="80" width="300" height="180"/>', "<img>"),
250
+ ]
251
+ for tag_name, element_xml, expected_hint in cases:
252
+ with self.subTest(tag=tag_name):
253
+ result = xml_text_overlap_lint.lint_xml(
254
+ f"""
255
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
256
+ <data>{element_xml}</data>
257
+ </slide>
258
+ """
259
+ )
260
+ issue = result["issues"][0]
261
+ self.assertEqual(result["summary"]["error_count"], 1)
262
+ self.assertEqual(issue["code"], "sxsd_unsupported_tag")
263
+ self.assertEqual(issue["tag"], tag_name)
264
+ self.assertIn(expected_hint, issue["hint"])
265
+
266
+ def test_lint_xml_reports_sxsd_unsupported_attr_with_alias_hint(self) -> None:
267
+ cases = [
268
+ ("shape", "x", "topLeftX", '<shape type="text" x="80" topLeftY="80" width="300" height="60"><content><p>Text</p></content></shape>'),
269
+ ("content", "fontColor", "color", '<shape type="text" topLeftX="80" topLeftY="80" width="300" height="60"><content fontColor="rgba(0, 0, 0, 1)"><p>Text</p></content></shape>'),
270
+ ]
271
+ for tag_name, attr_name, expected_attr, element_xml in cases:
272
+ with self.subTest(attr=attr_name):
273
+ result = xml_text_overlap_lint.lint_xml(
274
+ f"""
275
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
276
+ <data>{element_xml}</data>
277
+ </slide>
278
+ """
279
+ )
280
+ issue = result["issues"][0]
281
+ self.assertEqual(result["summary"]["error_count"], 1)
282
+ self.assertEqual(issue["code"], "sxsd_unsupported_attr")
283
+ self.assertEqual(issue["tag"], tag_name)
284
+ self.assertEqual(issue["attr"], attr_name)
285
+ self.assertIn(expected_attr, issue["hint"])
286
+
287
+ def test_lint_xml_ignores_server_filled_id_attrs(self) -> None:
288
+ result = xml_text_overlap_lint.lint_xml(
289
+ """
290
+ <slide id="server-slide-id" xmlns="http://www.larkoffice.com/sml/2.0">
291
+ <data>
292
+ <shape id="server-shape-id" type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
293
+ <fill id="server-fill-id" unexpected="value">
294
+ <fillColor color="rgba(255, 255, 255, 1)"/>
295
+ </fill>
296
+ </shape>
297
+ </data>
298
+ </slide>
299
+ """
300
+ )
301
+
302
+ self.assertEqual(result["summary"]["error_count"], 1)
303
+ issue = result["issues"][0]
304
+ self.assertEqual(issue["code"], "sxsd_unsupported_attr")
305
+ self.assertEqual(issue["tag"], "fill")
306
+ self.assertEqual(issue["attr"], "unexpected")
307
+
308
+ def test_lint_xml_reports_gradient_shorthand_attrs_on_fill_color(self) -> None:
309
+ result = xml_text_overlap_lint.lint_xml(
310
+ """
311
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
312
+ <data>
313
+ <shape type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
314
+ <fill>
315
+ <fillColor
316
+ type="gradient"
317
+ color1="rgba(255, 0, 0, 1)"
318
+ color2="rgba(0, 0, 255, 1)"
319
+ angle="45"
320
+ stop1="0%"
321
+ stop2="100%"/>
322
+ </fill>
323
+ </shape>
324
+ </data>
325
+ </slide>
326
+ """
327
+ )
328
+ unsupported_attrs = {issue["attr"] for issue in result["issues"]}
329
+ self.assertEqual(result["summary"]["error_count"], 6)
330
+ self.assertEqual(
331
+ unsupported_attrs,
332
+ {"type", "color1", "color2", "angle", "stop1", "stop2"},
333
+ )
334
+ self.assertTrue(all(issue["code"] == "sxsd_unsupported_attr" for issue in result["issues"]))
335
+ self.assertTrue(all(issue["tag"] == "fillColor" for issue in result["issues"]))
336
+
337
+ def test_lint_xml_accepts_chart_field_simple_content_attrs(self) -> None:
338
+ result = xml_text_overlap_lint.lint_xml(
339
+ """
340
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
341
+ <data>
342
+ <chart topLeftX="80" topLeftY="80" width="520" height="320">
343
+ <chartPlotArea>
344
+ <chartPlot type="line"/>
345
+ </chartPlotArea>
346
+ <chartData>
347
+ <dim1>
348
+ <chartField name="month" valueType="string">Jan, Feb</chartField>
349
+ </dim1>
350
+ <dim2>
351
+ <chartField name="value" valueType="number">1, 2</chartField>
352
+ </dim2>
353
+ </chartData>
354
+ </chart>
355
+ </data>
356
+ </slide>
357
+ """
358
+ )
359
+ self.assertEqual(result["summary"]["error_count"], 0)
360
+ self.assertNotIn("issues", result)
361
+
362
+ def test_lint_xml_does_not_load_iconpark_index_without_icons(self) -> None:
363
+ original_loader = xml_text_overlap_lint.load_iconpark_icon_types
364
+
365
+ def fail_if_loaded() -> set[str]:
366
+ raise AssertionError("iconpark index should not be loaded without <icon iconType>")
367
+
368
+ xml_text_overlap_lint.load_iconpark_icon_types = fail_if_loaded
369
+ try:
370
+ result = xml_text_overlap_lint.lint_xml(
371
+ """
372
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
373
+ <data>
374
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
375
+ <content><p>No icons here</p></content>
376
+ </shape>
377
+ </data>
378
+ </slide>
379
+ """
380
+ )
381
+ finally:
382
+ xml_text_overlap_lint.load_iconpark_icon_types = original_loader
383
+
384
+ self.assertEqual(result["summary"]["error_count"], 0)
385
+ self.assertNotIn("issues", result)
386
+
387
+ def test_lint_xml_accepts_iconpark_icon_type_from_index(self) -> None:
388
+ result = xml_text_overlap_lint.lint_xml(
389
+ """
390
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
391
+ <data>
392
+ <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
393
+ <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
394
+ </icon>
395
+ </data>
396
+ </slide>
397
+ """
398
+ )
399
+ self.assertEqual(result["summary"]["error_count"], 0)
400
+ self.assertNotIn("issues", result)
401
+
402
+ def test_lint_xml_reports_icon_missing_fill_color(self) -> None:
403
+ cases = [
404
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48"/>',
405
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48"><fill/></icon>',
406
+ (
407
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">'
408
+ "<fill><fillColor/></fill></icon>"
409
+ ),
410
+ ]
411
+ for icon_xml in cases:
412
+ with self.subTest(icon=icon_xml):
413
+ result = xml_text_overlap_lint.lint_xml(
414
+ f"""
415
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
416
+ <data>{icon_xml}</data>
417
+ </slide>
418
+ """
419
+ )
420
+
421
+ issue = result["issues"][0]
422
+ self.assertEqual(result["summary"]["error_count"], 1)
423
+ self.assertEqual(issue["code"], "icon_missing_fill_color")
424
+ self.assertEqual(issue["tag"], "icon")
425
+
426
+ def test_lint_xml_reports_icon_transparent_fill_color(self) -> None:
427
+ result = xml_text_overlap_lint.lint_xml(
428
+ """
429
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
430
+ <data>
431
+ <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
432
+ <fill><fillColor color="rgba(37, 99, 235, 0)"/></fill>
433
+ </icon>
434
+ </data>
435
+ </slide>
436
+ """
437
+ )
438
+ issue = result["issues"][0]
439
+ self.assertEqual(result["summary"]["error_count"], 1)
440
+ self.assertEqual(issue["code"], "icon_transparent_fill_color")
441
+ self.assertEqual(issue["tag"], "icon")
442
+ self.assertEqual(issue["attr"], "fillColor")
443
+
444
+ def test_lint_xml_reports_iconpark_icon_type_outside_index(self) -> None:
445
+ result = xml_text_overlap_lint.lint_xml(
446
+ """
447
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
448
+ <data>
449
+ <icon iconType="iconpark/Base/settng.svg" topLeftX="80" topLeftY="80" width="48" height="48">
450
+ <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
451
+ </icon>
452
+ </data>
453
+ </slide>
454
+ """
455
+ )
456
+ issue = result["issues"][0]
457
+ self.assertEqual(result["summary"]["error_count"], 1)
458
+ self.assertEqual(issue["code"], "iconpark_unsupported_icon_type")
459
+ self.assertEqual(issue["tag"], "icon")
460
+ self.assertEqual(issue["attr"], "iconType")
461
+ self.assertEqual(issue["iconType"], "iconpark/Base/settng.svg")
462
+ self.assertIn("iconpark-index.json", issue["hint"])
463
+ self.assertIn("iconpark/Base/setting.svg", issue["hint"])
464
+
465
+ def test_lint_xml_allows_namespaced_svg_inside_whiteboard(self) -> None:
466
+ result = xml_text_overlap_lint.lint_xml(
467
+ """
468
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
469
+ <data>
470
+ <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
471
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 180">
472
+ <rect x="10" y="10" width="120" height="60"/>
473
+ </svg>
474
+ </whiteboard>
475
+ </data>
476
+ </slide>
477
+ """
478
+ )
479
+ self.assertEqual(result["summary"]["error_count"], 0)
480
+ self.assertNotIn("issues", result)
481
+
482
+ def test_lint_xml_reports_unqualified_svg_inside_whiteboard_with_namespace_hint(self) -> None:
483
+ result = xml_text_overlap_lint.lint_xml(
484
+ """
485
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
486
+ <data>
487
+ <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
488
+ <svg viewBox="0 0 300 180">
489
+ <rect x="10" y="10" width="120" height="60"/>
490
+ </svg>
491
+ </whiteboard>
492
+ </data>
493
+ </slide>
494
+ """
495
+ )
496
+ issue = result["issues"][0]
497
+ self.assertEqual(result["summary"]["error_count"], 1)
498
+ self.assertEqual(issue["code"], "sxsd_unsupported_tag")
499
+ self.assertEqual(issue["tag"], "svg")
500
+ self.assertIn('xmlns="http://www.w3.org/2000/svg"', issue["hint"])
501
+
165
502
  def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
166
503
  result = xml_text_overlap_lint.lint_xml(
167
504
  """
@@ -182,6 +519,83 @@ class XmlTextOverlapLintTest(unittest.TestCase):
182
519
  self.assertEqual(result["summary"]["error_count"], 1)
183
520
  self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
184
521
 
522
+ def test_lint_xml_detects_current_itinerary_cjk_caption_occlusion(self) -> None:
523
+ result = xml_text_overlap_lint.lint_xml(
524
+ """
525
+ <slide id="pQO" xmlns="http://www.larkoffice.com/sml/2.0">
526
+ <data>
527
+ <shape width="190" height="80" topLeftX="580" topLeftY="170" presetHandlers="0" type="rect" id="blI">
528
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
529
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
530
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
531
+ </shape>
532
+ <shape width="160" height="25" topLeftX="595" topLeftY="180" type="text" id="blX">
533
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>日照金山</p></content>
534
+ </shape>
535
+ <shape width="160" height="40" topLeftX="595" topLeftY="205" type="text" id="blY">
536
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>清晨躺在床上看玉龙雪山日照金山奇观</p></content>
537
+ </shape>
538
+ <shape width="180" height="80" topLeftX="730" topLeftY="170" presetHandlers="0" type="rect" id="blH">
539
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
540
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
541
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
542
+ </shape>
543
+ <shape width="150" height="25" topLeftX="745" topLeftY="180" type="text" id="blp">
544
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>午餐返程</p></content>
545
+ </shape>
546
+ <shape width="150" height="40" topLeftX="745" topLeftY="205" type="text" id="blV">
547
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>享用特色午餐,带着美好回忆返程</p></content>
548
+ </shape>
549
+ <shape width="190" height="80" topLeftX="580" topLeftY="310" presetHandlers="0" type="rect" id="blP">
550
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
551
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
552
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
553
+ </shape>
554
+ <shape width="160" height="25" topLeftX="595" topLeftY="320" type="text" id="blG">
555
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>高路徒步</p></content>
556
+ </shape>
557
+ <shape width="160" height="40" topLeftX="595" topLeftY="345" type="text" id="blQ">
558
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>经典高路徒步,28道拐,龙洞瀑布,中虎跳峡</p></content>
559
+ </shape>
560
+ <shape width="180" height="80" topLeftX="730" topLeftY="310" presetHandlers="0" type="rect" id="blw">
561
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
562
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
563
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
564
+ </shape>
565
+ <shape width="150" height="25" topLeftX="745" topLeftY="320" type="text" id="blZ">
566
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>伴手礼</p></content>
567
+ </shape>
568
+ <shape width="150" height="40" topLeftX="745" topLeftY="345" type="text" id="blS">
569
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>酒店精心准备的归途伴手礼,留下难忘纪念</p></content>
570
+ </shape>
571
+ </data>
572
+ </slide>
573
+ """
574
+ )
575
+ overlap_pairs = {tuple(issue["elements"]) for issue in result["slides"][0]["issues"]}
576
+ self.assertEqual(result["summary"]["error_count"], 2)
577
+ self.assertIn(("blY", "blV"), overlap_pairs)
578
+ self.assertIn(("blQ", "blS"), overlap_pairs)
579
+
580
+ def test_lint_xml_detects_horizontal_text_overflow_across_declared_box_gap(self) -> None:
581
+ result = xml_text_overlap_lint.lint_xml(
582
+ """
583
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
584
+ <data>
585
+ <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
586
+ <content fontSize="18"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
587
+ </shape>
588
+ <shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
589
+ <content fontSize="18"><p>目标</p></content>
590
+ </shape>
591
+ </data>
592
+ </slide>
593
+ """
594
+ )
595
+ self.assertEqual(result["summary"]["error_count"], 1)
596
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
597
+ self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source", "target"])
598
+
185
599
  def test_lint_xml_does_not_check_bounds_or_text_height(self) -> None:
186
600
  result = xml_text_overlap_lint.lint_xml(
187
601
  """
@@ -223,6 +637,38 @@ class XmlTextOverlapLintTest(unittest.TestCase):
223
637
  self.assertEqual(result["summary"]["error_count"], 0)
224
638
  self.assertEqual(result["summary"]["warning_count"], 0)
225
639
 
640
+ def test_extract_elements_preserves_supported_element_geometry_order_and_text_metadata(self) -> None:
641
+ elements = xml_text_overlap_lint.extract_elements(
642
+ """
643
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
644
+ <data>
645
+ <img id="photo" src="tok" topLeftX="10" topLeftY="20" width="100" height="80"/>
646
+ <shape id="headline" type="text" topLeftX="40" topLeftY="60" width="320" height="90">
647
+ <content textType="headline" textAlign="center" autoFit="normal-auto-fit" fontSize="28">
648
+ <p><![CDATA[Growth & scale]]></p>
649
+ <p>Focused execution</p>
650
+ </content>
651
+ </shape>
652
+ <table id="table" topLeftX="400" topLeftY="60" width="220" height="120"></table>
653
+ <chart id="chart" topLeftX="640" topLeftY="60" width="220" height="120"/>
654
+ <whiteboard id="wb" topLeftX="80" topLeftY="220" width="760" height="240"/>
655
+ <shape id="missing-height" type="text" topLeftX="80" topLeftY="480" width="320">
656
+ <content><p>Skipped</p></content>
657
+ </shape>
658
+ </data>
659
+ </slide>
660
+ """
661
+ )
662
+ self.assertEqual([element["id"] for element in elements], ["photo", "headline", "table", "chart", "wb"])
663
+ self.assertEqual([element["kind"] for element in elements], ["img", "shape", "table", "chart", "whiteboard"])
664
+ self.assertEqual([element["order"] for element in elements], [0, 1, 2, 3, 4])
665
+ self.assertEqual(elements[1]["type"], "text")
666
+ self.assertEqual(elements[1]["textType"], "headline")
667
+ self.assertEqual(elements[1]["textAlign"], "center")
668
+ self.assertEqual(elements[1]["autoFit"], "normal-auto-fit")
669
+ self.assertEqual(elements[1]["fontSize"], 28)
670
+ self.assertEqual(elements[1]["text"], "Growth & scale\nFocused execution")
671
+
226
672
  def test_lint_xml_does_not_check_small_out_of_bounds_elements(self) -> None:
227
673
  result = xml_text_overlap_lint.lint_xml(
228
674
  """
@@ -266,6 +712,89 @@ class XmlTextOverlapLintTest(unittest.TestCase):
266
712
  )
267
713
  self.assertEqual(result["summary"]["error_count"], 0)
268
714
 
715
+ def test_lint_xml_warns_for_whiteboard_external_boundary_overlap(self) -> None:
716
+ result = xml_text_overlap_lint.lint_xml(
717
+ """
718
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
719
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
720
+ <data>
721
+ <whiteboard id="wb" topLeftX="50" topLeftY="140" width="860" height="360"/>
722
+ <shape id="card" type="rect" topLeftX="50" topLeftY="450" width="200" height="60">
723
+ <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
724
+ <content/>
725
+ </shape>
726
+ <shape id="label" type="text" topLeftX="65" topLeftY="460" width="170" height="20">
727
+ <content fontSize="12"><p>基础保障</p></content>
728
+ </shape>
729
+ </data>
730
+ </slide>
731
+ </presentation>
732
+ """
733
+ )
734
+ issues = result["slides"][0]["issues"]
735
+ self.assertEqual(result["summary"]["error_count"], 0)
736
+ self.assertEqual(result["summary"]["warning_count"], 1)
737
+ self.assertEqual(issues[0]["code"], "whiteboard_external_overlap")
738
+ self.assertIn("static whiteboard container-bbox risk", issues[0]["hint"])
739
+ self.assertIn("screenshot QA", issues[0]["hint"])
740
+ self.assertEqual(issues[0]["elements"], ["wb", "card"])
741
+ self.assertEqual(issues[0]["overlaps"][0]["overlap_height"], 50)
742
+
743
+ def test_lint_xml_allows_whiteboard_internal_labels(self) -> None:
744
+ result = xml_text_overlap_lint.lint_xml(
745
+ """
746
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
747
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
748
+ <data>
749
+ <whiteboard id="wb" topLeftX="100" topLeftY="120" width="420" height="260"/>
750
+ <shape id="label" type="text" topLeftX="150" topLeftY="180" width="180" height="40">
751
+ <content fontSize="14"><p>内部标注</p></content>
752
+ </shape>
753
+ </data>
754
+ </slide>
755
+ </presentation>
756
+ """
757
+ )
758
+ self.assertEqual(result["summary"]["error_count"], 0)
759
+ self.assertEqual(result["summary"]["warning_count"], 0)
760
+
761
+ def test_lint_xml_allows_bottom_layer_full_slide_whiteboard_background(self) -> None:
762
+ result = xml_text_overlap_lint.lint_xml(
763
+ """
764
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
765
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
766
+ <data>
767
+ <whiteboard id="background" topLeftX="0" topLeftY="0" width="960" height="540"/>
768
+ <shape id="title" type="text" topLeftX="80" topLeftY="80" width="420" height="80">
769
+ <content textType="title" fontSize="36"><p>Title</p></content>
770
+ </shape>
771
+ </data>
772
+ </slide>
773
+ </presentation>
774
+ """
775
+ )
776
+ self.assertEqual(result["summary"]["error_count"], 0)
777
+ self.assertEqual(result["summary"]["warning_count"], 0)
778
+
779
+ def test_lint_xml_allows_whiteboard_inside_background_panel(self) -> None:
780
+ result = xml_text_overlap_lint.lint_xml(
781
+ """
782
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
783
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
784
+ <data>
785
+ <shape id="panel" type="rect" topLeftX="40" topLeftY="100" width="880" height="340">
786
+ <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
787
+ <content/>
788
+ </shape>
789
+ <whiteboard id="wb" topLeftX="60" topLeftY="120" width="840" height="300"/>
790
+ </data>
791
+ </slide>
792
+ </presentation>
793
+ """
794
+ )
795
+ self.assertEqual(result["summary"]["error_count"], 0)
796
+ self.assertEqual(result["summary"]["warning_count"], 0)
797
+
269
798
  def test_lint_xml_detects_invalid_template_text_stack_overlap(self) -> None:
270
799
  cases = [
271
800
  (
@@ -13,6 +13,7 @@ metadata:
13
13
  **CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),其中包含认证、权限处理**
14
14
 
15
15
  > **任务搜索技巧**:先区分用户是否**特地指定使用搜索 skill**,以及是否真的提供了**查询关键字**(例如任务名称、关键词、片段描述)。如果用户特地指定使用搜索 skill,或明确给出了任务查询关键字,则目标是**任务**时优先使用 `+search`。如果用户没有特地指定使用搜索 skill,且意图里没有查询关键字,只有范围条件(例如“今年以来”“已完成”“由我创建”“我关注的”),并且使用 `+search` 与 `+get-related-tasks` / `+get-my-tasks` 都能达到目的时,应优先使用列表型能力,而不是搜索型能力。其中,“与我相关 / 我关注的 / 由我创建”等优先考虑 `+get-related-tasks`;“我负责的 / 分配给我”的列表优先考虑 `+get-my-tasks`。不要把时间范围词(例如“今年以来”)本身误当成 `query` 去走搜索。
16
+ > **任务搜索相关性提示**:`+search` 当前不会自动判断搜索结果与搜索发起人的相关性。如果用户明确要求搜索“与我相关”的任务,必须先识别具体关系,获取当前用户的 `open_id`,并显式传入对应的 `--assignee`(负责人)、`--creator`(创建人)或 `--follower`(关注人)过滤条件;不能只依赖 `query` 期待自动返回与当前用户相关的任务。
16
17
  > **任务清单搜索技巧**:任务清单也遵循同样的判断逻辑。先区分用户是否**特地指定使用搜索 skill**,以及是否真的提供了**清单查询关键字**(例如清单名称、关键词、片段描述)。如果用户特地指定使用搜索 skill,或明确给出了清单查询关键字,则优先使用 `+tasklist-search`。如果用户没有特地指定使用搜索 skill,且意图里没有查询关键字,只有范围条件(例如“由我创建的任务清单”“今年以来创建的清单”),并且使用搜索或原生列取清单都能达到目的时,应优先使用原生 `tasklists.list` 接口列取清单(先 `schema task.tasklists.list`,再 `lark-cli task tasklists list --as user ...`),再按 `creator`、`created_at` 等字段做本地筛选和分页控制。
17
18
  > **意图区分补充**:像“搜索飞书中今年以来我关注的任务”这类表达,虽然字面带有“搜索”,但如果没有真正的查询关键字,且本质是在限定“与我相关 + 时间范围”,则应优先走 `+get-related-tasks`;像“搜索飞书中由我创建的任务清单”这类表达,如果没有清单关键字,且本质是在限定“清单范围 + 创建者”,则应优先走原生 `tasklists.list` 后筛选,而不是直接走搜索型 shortcut。
18
19
  > **用户身份识别**:在用户身份(user identity)场景下,如果用户提到了“我”(例如“分配给我”、“由我创建”),请默认获取当前登录用户的 `open_id` 作为对应的参数值。
@@ -18,9 +18,16 @@ metadata:
18
18
  ## 内测提示
19
19
 
20
20
  - 当前功能正在内测中,仅少数用户可用。忽略 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md) 中的权限申请流程,以下方的指引为准。
21
- - 如果 `lark-cli` 提示 `missing required scope(s)` / `permission_violations`,不要走普通权限申请流程;先提示用户加入早鸟群确认内测权限已开通,再按“应用身份权限配置检查”处理应用权限、安装和数据范围。
21
+ - 如果 `lark-cli` 提示 `missing required scope(s)` / `missing_scopes`,不要走普通权限申请流程;先提示用户加入早鸟群确认内测权限已开通,再按“应用身份权限配置检查”处理应用权限、安装和数据范围。
22
22
  - 如果 `lark-cli` 返回 `error.code=20017` / `ErrNotInGray`,提示用户加入早鸟群:`https://go.larkoffice.com/join-chat/2f4nb0e1-fe00-4f67-bed7-25beaf533fbd`。
23
23
 
24
+ ## 会议查询权限
25
+
26
+ `+meeting-list-active` 和 `+meeting-events` 缺少权限时,先按上面的内测提示确认功能已开通,再读取 CLI 错误中的 `hint`,并根据当前调用身份处理:
27
+
28
+ - 用户身份 `--as user`:按 CLI 提示为当前用户授权 `vc:meeting.meetingevent:read`。
29
+ - 应用身份 `--as bot`:请应用开发者开通 `vc:meeting.bot.join:write`,不要执行 `auth login`;随后按“应用身份权限配置检查”确认应用发布、安装和数据范围。
30
+
24
31
  ## 定位
25
32
 
26
33
  本 skill 与 [`lark-vc`](../lark-vc/SKILL.md) 并列:
@@ -170,9 +177,9 @@ Shortcut 是对常用操作的高级封装(`lark-cli vc +<verb> [flags]`)。
170
177
 
171
178
  ## 应用身份权限配置检查
172
179
 
173
- 应用身份 `--as bot` 报 `no permission`、`missing required scope(s)`、`permission_violations`、`ErrNotInGray` 或 `20017` 时,不要引导用户执行 `auth login`。按顺序检查:
180
+ 应用身份 `--as bot` 报 `no permission`、`missing required scope(s)`、`missing_scopes`、`ErrNotInGray` 或 `20017` 时,不要引导用户执行 `auth login`。按顺序检查:
174
181
 
175
- 1. 以 CLI 返回的 metadata / error envelope 为准,确认提示的 VC Agent 相关权限已开通。常见读取 active meeting / events 需要会中事件读取权限;应用机器人入会 / 离会需要 bot 入会写权限。
182
+ 1. 确认内测权限后,按 CLI 错误中的 `hint` 处理;返回 `console_url` 时将其原样提供给用户。
176
183
  2. 应用已发布并安装到当前租户。
177
184
  3. 开放平台“权限可访问的数据范围”已开通并保存。
178
185
  4. 数据范围选择“按条件筛选”,条件配置为:**会议的归属者 包含 与应用的可用范围一致**。
@@ -180,7 +187,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli vc +<verb> [flags]`)。
180
187
 
181
188
  ## 用户身份被拒绝时
182
189
 
183
- 用户身份 `--as user` 报权限或身份不支持类错误时,不要反复引导用户执行 `auth login`。先以 CLI 返回的 metadata / error envelope 为准判断:如果错误表明当前接口不支持用户身份访问,再按用户意图切换处理:
190
+ 用户身份 `--as user` 调用 `+meeting-list-active` 或 `+meeting-events` 报普通 scope 缺失时,按“会议查询权限”处理;其他 shortcut 的 scope 缺失按各自 CLI `hint` 处理。普通 scope 缺失不表示接口不支持用户身份,只有 CLI 明确表明当前接口不支持用户身份访问时,才按用户意图切换处理:
184
191
 
185
192
  1. 如果用户只是查询当前登录用户所在的进行中会议,说明当前接口链路不支持用户身份访问,改用应用身份流程;需要目标用户 open_id,并要求应用机器人已在会中或先按用户确认执行入会。
186
193
  2. 如果用户明确要求应用机器人入会、旁听、代参会或读取应用机器人可见事件,直接切到 `--as bot`,并按上面的应用身份权限配置检查处理。
@@ -290,7 +290,7 @@ lark-cli vc +meeting-events \
290
290
  | 用户身份无权限 / 不可见 | 当前用户不是该会议的可见参与者,或 `meeting_id` 不是从用户身份路径获得 | 不要反复执行 `auth login`。先确认 `meeting_id` 是否来自 `+meeting-list-active --as user`;如果用户明确要切到应用身份,再通过 `+meeting-list-active --as bot --user-id <user_open_id>` 获取应用身份可读的 `meeting_id`,或在用户明确同意后让应用机器人入会,再用 `+meeting-events --as bot` 读取 |
291
291
  | `20001 meeting_status_MEETING_END` | 会议已结束且已超出后端允许的 5 分钟宽限窗口 | 本接口不再适合继续拉取事件。先用 `lark-cli vc +detail --meeting-ids <meeting.id>` 获取会议产物信息,再根据 `note_display_type` / `note_id` / `minute_token` 和用户意图选择纪要正文、逐字稿或妙记;参会人请用 `lark-cli vc meeting get --params '{"meeting_id":"<meeting.id>"}' --with-participants` |
292
292
  | `20002 meeting not exist` | `meeting_id` 错误,或会议实例当前已不可获取(常见于把 9 位会议号当 meeting_id 传) | 确认传入的是长数字 `meeting_id`,不是 9 位会议号 |
293
- | 应用身份权限不足 | 应用权限、租户安装、权限可访问的数据范围或 VC Agent privilege 未配置完整 | 不要执行 `auth login`。以 CLI 返回的 metadata / error envelope 为准确认缺失权限;检查应用发布/安装,以及开放平台“权限可访问的数据范围”:选择“按条件筛选”,条件为“会议的归属者 包含 与应用的可用范围一致”;仍失败再排查内测 privilege / 灰度 |
293
+ | 应用身份权限不足 | 应用权限、租户安装、权限可访问的数据范围或 VC Agent privilege 未配置完整 | 不要执行 `auth login`。请应用开发者开通 `vc:meeting.bot.join:write`;再检查应用发布/安装和权限可访问的数据范围,均正确仍失败时再排查内测灰度权限 |
294
294
  | `HTTP 404` / `HTTP 500` | 服务端当前无法找到或处理该会议实例 | 换一个正在进行且 bot 可见的 meeting_id,或排查后端问题 |
295
295
 
296
296
  ## 提示
@@ -46,7 +46,7 @@ lark-cli vc +meeting-leave --as bot --meeting-id 69xxxxxxxxxxxxx28 --dry-run
46
46
  ## 输出结果
47
47
 
48
48
  接口成功返回时,默认输出:`Left meeting <meeting-id> successfully.`。
49
- `--format json` 返回 API 原始响应体。
49
+ `--format json` 返回标准 `{ok, identity, data}` 信封,例如 `{"ok":true,"identity":"bot","data":{}}`,不是带 `code` / `msg` 的 API 原始响应体。
50
50
 
51
51
  ## 如何获取输入参数
52
52