ข้ามไปยังเนื้อหา

การดำเนินการกับเซสชัน: export, dump, share, fork, resume/continue

เอกสารนี้อธิบายพฤติกรรมที่ผู้ดำเนินการมองเห็นได้สำหรับการดำเนินการ export/share/fork/resume ของเซสชัน ตามที่ได้นำไปใช้งานในปัจจุบัน

การดำเนินการเส้นทางเข้าใช้งานการเปลี่ยนแปลงเซสชันการสร้าง/สลับไฟล์เซสชันผลลัพธ์ที่ได้
/dumpคำสั่ง slash แบบโต้ตอบไม่มีไม่มีข้อความในคลิปบอร์ด
/export [path]คำสั่ง slash แบบโต้ตอบไม่มีไม่มีไฟล์ HTML
--export <session.jsonl> [outputPath]เส้นทางเร็วเมื่อเริ่ม CLIไม่มีการเปลี่ยนแปลงเซสชันขณะทำงานไม่มีเซสชันที่ใช้งานอยู่; อ่านไฟล์เป้าหมายไฟล์ HTML
/shareคำสั่ง slash แบบโต้ตอบไม่มีไม่มีHTML ชั่วคราว + URL แชร์/gist
/forkคำสั่ง slash แบบโต้ตอบมี (เอกลักษณ์เซสชันที่ใช้งานอยู่เปลี่ยนแปลง)สร้างไฟล์เซสชันใหม่และสลับเซสชันปัจจุบันไปยังไฟล์นั้น (เฉพาะโหมดถาวร)คัดลอกไดเรกทอรีอาร์ติแฟกต์ไปยังเนมสเปซเซสชันใหม่เมื่อมีอยู่
/resumeคำสั่ง slash แบบโต้ตอบมี (สถานะในหน่วยความจำที่ใช้งานอยู่ถูกแทนที่)สลับไปยังไฟล์เซสชันที่มีอยู่ที่เลือกไม่มี
--resumeเริ่ม CLI (ตัวเลือก)มี หลังจากสร้างเซสชันเปิดไฟล์เซสชันที่มีอยู่ที่เลือกไม่มี
--resume <id|path>เริ่ม CLIมี หลังจากสร้างเซสชันเปิดเซสชันที่มีอยู่; กรณีข้ามโปรเจกต์สามารถแยกสาขาไปยังโปรเจกต์ปัจจุบันได้ไม่มี
--continueเริ่ม CLIมี หลังจากสร้างเซสชันเปิดเบรดครัมบ์เทอร์มินัลหรือเซสชันล่าสุด; สร้างอันใหม่หากไม่มีไม่มี

ขั้นตอนการทำงาน:

  1. InputController ส่งต่อ /export... ไปยัง CommandController.handleExportCommand
  2. คำสั่งแยกตามช่องว่างและใช้เฉพาะอาร์กิวเมนต์แรกหลัง /export เป็น outputPath
  3. AgentSession.exportToHtml() เรียก exportSessionToHtml(sessionManager, state, { outputPath, themeName })
  4. เมื่อสำเร็จ UI จะแสดงเส้นทางและเปิดไฟล์ในเบราว์เซอร์

รายละเอียดพฤติกรรม:

  • อาร์กิวเมนต์ --copy, clipboard, และ copy จะถูกปฏิเสธอย่างชัดเจนพร้อมคำเตือนให้ใช้ /dump แทน
  • การส่งออกฝังส่วนหัวเซสชัน/รายการ/ใบไม้ บวกกับ systemPrompt ปัจจุบันและคำอธิบายเครื่องมือจากสถานะของเอเจนต์
  • ไม่มีการเพิ่มรายการเซสชันในระหว่างการส่งออก

ข้อควรระวัง:

  • การแยกวิเคราะห์อาร์กิวเมนต์อิงตามช่องว่าง (text.split(/\s+/)) ดังนั้นเส้นทางที่มีเครื่องหมายอัญประกาศซึ่งมีช่องว่างจะไม่ถูกเก็บรักษาเป็นเส้นทางเดียวในเส้นทางคำสั่งนี้

ขั้นตอนการทำงานใน main.ts:

  1. จัดการแต่เนิ่น (ก่อนเริ่มต้นแบบโต้ตอบ/เซสชัน)
  2. เรียก exportFromFile(inputPath, outputPath?)
  3. SessionManager.open(inputPath) โหลดรายการ จากนั้นสร้างและเขียน HTML
  4. กระบวนการพิมพ์ Exported to: ... และออก

รายละเอียดพฤติกรรม:

  • ไฟล์นำเข้าที่ไม่มีอยู่จะแสดงผลเป็น File not found: <path>
  • เส้นทางนี้ไม่สร้าง AgentSession และไม่เปลี่ยนแปลงเซสชันที่กำลังทำงานอยู่

ขั้นตอนการทำงาน:

  1. CommandController.handleDumpCommand() เรียก session.formatSessionAsText()
  2. หากได้สตริงว่าง จะรายงาน No messages to dump yet.
  3. มิฉะนั้นจะคัดลอกไปยังคลิปบอร์ดผ่าน copyToClipboard ของระบบ

เนื้อหาดัมพ์ประกอบด้วย:

  • System prompt
  • โมเดลที่ใช้งานอยู่/ระดับการคิด
  • นิยามเครื่องมือ + พารามิเตอร์
  • ข้อความผู้ใช้/ผู้ช่วย
  • บล็อกการคิดและการเรียกใช้เครื่องมือ
  • ผลลัพธ์เครื่องมือและบล็อกการทำงาน (ยกเว้นรายการ bash/python ที่มี excludeFromContext)
  • รายการ custom/hook/file mention/branch summary/compaction summary

การดัมพ์ไม่ทำให้เกิดการเปลี่ยนแปลงความคงอยู่ของเซสชัน

/share ทำงานแบบโต้ตอบเท่านั้นและเริ่มต้นด้วยการส่งออกเซสชันปัจจุบันไปยังไฟล์ HTML ชั่วคราวเสมอ

  • เส้นทางไฟล์ชั่วคราว: ${os.tmpdir()}/${Snowflake.next()}.html
  • ใช้ session.exportToHtml(tmpFile)
  • หากการส่งออกล้มเหลว (โดยเฉพาะเซสชันในหน่วยความจำ) การแชร์จะสิ้นสุดพร้อมข้อผิดพลาด

loadCustomShare() ตรวจสอบ ~/.xcsh/agent สำหรับไฟล์ที่พบก่อนในลำดับนี้:

  • share.ts
  • share.js
  • share.mjs

ข้อกำหนด:

  • โมดูลต้องส่งออกฟังก์ชัน (htmlPath) => Promise<CustomShareResult | string | undefined> แบบ default

หากมีอยู่และใช้งานได้:

  • UI เข้าสู่สถานะโหลด Sharing...
  • การตีความผลลัพธ์ของตัวจัดการ:
    • string => ถือเป็น URL แสดงและเปิด
    • object => แสดง url และ/หรือ message; เปิด url
    • undefined/falsy => แสดงข้อความทั่วไป Session shared
  • ไฟล์ชั่วคราวจะถูกลบหลังจากเสร็จสิ้น

พฤติกรรม fallback ที่สำคัญ:

  • หากมีตัวจัดการกำหนดเองแต่การโหลดล้มเหลว คำสั่งจะเกิดข้อผิดพลาดและส่งคืน
  • หากตัวจัดการกำหนดเองทำงานและเกิดข้อผิดพลาด คำสั่งจะเกิดข้อผิดพลาดและส่งคืน
  • ในทั้งสองกรณีที่ล้มเหลว จะไม่ ถอยกลับไปใช้ GitHub gist
  • การถอยกลับไปใช้ gist เกิดขึ้นเฉพาะเมื่อไม่พบสคริปต์แชร์กำหนดเองเท่านั้น

เฉพาะเมื่อไม่พบตัวจัดการแชร์กำหนดเอง:

  1. ตรวจสอบ gh auth status
  2. แสดงโหลด Creating gist...
  3. รัน gh gist create --public=false <tmpFile>
  4. แยกวิเคราะห์ URL ของ gist ดึง gist id สร้าง preview URL https://gistpreview.github.io/?<id>
  5. แสดงทั้ง URL preview และ gist; เปิด preview

ความหมายของการยกเลิก/ยกเลิกการดำเนินการในการแชร์:

  • โหลดมี hook onAbort ที่คืนค่า UI ของตัวแก้ไขและรายงาน Share cancelled
  • คำสั่ง gh gist create ที่ทำงานอยู่ไม่ได้รับการส่งสัญญาณยกเลิกในเส้นทางโค้ดนี้; การยกเลิกอยู่ในระดับ UI และตรวจสอบหลังจากคำสั่งส่งคืน

/fork สร้างเซสชันใหม่จากเซสชันปัจจุบันและสลับเอกลักษณ์ของเซสชันที่ใช้งานอยู่

  • หากเอเจนต์กำลังสตรีม /fork จะถูกปฏิเสธพร้อมคำเตือน
  • ตัวบ่งชี้สถานะ/การโหลด UI จะถูกล้างก่อนดำเนินการ

AgentSession.fork():

  1. ส่ง session_before_switch พร้อม reason: "fork" (สามารถยกเลิกได้)
  2. ล้างการเขียนที่รอดำเนินการ
  3. เรียก SessionManager.fork()
  4. คัดลอกไดเรกทอรีอาร์ติแฟกต์จากเนมสเปซเซสชันเก่าไปยังเนมสเปซใหม่ (ทำได้ดีที่สุด; ความล้มเหลวในการคัดลอกที่ไม่ใช่ ENOENT จะถูกบันทึก ไม่ถือว่าร้ายแรง)
  5. อัปเดต agent.sessionId
  6. ส่ง session_switch พร้อม reason: "fork"

พฤติกรรมของ SessionManager.fork():

  • ต้องการโหมดถาวรและไฟล์เซสชันที่มีอยู่
  • สร้าง session id ใหม่และเส้นทางไฟล์ JSONL ใหม่
  • เขียนส่วนหัวใหม่ด้วย:
    • id ใหม่
    • timestamp ใหม่
    • cwd ไม่เปลี่ยนแปลง
    • parentSession ตั้งค่าเป็น session id ก่อนหน้า
  • เก็บรายการที่ไม่ใช่ส่วนหัวทั้งหมดไว้ในไฟล์ใหม่โดยไม่เปลี่ยนแปลง
  • ตัวจัดการเซสชันในหน่วยความจำส่งคืน undefined จาก fork()
  • AgentSession.fork() ส่งคืน false
  • UI รายงาน Fork failed (session not persisted or cancelled)

ขั้นตอนการทำงาน:

  1. เปิดตัวเลือกเซสชันที่เติมข้อมูลผ่าน SessionManager.list(currentCwd, currentSessionDir)
  2. เมื่อเลือกแล้ว SelectorController.handleResumeSession(sessionPath) เรียก session.switchSession(sessionPath)
  3. UI ล้าง/สร้างแชทและ todos ใหม่ จากนั้นรายงาน Resumed session

หมายเหตุ:

  • ตัวเลือกนี้แสดงเซสชันเฉพาะในขอบเขตไดเรกทอรีเซสชันปัจจุบัน
  • ไม่ใช้การค้นหาข้ามโปรเจกต์แบบทั่วโลก
  • main.ts แสดงรายการเซสชันสำหรับ cwd/sessionDir ปัจจุบันและเปิดตัวเลือก
  • เส้นทางที่เลือกจะเปิดด้วย SessionManager.open(selectedPath) ก่อนสร้างเซสชัน

ลำดับการแก้ไขของ createSessionManager():

  1. หากค่าดูเหมือนเส้นทาง (/, \, หรือ .jsonl) ให้เปิดโดยตรง
  2. มิฉะนั้นให้ถือเป็นคำนำหน้า id:
    • ค้นหาในขอบเขตปัจจุบัน (SessionManager.list(cwd, sessionDir))
    • หากไม่พบและไม่มี sessionDir ที่ชัดเจน ให้ค้นหาทั่วโลก (SessionManager.listAll())

พฤติกรรมเมื่อพบ id ข้ามโปรเจกต์:

  • หาก cwd ของเซสชันที่พบแตกต่างจาก cwd ปัจจุบัน CLI จะถามว่า:
    • Session found in different project ... Fork into current directory? [y/N]
  • หากตอบใช่: SessionManager.forkFrom(match.path, cwd, sessionDir) สร้างไฟล์แยกสาขาใหม่ในเครื่อง
  • หากตอบไม่/ค่าเริ่มต้นแบบไม่ใช่ TTY: คำสั่งจะเกิดข้อผิดพลาด

SessionManager.continueRecent(cwd, sessionDir):

  1. แก้ไขไดเรกทอรีเซสชันสำหรับ cwd ปัจจุบัน
  2. อ่านเบรดครัมบ์ที่กำหนดขอบเขตเทอร์มินัลก่อน
  3. ถอยกลับไปใช้ไฟล์เซสชันที่แก้ไขล่าสุด
  4. เปิดเซสชันที่พบ; หากไม่มี ให้สร้างเซสชันใหม่

นี่คือพฤติกรรมเฉพาะเมื่อเริ่มต้น; ไม่มีคำสั่ง slash /continue แบบโต้ตอบ

วิธีที่การสลับเซสชันเปลี่ยนแปลงสถานะขณะทำงานจริง

หัวข้อที่มีชื่อว่า “วิธีที่การสลับเซสชันเปลี่ยนแปลงสถานะขณะทำงานจริง”

AgentSession.switchSession(sessionPath) ดำเนินการเปลี่ยนผ่านขณะทำงานที่ใช้โดยการดำเนินการแบบ resume:

  1. ส่ง session_before_switch พร้อม reason: "resume" และ targetSessionFile (สามารถยกเลิกได้)
  2. ยกเลิกการสมัครรับเหตุการณ์ของเอเจนต์และยกเลิกงานที่กำลังดำเนินอยู่
  3. ล้างข้อความ steering/follow-up/next-turn ที่อยู่ในคิว
  4. ล้างการเขียนตัวจัดการเซสชันปัจจุบัน
  5. sessionManager.setSessionFile(sessionPath) และอัปเดต agent.sessionId
  6. สร้างบริบทเซสชันจากรายการที่โหลด
  7. ส่ง session_switch พร้อม reason: "resume"
  8. แทนที่ข้อความเอเจนต์จากบริบท
  9. คืนค่าโมเดล (หากมีในรีจิสทรีปัจจุบัน)
  10. คืนค่าหรือเริ่มต้นระดับการคิด
  11. เชื่อมต่อการสมัครรับเหตุการณ์เอเจนต์ใหม่

switchSession() เองไม่สร้างไฟล์เซสชันใหม่

สำหรับ newSession, fork, และ switchSession:

  • เหตุการณ์ก่อน: session_before_switch
    • เหตุผล: new, fork, resume
    • สามารถยกเลิกได้โดยการส่งคืน { cancel: true }
  • เหตุการณ์หลัง: session_switch
    • ชุดเหตุผลเดียวกัน
    • รวม previousSessionFile

ExtensionRunner.emit() ส่งคืนก่อนกำหนดเมื่อพบผลลัพธ์ก่อนเหตุการณ์ที่ยกเลิกแรก

SDK เชื่อมต่อเหตุการณ์เซสชันส่วนขยายกับ callbacks onSession ของเครื่องมือกำหนดเอง:

  • session_switch -> onSession({ reason: "switch", previousSessionFile })
  • session_branch -> reason: "branch"
  • session_start -> reason: "start"
  • session_tree -> reason: "tree"
  • session_shutdown -> reason: "shutdown"

callbacks เหล่านี้มีไว้สำหรับการสังเกตเท่านั้น ไม่สามารถยกเลิกการสลับ/แยกสาขาได้

พื้นผิวการยกเลิกอื่น ๆ ที่เกี่ยวข้องกับเอกสารนี้

หัวข้อที่มีชื่อว่า “พื้นผิวการยกเลิกอื่น ๆ ที่เกี่ยวข้องกับเอกสารนี้”
  • /fork ถูกบล็อกขณะสตรีม (ผู้ใช้ต้องรอ/ยกเลิกการตอบสนองปัจจุบันก่อน)
  • ตัวเลือก /resume สามารถยกเลิกได้โดยผู้ใช้ปิดตัวเลือก
  • --resume <id> ข้ามโปรเจกต์สามารถยกเลิกได้โดยการปฏิเสธพรอมต์การแยกสาขา
  • /share มีเส้นทางยกเลิก UI (Share cancelled) สำหรับขั้นตอน gist; ไม่เชื่อมต่อ semantics การฆิลกระบวนการสำหรับ gh gist create ในเส้นทางโค้ดนี้

เมื่อตัวจัดการเซสชันสร้างด้วย SessionManager.inMemory() (--no-session):

  • เส้นทางไฟล์เซสชันไม่มีอยู่
  • /export และ /share ล้มเหลวพร้อมข้อความ Cannot export in-memory session to HTML (ส่งต่อไปยัง UI ข้อผิดพลาดคำสั่ง)
  • /fork ล้มเหลวเพราะ SessionManager.fork() ต้องการความคงอยู่
  • /dump ยังคงใช้งานได้เพราะทำการ serialize สถานะเอเจนต์ในหน่วยความจำ
  • semantics ของ resume/continue ใน CLI จะถูกข้ามหากตั้งค่า --no-session เพราะการสร้างตัวจัดการส่งคืนแบบในหน่วยความจำทันที

ข้อควรระวังที่ทราบในการนำไปใช้งาน (ตามโค้ดปัจจุบัน)

หัวข้อที่มีชื่อว่า “ข้อควรระวังที่ทราบในการนำไปใช้งาน (ตามโค้ดปัจจุบัน)”
  • SelectorController.handleResumeSession() ไม่ตรวจสอบผลลัพธ์ boolean จาก session.switchSession(...); การสลับที่ถูกยกเลิกโดย hook ยังคงดำเนินผ่านเส้นทาง repaint/status “Resumed session” ของ UI ได้
  • ความล้มเหลวของ custom-share ใน /share จะไม่ลดระดับลงไปใช้ gist fallback เริ่มต้น; แต่จะสิ้นสุดคำสั่งพร้อมข้อผิดพลาด
  • การแยกวิเคราะห์อาร์กิวเมนต์ของ /export เป็นแบบง่ายและไม่เก็บรักษาเส้นทางที่มีเครื่องหมายอัญประกาศซึ่งมีช่องว่าง