sayacode 3.2.1__tar.gz → 3.2.2__tar.gz

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 (187) hide show
  1. {sayacode-3.2.1 → sayacode-3.2.2}/CHANGELOG.md +7 -0
  2. {sayacode-3.2.1 → sayacode-3.2.2}/PKG-INFO +3 -3
  3. {sayacode-3.2.1 → sayacode-3.2.2}/README.md +2 -2
  4. sayacode-3.2.2/frontend/src/products/useProduct.ts +98 -0
  5. {sayacode-3.2.1 → sayacode-3.2.2}/pyproject.toml +1 -1
  6. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/application.py +16 -13
  7. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/audit.py +9 -8
  8. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/preferences.py +1 -1
  9. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/inbox.py +10 -1
  10. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/records.py +2 -2
  11. sayacode-3.2.1/src/sayacode/web/static/assets/AgentGraph-WrnBvtXW.js → sayacode-3.2.2/src/sayacode/web/static/assets/AgentGraph-CRRA3WQV.js +1 -1
  12. sayacode-3.2.1/src/sayacode/web/static/assets/ProductDialog-CUkF8AeG.js → sayacode-3.2.2/src/sayacode/web/static/assets/ProductDialog-CkAhucsM.js +1 -1
  13. sayacode-3.2.1/src/sayacode/web/static/assets/Trajectory-DMDvzqgx.js → sayacode-3.2.2/src/sayacode/web/static/assets/Trajectory-Cafi12z-.js +1 -1
  14. sayacode-3.2.1/src/sayacode/web/static/assets/index-CDLQ7XMJ.js → sayacode-3.2.2/src/sayacode/web/static/assets/index-BKGwQS3l.js +2 -2
  15. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/index.html +1 -1
  16. {sayacode-3.2.1 → sayacode-3.2.2}/uv.lock +1 -1
  17. sayacode-3.2.1/frontend/src/products/useProduct.ts +0 -40
  18. {sayacode-3.2.1 → sayacode-3.2.2}/.gitignore +0 -0
  19. {sayacode-3.2.1 → sayacode-3.2.2}/LICENSE +0 -0
  20. {sayacode-3.2.1 → sayacode-3.2.2}/assets/image1.png +0 -0
  21. {sayacode-3.2.1 → sayacode-3.2.2}/assets/image2.png +0 -0
  22. {sayacode-3.2.1 → sayacode-3.2.2}/assets/image3.png +0 -0
  23. {sayacode-3.2.1 → sayacode-3.2.2}/assets/image4.png +0 -0
  24. {sayacode-3.2.1 → sayacode-3.2.2}/docs/architecture.md +0 -0
  25. {sayacode-3.2.1 → sayacode-3.2.2}/docs/design/memory-system.md +0 -0
  26. {sayacode-3.2.1 → sayacode-3.2.2}/docs/research/coding-agent-memory.md +0 -0
  27. {sayacode-3.2.1 → sayacode-3.2.2}/docs/webui.md +0 -0
  28. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/.prettierrc.json +0 -0
  29. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/README.md +0 -0
  30. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/index.html +0 -0
  31. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/package-lock.json +0 -0
  32. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/package.json +0 -0
  33. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/App.module.css +0 -0
  34. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/App.tsx +0 -0
  35. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/client.test.ts +0 -0
  36. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/client.ts +0 -0
  37. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/events.test.ts +0 -0
  38. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/events.ts +0 -0
  39. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/preloadRecovery.test.ts +0 -0
  40. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/preloadRecovery.ts +0 -0
  41. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/session.test.ts +0 -0
  42. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/types.ts +0 -0
  43. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/AgentGraph.module.css +0 -0
  44. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/AgentGraph.tsx +0 -0
  45. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ApprovalDialog.module.css +0 -0
  46. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ApprovalDialog.tsx +0 -0
  47. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ComposerActions.module.css +0 -0
  48. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ComposerActions.tsx +0 -0
  49. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.module.css +0 -0
  50. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.test.ts +0 -0
  51. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.tsx +0 -0
  52. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.module.css +0 -0
  53. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.test.tsx +0 -0
  54. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.tsx +0 -0
  55. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/DeleteSessionDialog.tsx +0 -0
  56. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/DirectoryPicker.tsx +0 -0
  57. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.module.css +0 -0
  58. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.test.tsx +0 -0
  59. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.tsx +0 -0
  60. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/MarkdownContent.tsx +0 -0
  61. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.module.css +0 -0
  62. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.test.ts +0 -0
  63. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.tsx +0 -0
  64. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/RecoveryScreen.tsx +0 -0
  65. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Sidebar.module.css +0 -0
  66. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Sidebar.tsx +0 -0
  67. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Trajectory.module.css +0 -0
  68. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Trajectory.tsx +0 -0
  69. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/approvalChoices.test.ts +0 -0
  70. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/approvalChoices.ts +0 -0
  71. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/i18n.test.ts +0 -0
  72. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/i18n.tsx +0 -0
  73. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/conversationTimeline.test.ts +0 -0
  74. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/conversationTimeline.ts +0 -0
  75. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/format.ts +0 -0
  76. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/tasks.test.ts +0 -0
  77. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/tasks.ts +0 -0
  78. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/trust.test.ts +0 -0
  79. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/trust.ts +0 -0
  80. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/main.tsx +0 -0
  81. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/ProductDialog.module.css +0 -0
  82. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/ProductDialog.tsx +0 -0
  83. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/SettingsPreferences.module.css +0 -0
  84. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/SettingsPreferences.tsx +0 -0
  85. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/diagnostics.tsx +0 -0
  86. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/mcp.tsx +0 -0
  87. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/memory.tsx +0 -0
  88. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/modelTest.test.ts +0 -0
  89. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/modelTest.ts +0 -0
  90. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/models.tsx +0 -0
  91. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/panel.module.css +0 -0
  92. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/session.module.css +0 -0
  93. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/session.tsx +0 -0
  94. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/skills.tsx +0 -0
  95. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/activity.test.ts +0 -0
  96. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/activity.ts +0 -0
  97. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/eventPolicy.test.ts +0 -0
  98. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/eventPolicy.ts +0 -0
  99. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/liveText.test.ts +0 -0
  100. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/liveText.ts +0 -0
  101. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/messages.test.ts +0 -0
  102. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/messages.ts +0 -0
  103. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/queuedInput.test.ts +0 -0
  104. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/queuedInput.ts +0 -0
  105. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/snapshotOrder.test.ts +0 -0
  106. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/snapshotOrder.ts +0 -0
  107. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/useWorkspace.resync.test.ts +0 -0
  108. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/useWorkspace.ts +0 -0
  109. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/styles/shared.module.css +0 -0
  110. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/styles/tokens.css +0 -0
  111. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/tsconfig.json +0 -0
  112. {sayacode-3.2.1 → sayacode-3.2.2}/frontend/vite.config.ts +0 -0
  113. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/__init__.py +0 -0
  114. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/__main__.py +0 -0
  115. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/__init__.py +0 -0
  116. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/context.py +0 -0
  117. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/events.py +0 -0
  118. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/graph.py +0 -0
  119. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/models.py +0 -0
  120. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/runtime.py +0 -0
  121. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/__init__.py +0 -0
  122. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/middleware.py +0 -0
  123. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/policy.py +0 -0
  124. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/__init__.py +0 -0
  125. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/events.py +0 -0
  126. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/headless.py +0 -0
  127. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/main.py +0 -0
  128. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/web.py +0 -0
  129. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/config.py +0 -0
  130. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/diagnostics.py +0 -0
  131. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/__init__.py +0 -0
  132. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/hooks.py +0 -0
  133. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/instructions.py +0 -0
  134. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/mcp.py +0 -0
  135. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/skills.py +0 -0
  136. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/__init__.py +0 -0
  137. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/application.py +0 -0
  138. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/attachments.py +0 -0
  139. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/events.py +0 -0
  140. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/products.py +0 -0
  141. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/run_actions.py +0 -0
  142. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/session_operations.py +0 -0
  143. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/views.py +0 -0
  144. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/workspaces.py +0 -0
  145. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/__init__.py +0 -0
  146. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/evidence.py +0 -0
  147. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/learning.py +0 -0
  148. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/middleware.py +0 -0
  149. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/privacy.py +0 -0
  150. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/processing.py +0 -0
  151. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/records.py +0 -0
  152. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/repository.py +0 -0
  153. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/retrieval.py +0 -0
  154. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/service.py +0 -0
  155. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/tools.py +0 -0
  156. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/turns.py +0 -0
  157. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/paths.py +0 -0
  158. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/process.py +0 -0
  159. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/profiles.py +0 -0
  160. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/prompts.py +0 -0
  161. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/sessions.py +0 -0
  162. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/__init__.py +0 -0
  163. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/manager.py +0 -0
  164. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/middleware.py +0 -0
  165. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/tools.py +0 -0
  166. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/worktree.py +0 -0
  167. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/__init__.py +0 -0
  168. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/analysis.py +0 -0
  169. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/catalog.py +0 -0
  170. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/files.py +0 -0
  171. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/git.py +0 -0
  172. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/shell.py +0 -0
  173. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/web.py +0 -0
  174. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/__init__.py +0 -0
  175. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/app.py +0 -0
  176. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/attachments.py +0 -0
  177. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/contracts.py +0 -0
  178. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/products.py +0 -0
  179. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/responses.py +0 -0
  180. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/security.py +0 -0
  181. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/session_operations.py +0 -0
  182. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/AgentGraph-CEriGMSJ.css +0 -0
  183. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/MarkdownContent-DmcbXhCl.js +0 -0
  184. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/ProductDialog-Cq8hWItk.css +0 -0
  185. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/Trajectory-yaV4MOoU.css +0 -0
  186. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/index-EwIvkuyc.css +0 -0
  187. {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/jsx-runtime-D3jfb0Ew.js +0 -0
@@ -1,5 +1,12 @@
1
1
  # 更新日志
2
2
 
3
+ ## 3.2.2(2026-09-27)
4
+
5
+ - 收窄本地审计记录:任务状态不再复制提示词、追问、结果和上下文快照;审批暂停只记录待批准数量,运行失败只记录异常类型,工具轨迹不再写入调用参数中的路径和搜索词。
6
+ - 串行化进程内并发审计追加,修复 Windows 上自动记忆的并行模型回调可能覆盖同一审计文件记录的问题。
7
+ - 修正工作区或查询条件切换时旧请求晚到覆盖当前设置面板的问题;新资源加载前隐藏旧数据,旧操作完成后不会刷新另一个资源。
8
+ - Web 构建产物随修复更新,wheel 和源码包继续直接提供界面资源。历史审计文件不会自动改写。
9
+
3
10
  ## 3.2.1(2026-09-25)
4
11
 
5
12
  - 将独立的“当前运行活动”卡片替换为对话内可展开的过程行:并发工具按调用标识原位更新,长时间运行显示动态状态与耗时,完成后保留简明摘要;详细事件仍在“运行轨迹”中查看。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sayacode
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: A local LangChain and LangGraph coding assistant with a Web UI
5
5
  Project-URL: Homepage, https://github.com/saya-ch/sayacode
6
6
  Project-URL: Repository, https://github.com/saya-ch/sayacode
@@ -65,7 +65,7 @@ Description-Content-Type: text/markdown
65
65
 
66
66
  <p>
67
67
  <img src="https://img.shields.io/badge/Python-3.11%20%E2%80%93%203.13-334155" alt="Python 3.11 至 3.13">
68
- <img src="https://img.shields.io/badge/Version-3.2.1-EF5DA8" alt="版本 3.2.1">
68
+ <img src="https://img.shields.io/badge/Version-3.2.2-EF5DA8" alt="版本 3.2.2">
69
69
  <img src="https://img.shields.io/badge/License-MIT-334155" alt="MIT 许可证">
70
70
  </p>
71
71
  </div>
@@ -74,7 +74,7 @@ Description-Content-Type: text/markdown
74
74
 
75
75
  SAYACODE 是基于 **LangChain / LangGraph** 的本机编程助手。左侧管理工作区与会话,中间呈现对话和工具轨迹,右侧展示计划、子 Agent、审批与代码交付。Agent 由 LangChain `create_agent` 构建;LangGraph 管理消息、待办、检查点和审批中断。Web 页面只展示和操作运行结果,不另建一套 Agent 执行协议。
76
76
 
77
- **3.x 的运行方式:**交互式 TUI 已由 FastAPI + React 工作台取代;适合脚本和 CI 的无头 CLI 继续保留。3.1 增加运行中消息队列、整棵子 Agent 任务树停止和会话管理;3.2 整理四档信任策略;3.2.1 将工具与模型过程放入对话时间线。
77
+ **3.x 的运行方式:**交互式 TUI 已由 FastAPI + React 工作台取代;适合脚本和 CI 的无头 CLI 继续保留。3.1 增加运行中消息队列、整棵子 Agent 任务树停止和会话管理;3.2 整理四档信任策略与对话内运行过程,并持续改进审计隐私和界面状态可靠性。
78
78
 
79
79
  ## 快速开始
80
80
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  <p>
17
17
  <img src="https://img.shields.io/badge/Python-3.11%20%E2%80%93%203.13-334155" alt="Python 3.11 至 3.13">
18
- <img src="https://img.shields.io/badge/Version-3.2.1-EF5DA8" alt="版本 3.2.1">
18
+ <img src="https://img.shields.io/badge/Version-3.2.2-EF5DA8" alt="版本 3.2.2">
19
19
  <img src="https://img.shields.io/badge/License-MIT-334155" alt="MIT 许可证">
20
20
  </p>
21
21
  </div>
@@ -24,7 +24,7 @@
24
24
 
25
25
  SAYACODE 是基于 **LangChain / LangGraph** 的本机编程助手。左侧管理工作区与会话,中间呈现对话和工具轨迹,右侧展示计划、子 Agent、审批与代码交付。Agent 由 LangChain `create_agent` 构建;LangGraph 管理消息、待办、检查点和审批中断。Web 页面只展示和操作运行结果,不另建一套 Agent 执行协议。
26
26
 
27
- **3.x 的运行方式:**交互式 TUI 已由 FastAPI + React 工作台取代;适合脚本和 CI 的无头 CLI 继续保留。3.1 增加运行中消息队列、整棵子 Agent 任务树停止和会话管理;3.2 整理四档信任策略;3.2.1 将工具与模型过程放入对话时间线。
27
+ **3.x 的运行方式:**交互式 TUI 已由 FastAPI + React 工作台取代;适合脚本和 CI 的无头 CLI 继续保留。3.1 增加运行中消息队列、整棵子 Agent 任务树停止和会话管理;3.2 整理四档信任策略与对话内运行过程,并持续改进审计隐私和界面状态可靠性。
28
28
 
29
29
  ## 快速开始
30
30
 
@@ -0,0 +1,98 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+
3
+ interface ProductState<T> {
4
+ key: string;
5
+ data: T | null;
6
+ loading: boolean;
7
+ busy: boolean;
8
+ error: string | null;
9
+ }
10
+
11
+ export function useProduct<T>(key: string, load: () => Promise<T>) {
12
+ const loader = useRef(load);
13
+ const activeResource = useRef<{ key: string } | null>(null);
14
+ const requestVersion = useRef(0);
15
+ const [state, setState] = useState<ProductState<T>>({
16
+ key,
17
+ data: null,
18
+ loading: true,
19
+ busy: false,
20
+ error: null,
21
+ });
22
+
23
+ useLayoutEffect(() => {
24
+ loader.current = load;
25
+ });
26
+ useLayoutEffect(() => {
27
+ // 重访同一个 key 也会得到新身份,旧操作不能回写新页面。
28
+ activeResource.current = { key };
29
+ return () => {
30
+ activeResource.current = null;
31
+ requestVersion.current += 1;
32
+ };
33
+ }, [key]);
34
+
35
+ const refresh = useCallback(async () => {
36
+ const owner = activeResource.current;
37
+ if (owner?.key !== key) return;
38
+ const version = ++requestVersion.current;
39
+ setState((current) => ({
40
+ key,
41
+ data: current.key === key ? current.data : null,
42
+ loading: true,
43
+ busy: current.key === key && current.busy,
44
+ error: null,
45
+ }));
46
+ try {
47
+ const data = await loader.current();
48
+ if (activeResource.current === owner && requestVersion.current === version) {
49
+ setState((current) => ({ ...current, key, data, loading: false, error: null }));
50
+ }
51
+ } catch (reason) {
52
+ if (activeResource.current === owner && requestVersion.current === version) {
53
+ setState((current) => ({
54
+ ...current,
55
+ key,
56
+ loading: false,
57
+ error: reason instanceof Error ? reason.message : "读取失败",
58
+ }));
59
+ }
60
+ }
61
+ }, [key]);
62
+
63
+ useEffect(() => {
64
+ void refresh();
65
+ }, [refresh]);
66
+
67
+ const setError = (error: string | null) => {
68
+ if (activeResource.current?.key !== key) return;
69
+ setState((current) => (current.key === key ? { ...current, error } : current));
70
+ };
71
+ const run = async (work: () => Promise<unknown>) => {
72
+ const owner = activeResource.current;
73
+ if (owner?.key !== key) return;
74
+ setState((current) => ({
75
+ key,
76
+ data: current.key === key ? current.data : null,
77
+ loading: current.key === key ? current.loading : true,
78
+ busy: true,
79
+ error: null,
80
+ }));
81
+ try {
82
+ const result = await work();
83
+ if (activeResource.current === owner) await refresh();
84
+ return result;
85
+ } catch (reason) {
86
+ if (activeResource.current === owner)
87
+ setError(reason instanceof Error ? reason.message : "操作失败");
88
+ throw reason;
89
+ } finally {
90
+ if (activeResource.current === owner)
91
+ setState((current) => (current.key === key ? { ...current, busy: false } : current));
92
+ }
93
+ };
94
+ // 新请求尚未开始时,先隐藏上一个 key 的结果。
95
+ const visible =
96
+ state.key === key ? state : { data: null, loading: true, busy: false, error: null };
97
+ return { ...visible, setError, refresh, run };
98
+ }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sayacode"
7
- version = "3.2.1"
7
+ version = "3.2.2"
8
8
  description = "A local LangChain and LangGraph coding assistant with a Web UI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -250,14 +250,6 @@ class SayacodeApp:
250
250
  if kind not in {"tool.started", "tool.completed", "tool.failed"}:
251
251
  return
252
252
  call_id = str(event.get("tool_call_id") or "")
253
- tool_input = event.get("tool_input")
254
- target: str | None = None
255
- if isinstance(tool_input, dict):
256
- for key in ("path", "file_path", "target_path", "directory", "pattern"):
257
- value = tool_input.get(key)
258
- if isinstance(value, str) and value:
259
- target = value[:300]
260
- break
261
253
  await self.audit.append(
262
254
  kind,
263
255
  thread_id=thread_id,
@@ -267,7 +259,6 @@ class SayacodeApp:
267
259
  "tool_call_id": call_id,
268
260
  "tool_name": event.get("tool_name"),
269
261
  "duration_ms": event.get("duration_ms"),
270
- "target": target,
271
262
  "output_characters": len(str(event.get("tool_output") or ""))
272
263
  if kind == "tool.completed"
273
264
  else None,
@@ -674,7 +665,9 @@ class SayacodeApp:
674
665
  )
675
666
  if result.interrupts:
676
667
  await self.audit.append(
677
- "run.paused", thread_id=thread_id, details={"interrupts": result.interrupts}
668
+ "run.paused",
669
+ thread_id=thread_id,
670
+ details={"interrupt_count": len(result.interrupts)},
678
671
  )
679
672
  return {
680
673
  "ok": False,
@@ -701,7 +694,11 @@ class SayacodeApp:
701
694
  except (KeyError, ValueError):
702
695
  profile = None
703
696
  error = _model_error_message(exc, profile)
704
- await self.audit.append("run.failed", thread_id=thread_id, details={"error": error})
697
+ await self.audit.append(
698
+ "run.failed",
699
+ thread_id=thread_id,
700
+ details={"error_type": type(exc).__name__},
701
+ )
705
702
  return {"ok": False, "status": "failed", "thread_id": thread_id, "error": error}
706
703
 
707
704
  async def stream(
@@ -778,7 +775,9 @@ class SayacodeApp:
778
775
  if interrupted:
779
776
  await self.runtime.set_thread_status(thread_id, "interrupted")
780
777
  await self.audit.append(
781
- "run.paused", thread_id=thread_id, details={"interrupts": interrupts}
778
+ "run.paused",
779
+ thread_id=thread_id,
780
+ details={"interrupt_count": len(interrupts)},
782
781
  )
783
782
  yield {
784
783
  "type": "approval.requested",
@@ -813,7 +812,11 @@ class SayacodeApp:
813
812
  except (KeyError, ValueError):
814
813
  profile = None
815
814
  error = _model_error_message(exc, profile)
816
- await self.audit.append("run.failed", thread_id=thread_id, details={"error": error})
815
+ await self.audit.append(
816
+ "run.failed",
817
+ thread_id=thread_id,
818
+ details={"error_type": type(exc).__name__},
819
+ )
817
820
  yield {"type": "run.failed", "thread_id": thread_id, "error": error, "ok": False}
818
821
 
819
822
  def _normalize_event(self, event: dict[str, Any], thread_id: str) -> list[dict[str, Any]]:
@@ -9,6 +9,7 @@ from collections import deque
9
9
  from collections.abc import Callable
10
10
  from datetime import UTC, datetime
11
11
  from pathlib import Path
12
+ from threading import Lock
12
13
  from time import perf_counter
13
14
  from typing import Any
14
15
  from uuid import uuid4
@@ -16,6 +17,7 @@ from uuid import uuid4
16
17
  from langchain_core.callbacks import BaseCallbackHandler
17
18
 
18
19
  _SECRET = re.compile(r"(?:api[_-]?key|secret|password|credential|authorization|token)$", re.I)
20
+ _WRITE_LOCK = Lock()
19
21
 
20
22
 
21
23
  def _redact(value: Any, key: str = "") -> Any:
@@ -50,15 +52,16 @@ class AuditLog:
50
52
  """异步追加一条审计事件。传入事件名和会话任务运行编号加详情,返回写下的那行。写入走后台线程,不堵事件循环。"""
51
53
  row = self._row(event, thread_id=thread_id, task_id=task_id, details=details, run_id=run_id)
52
54
 
53
- def write() -> None:
54
- """追加一条审计记录并落盘。"""
55
+ await asyncio.to_thread(self._append_row, row)
56
+ return row
57
+
58
+ def _append_row(self, row: dict[str, Any]) -> None:
59
+ """同一进程内串行追加,避免并发回调覆盖同一文件的记录。"""
60
+ with _WRITE_LOCK:
55
61
  self.path.parent.mkdir(parents=True, exist_ok=True)
56
62
  with self.path.open("a", encoding="utf-8") as handle:
57
63
  handle.write(json.dumps(row, ensure_ascii=False, default=str) + "\n")
58
64
 
59
- await asyncio.to_thread(write)
60
- return row
61
-
62
65
  def append_sync(
63
66
  self,
64
67
  event: str,
@@ -70,9 +73,7 @@ class AuditLog:
70
73
  ) -> dict[str, Any]:
71
74
  """从同步回调接口写入一条回调事件。传入事件名和会话任务运行编号加详情,返回写下的那行。回调里不能等事件循环,只能用同步写。"""
72
75
  row = self._row(event, thread_id=thread_id, task_id=task_id, details=details, run_id=run_id)
73
- self.path.parent.mkdir(parents=True, exist_ok=True)
74
- with self.path.open("a", encoding="utf-8") as handle:
75
- handle.write(json.dumps(row, ensure_ascii=False, default=str) + "\n")
76
+ self._append_row(row)
76
77
  return row
77
78
 
78
79
  @staticmethod
@@ -20,7 +20,7 @@ def _package_version() -> str:
20
20
  try:
21
21
  return version("sayacode")
22
22
  except PackageNotFoundError:
23
- return "3.2.1"
23
+ return "3.2.2"
24
24
 
25
25
 
26
26
  def _state_home() -> Path:
@@ -256,7 +256,16 @@ async def on_task_update(app: Any, record: Any) -> None:
256
256
  "task.status",
257
257
  thread_id=record.thread_id,
258
258
  task_id=record.task_id,
259
- details=record.to_dict(),
259
+ details={
260
+ "status": record.status,
261
+ "role": record.role,
262
+ "outcome": record.last_outcome,
263
+ "turn_seq": record.turn_seq,
264
+ "delivery_state": record.delivery_state,
265
+ "parent_thread_id": record.parent_thread_id,
266
+ "worktree_enabled": record.worktree_enabled,
267
+ "unconfirmed_effects": record.unconfirmed_effects,
268
+ },
260
269
  )
261
270
  if (
262
271
  record.parent_thread_id is None
@@ -68,10 +68,10 @@ class TaskRecord:
68
68
  turn_seq: int = 0
69
69
 
70
70
  def to_dict(self) -> dict[str, Any]:
71
- """做什么,返回展示和审计可用的任务元数据视图。
71
+ """做什么,返回界面和任务工具可用的任务视图。
72
72
 
73
73
  参数与返回,无入参,返回全字段字典,密钥会被遮蔽。
74
- 调用约束,只给界面和审计用,不要拿它再存回去。"""
74
+ 调用约束,含提示词和结果正文,不能写入审计,也不要拿它再存回去。"""
75
75
  data = asdict(self)
76
76
  snapshot = data.get("profile_snapshot")
77
77
  if isinstance(snapshot, dict) and snapshot.get("api_key"):