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.
- {sayacode-3.2.1 → sayacode-3.2.2}/CHANGELOG.md +7 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/PKG-INFO +3 -3
- {sayacode-3.2.1 → sayacode-3.2.2}/README.md +2 -2
- sayacode-3.2.2/frontend/src/products/useProduct.ts +98 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/pyproject.toml +1 -1
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/application.py +16 -13
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/audit.py +9 -8
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/preferences.py +1 -1
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/inbox.py +10 -1
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/records.py +2 -2
- 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
- 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
- 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
- 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
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/index.html +1 -1
- {sayacode-3.2.1 → sayacode-3.2.2}/uv.lock +1 -1
- sayacode-3.2.1/frontend/src/products/useProduct.ts +0 -40
- {sayacode-3.2.1 → sayacode-3.2.2}/.gitignore +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/LICENSE +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/assets/image1.png +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/assets/image2.png +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/assets/image3.png +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/assets/image4.png +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/docs/architecture.md +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/docs/design/memory-system.md +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/docs/research/coding-agent-memory.md +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/docs/webui.md +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/.prettierrc.json +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/README.md +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/index.html +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/package-lock.json +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/package.json +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/App.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/App.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/client.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/client.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/events.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/events.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/preloadRecovery.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/preloadRecovery.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/session.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/api/types.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/AgentGraph.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/AgentGraph.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ApprovalDialog.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ApprovalDialog.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ComposerActions.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ComposerActions.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Conversation.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.test.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/ConversationProcess.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/DeleteSessionDialog.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/DirectoryPicker.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.test.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Inspector.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/MarkdownContent.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/QueueDock.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/RecoveryScreen.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Sidebar.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Sidebar.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Trajectory.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/Trajectory.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/approvalChoices.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/components/approvalChoices.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/i18n.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/i18n.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/conversationTimeline.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/conversationTimeline.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/format.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/tasks.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/tasks.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/trust.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/lib/trust.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/main.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/ProductDialog.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/ProductDialog.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/SettingsPreferences.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/SettingsPreferences.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/diagnostics.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/mcp.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/memory.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/modelTest.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/modelTest.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/models.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/panel.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/session.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/session.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/products/skills.tsx +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/activity.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/activity.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/eventPolicy.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/eventPolicy.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/liveText.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/liveText.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/messages.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/messages.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/queuedInput.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/queuedInput.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/snapshotOrder.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/snapshotOrder.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/useWorkspace.resync.test.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/state/useWorkspace.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/styles/shared.module.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/src/styles/tokens.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/tsconfig.json +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/frontend/vite.config.ts +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/__main__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/context.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/events.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/graph.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/models.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/agent/runtime.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/middleware.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/approvals/policy.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/events.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/headless.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/main.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/cli/web.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/config.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/diagnostics.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/hooks.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/instructions.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/mcp.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/extensions/skills.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/application.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/attachments.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/events.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/products.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/run_actions.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/session_operations.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/views.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/host/workspaces.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/evidence.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/learning.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/middleware.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/privacy.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/processing.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/records.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/repository.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/retrieval.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/service.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/tools.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/memory/turns.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/paths.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/process.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/profiles.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/prompts.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/sessions.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/manager.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/middleware.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/tools.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tasks/worktree.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/analysis.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/catalog.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/files.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/git.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/shell.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/tools/web.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/__init__.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/app.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/attachments.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/contracts.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/products.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/responses.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/security.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/session_operations.py +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/AgentGraph-CEriGMSJ.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/MarkdownContent-DmcbXhCl.js +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/ProductDialog-Cq8hWItk.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/Trajectory-yaV4MOoU.css +0 -0
- {sayacode-3.2.1 → sayacode-3.2.2}/src/sayacode/web/static/assets/index-EwIvkuyc.css +0 -0
- {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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
+
}
|
|
@@ -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",
|
|
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(
|
|
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",
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
@@ -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=
|
|
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"):
|