Get started

Swarms — การกระจายงานไปยังเอเจนต์และการประสานงานในโหมดโค้ด

Swarms — การกระจายงานไปยังเอเจนต์และการประสานงานในโหมดโค้ด

สถานะ: จัดส่งแล้ว — ถูกแทนที่ด้วย docs/tools/swarm.md เอกสารนี้ยังคงไว้เป็น บันทึกการออกแบบการนำไปใช้งาน

1. คืออะไรและทำไมจึงต้องมี

swarm คือซับเอเจนต์จำนวนมากที่ได้รับการประสานงานอย่างกำหนดผลลัพธ์แน่นอนจากสคริปต์ โหมดโค้ด: กระจายงานไปยังตัวอ่าน N ตัว ตรวจสอบข้อค้นพบแบบโต้แย้ง สังเคราะห์ผ่าน ตัวจัดลำดับความสำคัญแบบมีสถานะ และวนซ้ำตามเกตการตัดสินใจ โฟลว์การควบคุม (Promise.all, while, if) ก็คือ การประสานงาน — โดยตั้งใจให้ ไม่มี DSL แบบกราฟ ไม่มีโหมดใหม่ และไม่มีพื้นผิวเครื่องมือระดับบนสุดใหม่

โหมดโค้ดของ OpenClaw (QuickJS-WASI, snapshot/resume, คำขอผ่านบริดจ์) เป็น รากฐาน การเรียกบริดจ์ที่พักไว้จะคงอยู่ผ่านสแนปช็อต VM การรีสตาร์ต Gateway และ กลับมาทำงานต่อจากจุดที่หยุดไว้อย่างแม่นยำ — แข็งแกร่งกว่าการออกแบบที่เล่นเจอร์นัลซ้ำ โดย ไม่มีข้อจำกัดด้านการกำหนดผลลัพธ์แน่นอนสำหรับสคริปต์

การตั้งชื่อ: ชื่อในผลิตภัณฑ์/เอกสารคือ Swarm ตัวระบุในโค้ดคงไว้ตามตัวอักษร: API ฝั่งเกสต์ agents.*, การกำหนดค่า tools.swarm, คอลัมน์กลุ่ม swarm

2. การตัดสินใจ (ผู้ดูแล, 2026-07-17)

  • ต้นทุน: บังคับใช้ขีดจำกัดจากการกำหนดค่า; งบประมาณโทเค็นต่อ swarm เป็นทางเลือก ไม่มีงบประมาณบังคับ
  • การอนุมัติ: ลูกทำงานแบบ ปิดเมื่อเกิดความล้มเหลว / ไม่โต้ตอบ การดำเนินการที่ต้อง ได้รับอนุมัติจะถูกปฏิเสธ; การปฏิเสธจะถูกรายงานในผลลัพธ์ของลูก; สคริปต์ เป็นผู้ตัดสินใจ ไม่มีพรอมต์รบกวนผู้ปฏิบัติงานจากการกระจายงาน
  • v1 รองรับเฉพาะสคริปต์เฉพาะกิจที่โมเดลเขียน เวิร์กโฟลว์ที่บันทึก/ตั้งชื่อไว้ และจุดเข้า CLI/cron: ทำภายหลัง (มีโหมดโค้ดแบบ headless สำหรับ cron อยู่แล้ว)
  • อัตลักษณ์ของลูก: ใช้เอเจนต์ผู้ปฏิบัติงานเฉพาะโดยค่าเริ่มต้นผ่านการกำหนดค่า tools.swarm.defaultAgentId (ตรวจสอบกับรายการอนุญาตเป้าหมายซับเอเจนต์ที่มีอยู่); เขียนทับต่อการสร้าง ด้วย agentId Core ไม่มีรหัสเอเจนต์แบบรวมมาให้; เอกสารแนะนำการกำหนดค่าเอเจนต์ worker แบบกระชับ
  • ไม่มีการเปลี่ยนแปลงซอร์ส Codex ฮาร์เนส Codex ใช้รูปแบบ spawn/wait (§8)

3. ภาพรวมสถาปัตยกรรม

Code
สคริปต์โหมดโค้ด (QuickJS VM, Gateway)          สคริปต์ Codex V8 (กระบวนการ Codex)  agents.run(...) ── การเรียกบริดจ์ที่พักไว้       tools.sessions_spawn / tools.agents_wait        │                                                │ RPC ของ item/tool/call (แต่ละครั้ง ≤600s)        ▼                                                ▼             CORE (ไม่ขึ้นกับฮาร์เนส, รีโพนี้)  sessions_spawn {collect:true, outputSchema, fastMode, groupId}  agents_wait {ids, timeoutSeconds}  รีจิสทรีซับเอเจนต์ (SQLite): ระเบียนการเสร็จสิ้นของตัวรวบรวม, รหัสกลุ่ม swarm  ลูก = เซสชันซับเอเจนต์ทั่วไป (จำกัดตามเลน, การอนุมัติแบบปิดเมื่อเกิดความล้มเหลว)  sessions.changed SSE ──► จุดใน Control UI / แถบด้านข้าง / ข้อความสถานะช่องทาง

มีเจ้าของหลักหนึ่งเดียวสำหรับความหมายของ spawn/complete/settle (เครื่องมือ Core + รีจิสทรี) การขนส่งสำหรับ await สองแบบ: QuickJS พักการเรียกบริดจ์ไว้อย่างไม่มีกำหนด (สแนปช็อต); Codex สำรวจ agents_wait ผ่าน RPC ที่มีขอบเขต

4. เกตการกำหนดค่า (v1)

tools.swarm ใหม่ (ส่วนกลาง + การเขียนทับต่อเอเจนต์ โดยใช้รูปแบบการผสานเดียวกับ tools.codeMode):

jsonc
"tools": {  "swarm": {    "enabled": false,            // เกตหลัก, ค่าเริ่มต้นปิด    "maxConcurrent": 8,          // จำนวนลูกที่ทำงานพร้อมกัน (ขีดจำกัดเลน swarm)    "maxChildrenPerGroup": 50,   // จำนวนลูกที่ยังทำงานอยู่ต่อกลุ่ม swarm    "maxTotalPerGroup": 200,     // จำนวนการสร้างตลอดอายุของกลุ่ม (แนวป้องกันการทำงานไม่สิ้นสุด)    "waitTimeoutSecondsMax": 600,    "defaultAgentId": ""         // ไม่บังคับ; รหัสเอเจนต์ลูกเมื่อ spawn ไม่ระบุ agentId  }}
  • Zod: ยูเนียน boolean | strict object แบบเดียวกับ CodeModeSchema (src/config/zod-schema.agent-runtime.ts); swarm: true{enabled: true}
  • ประเภทใน src/config/types.tools.ts (ทั้งต่อเอเจนต์และ tools ระดับบนสุด), ป้ายกำกับใน schema.labels.ts, ความช่วยเหลือใน schema.help.runtime.ts
  • ตัวช่วยการแก้ค่า resolveSwarmConfig(cfg, agentId) ที่สะท้อนรูปแบบ resolveCodeModeConfig (src/agents/code-mode.ts:215) พร้อมจำกัดตัวเลขทั้งหมดให้อยู่ในช่วง
  • ผลของเกตเมื่อปิดใช้งาน: ไม่มีเครื่องมือ agents_wait ในแค็ตตาล็อก; พารามิเตอร์ collect/outputSchema/fastMode/groupId บน sessions_spawn จะถูกปฏิเสธพร้อมข้อผิดพลาดที่ชัดเจนซึ่งระบุคีย์การกำหนดค่า ไม่มีการเปลี่ยนแปลงพฤติกรรมอื่น
  • defaultAgentId ได้รับการตรวจสอบผ่าน resolveSubagentAllowedTargetIds (src/agents/subagent-target-policy.ts); รหัสที่ไม่รู้จัก → ข้อผิดพลาดในการสร้าง ไม่ใช่การใช้ค่าทดแทน

5. Core: การสร้างในโหมดตัวรวบรวม + agents_wait (v1)

5.1 ส่วนเพิ่มเติมของ sessions_spawn (ทั้งหมดขึ้นกับการเปิดใช้งาน swarm)

  • collect: boolean — เมื่อเป็นจริง การทำงานของลูกจะได้รับการลงทะเบียนด้วย expectsCompletionMessage: false และ ระเบียนการเสร็จสิ้นของตัวรวบรวม แทนการส่งประกาศ/การชี้นำ เครื่องมือส่งคืน { runId, sessionKey } ทันที ไม่มีการผูกกับช่องทาง/เธรด
  • outputSchema: object — JSON Schema ลูกจะได้รับเครื่องมือสังเคราะห์ structured_output ที่เพิ่มต่อท้ายพื้นผิวเครื่องมือ; ส่วนเพิ่มเติมของพรอมต์ระบบ สั่งให้เรียกเครื่องมือนี้หนึ่งครั้งเท่านั้นพร้อมผลลัพธ์สุดท้าย เมื่อการตรวจสอบ ล้มเหลว ลูกจะได้รับการกระตุ้นให้ลองใหม่หนึ่งครั้ง; หลังจากนั้นระเบียนการเสร็จสิ้น จะมี structured: undefined พร้อมข้อความดิบและ schemaError
  • fastMode: true | "auto" | false — ส่งผ่านไปยังแพตช์เซสชันลูก ควบคู่กับโมเดล/การคิดผ่าน resolveSubagentModelAndThinkingPlan (src/agents/subagent-spawn-plan.ts) โดยใช้แกน FastMode ที่มีอยู่ (src/shared/fast-mode.ts) ไม่ระบุ = สืบทอด
  • groupId: string — ตราประทับกลุ่ม swarm ค่าเริ่มต้นคือ swarm:<requesterSessionKey>:<runId-of-requesting-run> คงอยู่ใน ระเบียนรีจิสทรีและแถวเซสชันลูก ใช้สำหรับขีดจำกัด การแสดงรายการ การเก็บถาวร แบบกลุ่ม และจุดต่าง ๆ
  • label: string มีอยู่แล้ว — แสดงในจุดต่าง ๆ และ subagents list
  • รหัสเอเจนต์ลูก: params.agentId → มิฉะนั้น tools.swarm.defaultAgentId → มิฉะนั้น เอเจนต์ผู้ร้องขอ (พฤติกรรมที่มีอยู่)

5.2 การอนุมัติแบบปิดเมื่อเกิดความล้มเหลว

ลูกแบบตัวรวบรวมทำงานด้วยบริบทการอนุมัติแบบไม่โต้ตอบ: การเรียกเครื่องมือใด ๆ ที่ต้องได้รับอนุมัติจากผู้ปฏิบัติงานจะได้รับผลเป็นการปฏิเสธแบบมีโครงสร้าง (approval_required) ที่ลูกมองเห็น และคาดว่าลูกจะรายงาน การติดขัดในผลลัพธ์ การนำไปใช้: ใช้ระบบนโยบายการอนุมัติ exec/เครื่องมือ ที่มีอยู่ซ้ำ โดยบังคับใช้ตัวแก้ค่า deny สำหรับการทำงานของลูกในโหมดตัวรวบรวม ไม่มีการส่งเหตุการณ์อนุมัติจากลูกแบบตัวรวบรวมไปยังพื้นผิวของผู้ปฏิบัติงาน

5.3 เครื่องมือ agents_wait (ใหม่, มีเกตควบคุม)

Code
agents_wait({ ids: string[], timeoutSeconds?: number })→ {    completed: [{ runId, status: "done"|"failed"|"killed"|"timeout",                  result: string, structured?: unknown, schemaError?: string,                  sessionKey, label?, usage?: {inputTokens, outputTokens} }],    pending: string[]  }
  • ส่งคืนทันทีเมื่อมีรหัส อย่างน้อยหนึ่งรายการ เสร็จสมบูรณ์ (ความหมายแบบ เสร็จก่อน/แข่งขันกัน ซึ่งทำให้สร้างไปป์ไลน์ได้) หรือเมื่อหมดเวลาพร้อม completed: []
  • ค่าเริ่มต้นของ timeoutSeconds คือ 30 และถูกจำกัดไว้ที่ waitTimeoutSecondsMax
  • ทำซ้ำได้โดยไม่เปลี่ยนผลลัพธ์: รหัสที่เสร็จสมบูรณ์แล้วจะส่งคืนระเบียนเดิมอีกครั้ง (ระเบียนจะ ถูกเก็บไว้จนกว่าจะเก็บกลุ่มถาวร) รหัสที่ไม่รู้จัก → รายการข้อผิดพลาดต่อรหัส ไม่ใช่การ throw
  • ความเป็นเจ้าของ: เฉพาะเซสชันที่สร้างการทำงาน (หรือลำดับพาเรนต์ของเซสชันนั้น) เท่านั้นที่รอ การทำงานดังกล่าวได้ — กฎความเป็นเจ้าของเดียวกับ wait ในโหมดโค้ด (code-mode.ts:1684)
  • รีจิสทรี: ระเบียนการเสร็จสิ้นอยู่ในที่เก็บ SQLite ของรีจิสทรีซับเอเจนต์ ที่มีอยู่ (subagent-registry.store.sqlite.ts) — ฟิลด์ใหม่ ไม่มีที่เก็บใหม่ และไม่มี การเพิ่มเวอร์ชันสคีมา (เฉพาะคอลัมน์เพิ่มเติม; ดูข้อจำกัดใน §9)

5.4 การบังคับใช้ขีดจำกัด

  • maxConcurrent: ลูกแบบตัวรวบรวมทำงานบนเลนซับเอเจนต์ที่มีอยู่ แต่ นับแยกตามกลุ่ม swarm; การสร้างที่เกินขีดจำกัดจะเข้าคิวแบบ FIFO (ฝั่งโฮสต์ ใน เส้นทางการสร้าง — ส่งคืน runId ทันที และเริ่มทำงานเมื่อมีช่องว่าง)
  • maxChildrenPerGroup / maxTotalPerGroup: การสร้างจะถูกปฏิเสธด้วยข้อผิดพลาดแบบมีชนิด เมื่อเกินค่า; ข้อความข้อผิดพลาดจะระบุคีย์การกำหนดค่า
  • ความลึก: ลูกแบบตัวรวบรวมคงความหมายของ DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH (ลูกเป็นโหนดปลายทาง เว้นแต่กำหนดค่าการซ้อนอย่างชัดเจน)

6. สัญญาการทดสอบ (v1, เลน A)

  • ยูนิต: การแก้ค่า/จำกัดช่วงการกำหนดค่า; การปฏิเสธโดยเกตเมื่อปิดใช้งาน; การใช้ค่าเริ่มต้น ของ groupId; การบังคับใช้ขีดจำกัด (เข้าคิว + ปฏิเสธ); ความหมายการแข่งขันของการรอ; การทำซ้ำ การรอโดยไม่เปลี่ยนผลลัพธ์; การปฏิเสธความเป็นเจ้าของ; การตรวจสอบเอาต์พุตแบบมีโครงสร้าง + กระตุ้นให้ลองใหม่ + เส้นทาง schemaError; การส่ง fastMode ไปยังแพตช์เซสชัน; การตรวจสอบ defaultAgentId
  • การผสานรวม (vitest, รันไทม์โมเดลจำลอง): สร้างลูกแบบตัวรวบรวม 3 ตัว รอ ในลูป ยืนยันลำดับการเสร็จก่อนและการระบายขั้นสุดท้าย; การจำลอง การรีสตาร์ต Gateway: โหลดรีจิสทรีใหม่ → การรอแก้ผลจากการเสร็จสิ้นที่คงอยู่
  • การทดสอบทั้งหมดอยู่ร่วมกับ *.test.ts; ไม่มีการเรียกโมเดลจริง

7. พื้นผิวฝั่งเกสต์ QuickJS (เลน B, หลัง Core)

  • โกลบอลฝั่งเกสต์ติดตั้งใน CONTROLLER_SOURCE (src/agents/code-mode.worker.ts:190-374) และเพิ่มชื่อสงวนใน code-mode-namespaces.ts:
    • agents.run(prompt, opts) → Promise<result|structured> — รูปแบบย่อ: การสร้างตัวรวบรวม + การรอแบบพักไว้บนเมธอดบริดจ์เฉพาะ (agentWait) ซึ่งโฮสต์จะตัดสินผลเมื่อเสร็จสมบูรณ์ (ไม่มีการสำรวจ; ปลอดภัยต่อสแนปช็อต)
    • agents.session(system, opts) → Promise<handle>; handle.send(input, opts) → Promise<...>; handle.close() (v1.1 — จัดส่งหลัง run(); ใช้ mode:"session" + ระเบียนตัวรวบรวมต่อเทิร์น)
    • phase(title), log(message) — การแจ้งเตือนผ่านบริดจ์แบบส่งแล้วไม่รอผล → เหตุการณ์ความคืบหน้าของ swarm
  • เพิ่มเมธอดบริดจ์ใน CodeModeBridgeMethod (code-mode.ts:91): agentSpawn, agentWait, swarmNote โดย agentSpawn/agentWait ปลอดภัยต่อการเล่นซ้ำ โดยโครงสร้าง: คีย์การทำซ้ำโดยไม่เปลี่ยนผลลัพธ์ (codeModeRunId, bridgeId) ถูกเก็บไว้ในระเบียนรีจิสทรี; การรีสตาร์ตจะตัดสินผลซ้ำจากการเสร็จสิ้นที่คงอยู่ และไม่สร้างซ้ำ
  • การเรียกบริดจ์ agentWait ที่รอดำเนินการจะขยาย TTL ของสแนปช็อตการทำงาน (ชุด เอเจนต์ที่รอดำเนินการคือสัญญาณ; ไม่มีแฟล็ก)
  • ไฟล์เสมือน API.read("agents.d.ts") อธิบายพื้นผิวแบบมีชนิด + รูปแบบ การกระจายงาน / เกต / รอบ (createCodeModeApiVirtualFiles, code-mode-namespaces.ts:876)

8. การฉายไปยังฮาร์เนส Codex (เลนภายหลัง)

  • sessions_spawn (พร้อมพารามิเตอร์ใหม่) และ agents_wait ไหลผ่าน บริดจ์เครื่องมือแบบไดนามิกที่มีอยู่; ภายในสคริปต์โหมดโค้ด Codex ทั้งสองจะปรากฏเป็น tools.* โดยอัตโนมัติ (ตรวจสอบแล้ว: codex-rs/code-mode/src/runtime/globals.rs:14-65, codex-rs/core/src/tools/spec_plan.rs:448-507)
  • agents_wait ได้รับคลาสหมดเวลาแบบยาวสำหรับเครื่องมือไดนามิก (ขีดจำกัด 600s; extensions/codex/src/app-server/dynamic-tool-execution.ts:37-39) และ ถูกทำเครื่องหมายว่าปลอดภัยต่อการหมดเวลา/เล่นซ้ำ
  • คีย์กลุ่มสำหรับพาเรนต์ Codex: swarm:<parentSessionKey>:<turnId>
  • ซับเอเจนต์ spawn_agent แบบเนทีฟของ Codex อยู่ร่วมกันได้; แถวมิเรอร์งานของซับเอเจนต์เหล่านั้นส่งข้อมูล ไปยังพื้นผิวความคืบหน้าเดียวกัน

9. การคงอยู่และการเก็บรักษา

  • ไม่มีที่เก็บใหม่ ระเบียนรีจิสทรีขยายตาราง SQLite ของรีจิสทรีซับเอเจนต์ ที่มีอยู่; ลูกเป็นแถว sessions ทั่วไป เพิ่มเฉพาะคอลัมน์ — การเปลี่ยนแปลงใด ๆ ที่ต้องเพิ่มเวอร์ชันสคีมา SQLite ต้องได้รับ การอนุมัติอย่างชัดเจนจากผู้ดูแลก่อน (นโยบายรีโพ)
  • รหัสกลุ่ม swarm อยู่ในระเบียนรีจิสทรี + ข้อมูลเมตาของเซสชันลูก
  • การเก็บรักษา: ระเบียนตัวรวบรวมที่เสร็จสมบูรณ์จะคงอยู่จนถึง การเก็บกลุ่มถาวร: เมื่อการทำงานของพาเรนต์เสร็จสิ้น (หรือ TTL หมดอายุ) ลูกของกลุ่มจะถูกเก็บถาวร เป็นชุด (ขยายการกวาด DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES ที่มีอยู่ให้ทำงานแยกตามกลุ่ม)

10. พื้นผิวความคืบหน้า ("จุดต่าง ๆ") — เลนภายหลัง

  • ทำงานโดยนัยและขับเคลื่อนโดยฮาร์เนส อนุมานจาก SSE sessions.changed ที่มีอยู่ + รีจิสทรี; บันทึก phase/log เพิ่มความหมาย ไม่มีการเรนเดอร์ที่ขับเคลื่อนโดยเอเจนต์
  • Control UI: ตัวเรนเดอร์ swarm ในตระกูลวิดเจ็ตพื้นที่ทำงาน (ui/src/lib/workspace/widgets/) — ตารางจุดที่จัดกลุ่มตามเฟส บรรทัด ผู้บรรยาย สถานะ/ป้ายกำกับ/โมเดลต่อจุด; ทรีลูกในแถบด้านข้างไม่เปลี่ยนแปลง
  • ช่องทาง: ข้อความสถานะที่แก้ไขและควบคุมอัตราหนึ่งข้อความต่อกลุ่ม (ปฏิบัติตาม docs/concepts/streaming.md; ห้ามส่งข้อความแยกต่อลูก)

11. หน้า Labs (Control UI, เลนอิสระ)

Settings → Labs: ตัวสลับฟีเจอร์ทดลอง โดยรายการแรกคือ Code Mode และ Swarm แต่ละแถวประกอบด้วยชื่อ คำอธิบายหนึ่งบรรทัด ลิงก์เอกสาร และตัวสลับที่เชื่อมต่อ ผ่าน RPC config.patch ที่มีอยู่ (merge-patch ตาม RFC 7396 — ตั้งค่า tools.codeMode.enabled / tools.swarm.enabled) พร้อมคำแนะนำว่า "ต้องรีสตาร์ต" เมื่อเกี่ยวข้อง ผู้ใช้ค้นพบได้ง่าย แต่ข้อความต้องระบุสถานะทดลอง อย่างชัดเจน i18n: สตริงทั้งหมดผ่าน en.ts + ไปป์ไลน์ซิงค์ตามปกติ

12. ตำแหน่งการทำงาน (ภายหลัง)

  • placement ให้เลือกเมื่อ spawn: "local" (ค่าเริ่มต้น) | "cloud:<profile>" ผ่าน การส่งต่อสภาพแวดล้อม worker ที่มีอยู่ (sessions.dispatch) ส่วนตำแหน่งการทำงานแบบพูล เพิ่มภายหลัง หาก child ของ SSH sandbox ในกล่องที่ใช้ร่วมกันพิสูจน์แล้วว่าไม่เพียงพอ
  • VM ของ Orchestrator จะอยู่บน Gateway เสมอ ส่วน settle/dots/budget ไม่รับรู้ตำแหน่งการทำงาน

13. สิ่งที่ไม่ใช่เป้าหมาย

  • ไม่มี DSL แบบกราฟ — ลำดับการควบคุมคือกราฟ (ตั้งใจออกแบบและมีเอกสารกำกับ)
  • ไม่มีการเปลี่ยนแปลงซอร์สของ Codex และไม่ใช้ส่วนภายในของ Codex Code Mode ซ้ำ
  • ไม่มีเวิร์กโฟลว์ที่บันทึกไว้/ตั้งชื่อใน v1 และไม่มีจุดเริ่มต้นผ่าน CLI
  • ไม่มีการส่งต่อการอนุมัติของผู้ดำเนินการจากแต่ละ child ขึ้นมา
  • ไม่มีการจัดเตรียมคลาวด์แบบ 1:1 ในระดับ fan-out
  • ไม่มี compat shim สำหรับรันไทม์ในสถานะคงที่ Swarm เป็นพื้นผิวใหม่ที่มีการควบคุมการเปิดใช้

14. ระยะการสร้าง / การแบ่ง PR

  1. เลน A (core): การกำหนดค่า §4 + spawn/wait/caps/approvals ใน §5 + การทดสอบ §6
  2. เลน C (หน้า Labs): §11 — เป็นอิสระและรวมได้ก่อน
  3. เลน B (พื้นผิว QuickJS): §7 — หลังจากรวมสัญญาของ A แล้ว
  4. ตัวเรนเดอร์ dots (§10), การฉายภาพ Codex (§8), agents.session (§7 v1.1), ตำแหน่งการทำงาน (§12), การเขียนเอกสารผู้ใช้ใหม่ — เป็น PR ติดตามผลตามลำดับนี้

แต่ละ PR: ไปป์ไลน์ CI ผ่านทั้งหมด, $autoreview สะอาด, ปิดไว้เป็นค่าเริ่มต้น และ main พร้อมเผยแพร่

Was this useful?
On this page

On this page