fuli-context 0.8.2 → 0.9.0-beta.1
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.
- package/README.md +115 -12
- package/README.zh-CN.md +99 -12
- package/dist/web/assets/{AboutPage-fBq5VFoQ.js → AboutPage-u2Bv6cO-.js} +1 -1
- package/dist/web/assets/{AgentHand-CMZk5GjY.js → AgentHand-CsMcNXUm.js} +1 -1
- package/dist/web/assets/{AgentName-DRZHAIB2.js → AgentName-BboCA-Fd.js} +1 -1
- package/dist/web/assets/{AgentProfilePage-BPQEfMhB.js → AgentProfilePage-Dyux6qE3.js} +1 -1
- package/dist/web/assets/AgentsDirectoryPage-BEJdvmX7.js +1 -0
- package/dist/web/assets/{ConnectionsPage-COb56qnk.js → ConnectionsPage-Dw_hK_EM.js} +1 -1
- package/dist/web/assets/{EmployeeRecruitDialog-BLTpjCcK.js → EmployeeRecruitDialog-DP_ktv3w.js} +1 -1
- package/dist/web/assets/{EmployeeWorkbenchPage-HUd-gne2.js → EmployeeWorkbenchPage-CAt8lVnr.js} +1 -1
- package/dist/web/assets/{GrowthLoading-Bu_kH-I8.js → GrowthLoading-DIwqGFSc.js} +1 -1
- package/dist/web/assets/{KnowledgeEditDialog-BTwXXRCD.js → KnowledgeEditDialog-Degd9AGD.js} +1 -1
- package/dist/web/assets/{KnowledgeInspector-Ct5hl2--.js → KnowledgeInspector-yms1ysZ4.js} +1 -1
- package/dist/web/assets/{KnowledgeOrganizerPage-DMC8Hh43.js → KnowledgeOrganizerPage-C64wNn8a.js} +1 -1
- package/dist/web/assets/KnowledgePage-BTFEXzgi.js +1 -0
- package/dist/web/assets/{KnowledgeWorkspace-DmHIc0jb.js → KnowledgeWorkspace-BsF1X4Aq.js} +1 -1
- package/dist/web/assets/{NotFoundPage-C0yOR9T9.js → NotFoundPage-DfaNTiD1.js} +1 -1
- package/dist/web/assets/{OverviewPage-BreX_kb4.js → OverviewPage-VXMfcWFI.js} +1 -1
- package/dist/web/assets/{PersonalProfilePage-CX1a6_E3.js → PersonalProfilePage-C1LBhcYu.js} +1 -1
- package/dist/web/assets/PersonalProjectsPage-B5lV4ZJW.js +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-6afJ8HUe.css +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-DF-IvEqp.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-CgL92gng.js +13 -0
- package/dist/web/assets/ProjectAgentsPage-CoJ4qQlA.css +1 -0
- package/dist/web/assets/{ProjectScopePicker-DVHfXX1f.js → ProjectScopePicker-CZUMdyYz.js} +1 -1
- package/dist/web/assets/{PublicProjectsPage-CtctKml1.js → PublicProjectsPage-e4CpxHk9.js} +1 -1
- package/dist/web/assets/{ReviewPage-DUqaK-do.js → ReviewPage-DugEaFvI.js} +1 -1
- package/dist/web/assets/RoundtablesPage-642YtZBC.js +2 -0
- package/dist/web/assets/RoundtablesPage-DCbIBnBq.css +1 -0
- package/dist/web/assets/{SearchableSelect-BZtyYmZN.js → SearchableSelect-Do6X3fXi.js} +1 -1
- package/dist/web/assets/{SettingsPage-CB4ymLr8.js → SettingsPage-wqYxDPyu.js} +1 -1
- package/dist/web/assets/{SharedJefaReportPage-CbFtrDCY.js → SharedJefaReportPage-CNQDezJr.js} +1 -1
- package/dist/web/assets/{WritingTastePage-B7lSzs2z.js → WritingTastePage-CAUvbDlL.js} +1 -1
- package/dist/web/assets/{agent-profile.css_vue_type_style_index_0_src_true_lang-C22UdfU0.js → agent-profile.css_vue_type_style_index_0_src_true_lang-DHOcWSe_.js} +1 -1
- package/dist/web/assets/{catalog-DU4Kfd-N.js → catalog-JmKyJnKL.js} +1 -1
- package/dist/web/assets/{client-CEHkJJ-5.js → client-DTQ_tUr2.js} +1 -1
- package/dist/web/assets/{console--CZxc3PW.js → console-CrhIK67Y.js} +1 -1
- package/dist/web/assets/{conversation-launcher-settings-Co1RlTFH.js → conversation-launcher-settings-fUWCt3XG.js} +1 -1
- package/dist/web/assets/copy-CX6pyzAG.js +1 -0
- package/dist/web/assets/i18n-CIIxzhPZ.js +10 -0
- package/dist/web/assets/{identity-CsowvtIm.js → identity-CdFITIOu.js} +1 -1
- package/dist/web/assets/{index-CWMqDq8g.css → index-CYZi7E1e.css} +1 -1
- package/dist/web/assets/index-DMoxVzDU.js +2 -0
- package/dist/web/assets/{model-CGpllrds.js → model-CJsrDUNb.js} +1 -1
- package/dist/web/assets/{personal-profile-graph-BNTG4PSY.js → personal-profile-graph-CQLOait6.js} +1 -1
- package/dist/web/assets/{shared-report-BFUJqqQe.js → shared-report-Cll2Yj8W.js} +1 -1
- package/dist/web/assets/{task-evidence-BUu7yYIn.js → task-evidence-DTiPCs7r.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-raNlHRDl.js → useApi-CROJJdhE-fcdVGInY.js} +1 -1
- package/dist/web/assets/{useModalDialog-CroGk2Cs.js → useModalDialog-B-45idX9.js} +1 -1
- package/dist/web/assets/{vue-router-JFYBLXB1.js → vue-router-BUX6njIv.js} +1 -1
- package/dist/web/index.html +12 -11
- package/docs/agent-conversations-and-collaboration.md +18 -1
- package/docs/employee-agents.md +20 -1
- package/docs/roundtable-beta-testing.md +102 -0
- package/docs/roundtable-beta-validation.md +125 -0
- package/docs/roundtable-research.md +142 -0
- package/graph-provider/fuli_graph/app.py +5 -0
- package/graph-provider/fuli_graph/models.py +1 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +1 -0
- package/graph-provider/fuli_graph/store_project_agent_context.py +22 -1
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +23 -8
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +86 -11
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +1 -0
- package/graph-provider/fuli_graph/store_records.py +1 -0
- package/graph-provider/fuli_graph/store_task_contexts.py +2 -1
- package/graph-provider/fuli_graph/task_context_adoption.py +9 -4
- package/graph-provider/fuli_graph/task_context_temporary_owner.py +25 -0
- package/graph-provider/fuli_graph/temporary_project_scope.py +88 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +131 -79
- package/package.json +8 -4
- package/skills/capturing-session-knowledge/SKILL.md +20 -5
- package/skills/operating-fuli/SKILL.md +8 -0
- package/src/agent-tools/graph-definitions.js +1 -1
- package/src/agent-tools/interface-catalog.js +5 -0
- package/src/agent-tools/project-agent-definitions.js +2 -2
- package/src/agent-tools.js +9 -2
- package/src/agents/a2a/roundtable-participant.js +374 -0
- package/src/agents/claude-code/roundtable-participant.js +24 -0
- package/src/agents/codex/roundtable-config.js +29 -0
- package/src/agents/codex/roundtable-participant.js +34 -0
- package/src/agents/grok/roundtable-participant.js +185 -0
- package/src/agents/identity-receipt.js +29 -0
- package/src/agents/pi/roundtable-participant.js +193 -0
- package/src/agents/pi/roundtable-result-format.js +183 -0
- package/src/cli/command-registry.js +2 -0
- package/src/cli/roundtable-command.js +59 -0
- package/src/cli.js +5 -0
- package/src/conversations/task-context-view.js +3 -1
- package/src/employees/service.js +9 -2
- package/src/employees/task-entry.js +5 -1
- package/src/graphiti/agent-conversations.js +15 -1
- package/src/graphiti/agent-knowledge-workflows.js +14 -5
- package/src/graphiti/federated-application.js +9 -41
- package/src/graphiti/project-agent-mapping.js +1 -0
- package/src/graphiti/project-agent-mention.js +35 -0
- package/src/graphiti/project-agent-task-entry.js +22 -3
- package/src/graphiti/project-agent-workflows.js +31 -12
- package/src/graphiti/project-path-context.js +1 -0
- package/src/graphiti/provider-client.js +3 -0
- package/src/graphiti/task-entry-preferences.js +50 -0
- package/src/graphiti/temporary-project-scope.js +46 -0
- package/src/http/api-router.js +2 -0
- package/src/http/error-mapping.js +2 -0
- package/src/mcp/provider-task-context-registry.js +10 -0
- package/src/mcp/session-id.js +1 -0
- package/src/mcp/tool-annotations.js +2 -0
- package/src/roundtables/application.js +69 -0
- package/src/roundtables/domain.js +181 -0
- package/src/roundtables/http-router.js +56 -0
- package/src/roundtables/mcp-server.js +48 -0
- package/src/roundtables/participant-registry.js +12 -0
- package/src/roundtables/process.js +70 -0
- package/src/roundtables/result-contract.js +47 -0
- package/src/roundtables/server.js +52 -0
- package/src/roundtables/service.js +396 -0
- package/src/roundtables/store.js +59 -0
- package/src/roundtables/tool-contract.js +41 -0
- package/src/roundtables/turn-prompt.js +21 -0
- package/src/roundtables/worker.js +85 -0
- package/dist/web/assets/AgentsDirectoryPage-CKZfrcuD.js +0 -1
- package/dist/web/assets/KnowledgePage-uXW1Eovz.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-Br1S8ETM.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-CA04h1Uf.js +0 -13
- package/dist/web/assets/ProjectAgentsPage-CZWWV4Kw.css +0 -1
- package/dist/web/assets/i18n-BtOOoyJd.js +0 -9
- package/dist/web/assets/index-7X3LGByg.js +0 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Agent 圆桌 Beta 验证记录
|
|
2
|
+
|
|
3
|
+
核验日期:2026-10-09。本页记录已有证据及其范围;发布与 registry 安装包另行核验。
|
|
4
|
+
圆桌已提供讨论、分工、实施、审查和人工验收的流程;Codex 的完整真实运行停在等待用户验收,未代用户接受交付。Pi 混合圆桌的失败记录另列,未计为完成。
|
|
5
|
+
|
|
6
|
+
## CI 与发布前检查
|
|
7
|
+
|
|
8
|
+
最终功能源码为 [`e87637f`](https://github.com/JevonsCode/fuli/commit/e87637f5c23db29c70a4d5d55594a6ff5db577a2),
|
|
9
|
+
[对应 CI](https://github.com/JevonsCode/fuli/actions/runs/37918728225) 已成功结束,包含共享阶段提示和 Pi 结果边界修复。
|
|
10
|
+
下列数字属于该提交。随后仅更新验证文档;后续功能修改需要重新验证,发布工作流也再次执行检查。
|
|
11
|
+
|
|
12
|
+
| 检查 | 结果 | 证据范围 |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Node 完整套件 | 1041 项:1033 通过,0 失败,8 跳过 | Linux CI;跳过项没有计为通过 |
|
|
15
|
+
| Vue 套件 | 371 通过 | 组件、状态和交互检查 |
|
|
16
|
+
| Vue typecheck / 构建 | 通过 | 类型及生产资源构建 |
|
|
17
|
+
| Package smoke | 通过 | `npm run test:package` 检查打包产物 |
|
|
18
|
+
| 依赖 audit | 0 漏洞 | CI audit 和本地最终 audit 记录 |
|
|
19
|
+
| Provider 检查 | 通过 | Python Provider 测试及 Compose 配置检查 |
|
|
20
|
+
| 独立代码审查 | 多轮修复后无 P1 / P2 | core、协议、worker、Codex 隔离和 parser;focused 检查 72/72,后续检查 8/8 |
|
|
21
|
+
|
|
22
|
+
Windows 本机完整套件仍有 29 项失败,失败集合与所比较基线逐项匹配。
|
|
23
|
+
Linux CI 已通过;Windows 完整套件尚未全部通过,环境差异继续保留为验证范围。
|
|
24
|
+
|
|
25
|
+
Pi 的新增 post-format 路径与共享阶段提示修复已完成独立复审:圆桌回归 87/87、Pi 专项检查 20/20 通过,未发现尚未解决的 P1/P2。真实 Pi grep 工具验证了配置继承导致的越界及其修复;失败状态、产物引用、分歧、取消和上下文预算分别核对。模型正文可靠性仍为试验范围。当前 CI、审查和真实 CLI 证据不自动覆盖后续功能修改,
|
|
26
|
+
GitHub prerelease、npm beta dist-tag、registry / tarball 和官网部署也需分别确认。
|
|
27
|
+
|
|
28
|
+
## 真实本机 Codex 圆桌
|
|
29
|
+
|
|
30
|
+
本地回执 `output-roundtable-cli-live-verified.json` 的核验时间为 `2026-10-09T09:20:13.155Z`,
|
|
31
|
+
证据级别为 `real_cli_local_network`。三个独立席位通过本机网络完成以下 7 个执行回合:
|
|
32
|
+
|
|
33
|
+
| 顺序 | 阶段 | 席位 | 回合状态 |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| 1 | discussion | 主持人 | completed |
|
|
36
|
+
| 2 | discussion | 实施者 | completed |
|
|
37
|
+
| 3 | discussion | 审查者 | completed |
|
|
38
|
+
| 4 | planning | 主持人 | completed |
|
|
39
|
+
| 5 | implementation | 实施者 | completed |
|
|
40
|
+
| 6 | review | 独立审查者 | completed |
|
|
41
|
+
| 7 | synthesis | 主持人 | completed |
|
|
42
|
+
|
|
43
|
+
实施者创建 `roundtable-evidence.txt`,独立审查者实际读取并逐字节比较,报告 `passed: true`。
|
|
44
|
+
验收程序也确认文件存在、内容完全匹配:
|
|
45
|
+
|
|
46
|
+
| 产物检查 | 观察值 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| 文件大小 | 52 字节 |
|
|
49
|
+
| SHA-256 | `3de9091873b8023d1fbd83bccd8713cf9a44cfa051f6136f9b8dcd2413b39a1e` |
|
|
50
|
+
| 回合完成 | 7/7 |
|
|
51
|
+
| 独立真实会话 | 7 |
|
|
52
|
+
| 报告用量的回合 | 7 |
|
|
53
|
+
| 实际模型 | 未知;回执中的 `model` 均为空,未用配置值补齐 |
|
|
54
|
+
| 物理电脑 | `physicalComputers=1` |
|
|
55
|
+
| 最终阶段 / 状态 | `synthesis` / `waiting_input` |
|
|
56
|
+
| 等待原因 | `owner_completion_required` |
|
|
57
|
+
| 人工验收 | `not_performed` |
|
|
58
|
+
|
|
59
|
+
回执将应用标为参与端报告的 Codex,保留 `provenance=participant_reported` 和
|
|
60
|
+
`identityVerified=false`。会话、用量及文件结果有真实运行证据,服务端没有独立认证原生进程身份。
|
|
61
|
+
已注册席位、预检和讨论计划都不单独算执行;这里的实施与审查依据实际回合、工具行为和产物检查。
|
|
62
|
+
|
|
63
|
+
这次验证覆盖一台电脑上的独立席位与会话,尚未证明多台物理电脑或不同厂商客户端的完整协作。
|
|
64
|
+
独立 reviewer 的通过结论保留为验证报告,用户的人工验收仍待进行。
|
|
65
|
+
|
|
66
|
+
## Pi 与本地模型
|
|
67
|
+
|
|
68
|
+
Pi 1.1.0 配合本地 qwen3:8b 已完成 3 项基本真实工具检查。
|
|
69
|
+
`output-pi-basic-live.log` 的汇总为 `passed: true`,本地预检通过,实际调用和会话使用 Pi / Ollama;
|
|
70
|
+
运行回执中的本地模型别名为 `fuli-roundtable-qwen3:latest`。
|
|
71
|
+
|
|
72
|
+
| 基本检查 | 结果 |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| 读取已存在文件 | 通过;实际调用 read 并返回文件内容 |
|
|
75
|
+
| 写入指定字面内容 | 通过;实际调用 write,产物检查通过 |
|
|
76
|
+
| 拒绝工作区外路径 | 通过;越界 write 的工具结果为错误 |
|
|
77
|
+
|
|
78
|
+
上述 3/3 只证明基本读写与路径边界。新增的严格结果整理、执行预算和阶段提示等 Pi 专项检查 20/20 通过,并完成独立复审,
|
|
79
|
+
但真实模型报告仍不稳定:Qwen3 的最新检查为 3/4,已有 Qwen2.5:14b 的一次对照为 2/4。
|
|
80
|
+
Qwen3 曾有两次 4/4;最后一次读取工具实际返回随机标记,主模型及整理模型的正文却复述错误示例,
|
|
81
|
+
该检查如实记为失败,`verification.passed` 保持 false。正确写入及回读、只读和越界拒写有真实工具证据。
|
|
82
|
+
Pi 的正文与产物必须由独立审查者检查,不能把一次成功或合法 JSON 当可靠交付。
|
|
83
|
+
|
|
84
|
+
混合圆桌的首次运行因 Codex 云端 `workspace routing discovery failed` 停在首轮;Pi 仅预检和加入。
|
|
85
|
+
短时 Codex ACK 恢复通过后,第二次运行完成 Codex 主持首轮;Pi 在模型调用前因完整请求超出 16K
|
|
86
|
+
上下文预算中止,实际用量为 0。两次均没有实施文件、独立审查或汇总,也没有人工验收。
|
|
87
|
+
随后新建共享原权重的 `fuli-roundtable-qwen3-32k:latest`,原模型上限实测为 40960,适配器预检及
|
|
88
|
+
Ollama 运行状态均确认 32768;一次真实只读探测的工具内容和正文均匹配随机标记,无写入。
|
|
89
|
+
所有这些实测都在一台物理电脑上进行。Pi + Ollama 仍为 **experimental(试验接入)**,基本工具或合法输出不能代替完整混合圆桌验收。
|
|
90
|
+
|
|
91
|
+
### 32K 混合圆桌:真实输出,尚未完成交付
|
|
92
|
+
|
|
93
|
+
第三次运行记录为 `output-roundtable-mixed-32k.json`,核验时间 `2026-10-09T10:20:51.281Z`。Codex 主持人的讨论回合 `completed`,Pi 使用 `fuli-roundtable-qwen3-32k:latest` 真实生成输出,并报告用量;但 Pi 错把未来 implementation / review 阶段的 `pending` 任务当作当前 discussion 的阻塞,提交 `blocked`。服务最终为 `discussion` / `waiting_input`,原因 `participant_blocked`。`artifactMatches`、`reviewPassed`、`reachedSynthesis` 均为 false,没有完成实施产物、独立审查或汇总,人工验收未进行。
|
|
94
|
+
|
|
95
|
+
共享阶段提示修复后的运行记录为 `output-roundtable-mixed-phase-fixed.json`,核验时间 `2026-10-09T10:33:57.477Z`。Codex 讨论回合再次 `completed`;Pi 32K CLI 退出码为 0,出现 `agent_settled`,真实生成 input 2391 / output 108 tokens,但原生最终正文验收为 `response_invalid`,尚未进入 formatter。JSONL 会话、模型和用量已解析;日志没有保存最终正文,无法进一步区分 stopReason、空正文或大小限制的具体触发条件。服务停在 `discussion` / `waiting_input`,原因 `participant_failed`;没有实际产物校验值、审查或汇总完成证据,人工验收仍未进行。`physicalComputers=1`;取消请求、worker 收尾、协调端关闭及所拥有工作区清理均有完成记录。
|
|
96
|
+
|
|
97
|
+
这两次记录证明模型实际运行过,也证明流程仍未通过;CLI 成功退出不等于结果契约有效或协作完成。共享阶段提示已有修复和专项测试证据,Pi 的有效结果与完整混合流程仍需新的真实验证。
|
|
98
|
+
|
|
99
|
+
## 其他接入与界面边界
|
|
100
|
+
|
|
101
|
+
| 范围 | 当前证据 | 仍需验证 |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| Grok API | 按官方 API 契约完成协议 fixture 检查 | 缺少有效凭据的真实模型调用、真实 model / usage 与完整圆桌 |
|
|
104
|
+
| Grok Bot / Team Bot | 官方支持自定义 Remote HTTPS MCP;已提供有限圆桌 MCP 接入步骤 | Bot 的真实登录、配置、主动加入和领取 / 提交回合;尚未 live 验收 |
|
|
105
|
+
| A2A | 官方 SDK 的协议测试 | 独立外部真实服务的发现、任务、产物及取消确认 |
|
|
106
|
+
| Claude Code | 适配器与协议 / 故障检查 | 有效凭据下的完整真实运行及跨平台协作 |
|
|
107
|
+
| 多台物理电脑 | 本机独立网络客户端、HTTP / MCP、SQLite 恢复和授权检查 | 用户在多台电脑上验证可达性、客户端运行、撤销和恢复 |
|
|
108
|
+
| 圆桌页面 | Vue、组件、HTTP 路由、构建检查,以及 IAB 实际页面和 400px 小屏验收(范围见下文) | 其他页面与最终 registry 安装包界面;本次页面验收没有启动模型或取得执行回执 |
|
|
109
|
+
| 官网 | 构建与内容检查 | 实际视觉和小屏验收 |
|
|
110
|
+
|
|
111
|
+
### 实际页面验收
|
|
112
|
+
|
|
113
|
+
IAB 已实际打开圆桌页面,完成中英文切换,并通过界面创建无项目草案。中文显示“讨论·独立临时任务”,Codex 与 Pi 分别显示为独立席位。Pi 邀请界面可见模型输入,凭据保持隐藏;执行停止操作后,页面显示“已停止”。
|
|
114
|
+
|
|
115
|
+
在 400 × 850 视口中,实测 `documentWidth = bodyWidth = 400`,没有横向溢出;截图核对了席位的纵向布局。这次验收覆盖上述圆桌页面操作,尚未覆盖官网、其他页面和最终 registry 安装包界面。
|
|
116
|
+
|
|
117
|
+
页面验收没有启动模型,也没有执行回执,不能作为模型运行或任务完成的证据。验收使用的静态构建早于 Pi 模型 placeholder 更新为 `fuli-roundtable-qwen3-32k:latest`;该源代码更新另有 focused 组件测试 10 / 10 通过的证据,最终源码 CI 已构建该更新,registry 安装包界面仍需复核。
|
|
118
|
+
|
|
119
|
+
Grok API 席位与现有 Grok Bot 分别验收。官方 MCP 配置能力证明有接入方式,
|
|
120
|
+
不证明 Bot 已连接,也不提供由外部 API 唤醒现有 Bot 聊天的证据。
|
|
121
|
+
A2A 协议测试不代表完整规范兼容或外部服务已经接通。
|
|
122
|
+
|
|
123
|
+
后续实际步骤见[跨电脑 Beta 测试](roundtable-beta-testing.md)。测试时分别记录物理电脑、
|
|
124
|
+
运行端、实际会话、模型与用量、产物及审查结果;缺少的数据保持未知。
|
|
125
|
+
本页只公布必要摘要、相对产物名与校验值,不公开凭据、本机绝对路径或原始运行日志。
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Agent 圆桌接入与开源研究
|
|
2
|
+
|
|
3
|
+
核实日期:2026-10-09。本文是官方文档、仓库与公开 npm 元数据的研究记录,
|
|
4
|
+
不是接入验收报告;没有启动模型、读取密钥或更改运行配置。
|
|
5
|
+
|
|
6
|
+
## 建议
|
|
7
|
+
|
|
8
|
+
采用 Fuli 自有圆桌领域与协调任务,运行时通过统一 participant port 接入。
|
|
9
|
+
完整 beta 应同时提供真实本机 CLI、开放 MCP 参与、远端 A2A、API 模型席位,
|
|
10
|
+
并展示每个入口的实际就绪、执行、取消和证据状态。已有身份、授权、记忆、
|
|
11
|
+
任务验证、租约和时序历史继续由 Fuli 管理。
|
|
12
|
+
|
|
13
|
+
最适合直接增加的依赖是官方 `@a2a-js/sdk`。群聊、恢复和工具授权借鉴下表的
|
|
14
|
+
设计;不引入另一套框架来接管 Fuli 的记忆和任务真相。此项是结合本项目架构
|
|
15
|
+
作出的工程判断。
|
|
16
|
+
|
|
17
|
+
## Fuli / Grok Bot / Codex Dot:事实与取舍
|
|
18
|
+
|
|
19
|
+
“Codex Dot”是用户的称呼;本次官方资料使用的是 **your dot**,未核实独立的同名产品或专属公开 API。下面分别比较官方 dot 与 Codex 能力。本机只读核查确认 `codex-cli 0.162.0-alpha.2` 提供非交互执行、代码审查、MCP 配置及会话恢复入口;这些入口本身不证明任务已执行,真实 Codex 圆桌回合另见[Beta 验证记录](roundtable-beta-validation.md)。[Codex CLI 官方契约](https://learn.chatgpt.com/docs/cli/reference)
|
|
20
|
+
|
|
21
|
+
| 产品 / 已核实入口 | 强项与适用取舍 | 当前边界 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Fuli Agent 圆桌 | 用户使用自己的电脑、运行端与凭据,通过独立 CLI / API / MCP / A2A 席位围绕共同目标协作;房间内共享且可追溯的消息、分歧、产物和审查接续 Fuli 的身份与知识范围,最终验收由人决定。 | 已提供 Beta 接入与协调流程,完整真实圆桌证据目前来自一台电脑上的 Codex;多电脑和其他运行端的真实协作分别待验收。尚未提供托管常驻服务、默认第三方通讯平台一键入口,也没有 Grok 原生 Bot 的外部唤醒能力。见[接入步骤](roundtable-beta-testing.md)与[证据范围](roundtable-beta-validation.md)。 |
|
|
24
|
+
| 官方 Grok Bot | 常驻云同事适合持续跟进;官方支持 2–6 位 Bot 群聊、异步交接及内部唤醒,笔记本关闭后仍可继续工作。[协作](https://docs.x.ai/grok-bot/chat-and-collaboration)、[FAQ](https://docs.x.ai/grok-bot/faq) | 同一账户的 Bots 共用云电脑、文件和登录,不构成彼此的安全隔离。Custom Remote HTTPS MCP 提供主动接入 Fuli 的方式,但尚未 live 验收;本次核实的官方文档未建立 Fuli 从外部唤醒现有 Bot 的公开契约。[Team Bots](https://docs.x.ai/grok-bot/team-bots) |
|
|
25
|
+
| 官方 your dot 与 Codex | dot 可在对话之间持续跟进、并行委派,创建或续接本地 Codex 任务,也可使用已配置的 Codex 云环境;它有自己的云电脑,适合由一个助手持续协调工作。[任务与记忆](https://learn.chatgpt.com/docs/dots/tasks-and-memory)、[电脑与应用](https://learn.chatgpt.com/docs/dots/computers-and-apps) | 官方当前说明一次可连接一台个人电脑;本地任务要求该电脑在线且 ChatGPT 应用打开,云任务可在本机离线时运行。该能力不能直接推导为独立厂商 CLI / API / MCP / A2A 席位的通用圆桌契约,也不证明本项目已接通 dot。 |
|
|
26
|
+
|
|
27
|
+
因此,Fuli 的取舍是让不同运行端保留自己的执行入口、权限和证据,在用户可控的协作记录中完成交付;Grok Bot 和 dot 的常驻托管与内建协调更省运维。长期记忆、多 Agent 和调度并非 Fuli 独有;跨运行端接续、可检查的协作过程和人工权威,才是本项目需要持续验证的价值。
|
|
28
|
+
|
|
29
|
+
## 开源方案与取舍
|
|
30
|
+
|
|
31
|
+
| 方案 / 官方来源 | 当前能力与许可证 | Fuli 应采用什么 | 集成代价与边界 |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| [A2A JavaScript SDK](https://github.com/a2aproject/a2a-js) | Apache-2.0;A2A v1.0,JSON-RPC、REST、gRPC;v0.3 兼容需显式开启 | **直接集成客户端适配器**:Agent Card、任务、流、产物、取消;必要时以相同领域操作托管服务端 | Node >=20;JSON-RPC/REST 客户端不需要 Express、gRPC 或 SQL 数据库。协议不会替用户登录或操纵封闭客户端 |
|
|
34
|
+
| [Microsoft Agent Framework](https://github.com/microsoft/agent-framework)、[A2A 接入](https://github.com/microsoft/agent-framework/blob/main/python/packages/a2a/README.md)、[许可证](https://github.com/microsoft/agent-framework/blob/main/LICENSE) | MIT;Python/.NET;开放模型提供方、工作流、MCP 与远端 A2A 包装 | 借鉴远端 Agent 能力包装;把外部 MAF 服务作为 A2A 席位 | 整体引入会增加 Python/.NET 编排与生命周期;本项目 JS 应用层已有相关职责。核实具体 A2A 包版本,不能从框架总版本推断兼容性 |
|
|
35
|
+
| [LangGraphJS](https://github.com/langchain-ai/langgraphjs)、[interrupt](https://docs.langchain.com/oss/javascript/langgraph/interrupts) | MIT;有状态图、持久检查点、暂停和恢复;可不依赖 LangChain | 借鉴可验证阶段转换、等待用户和恢复设计;复杂新工作流可另行使用 | Fuli 现有任务和圆桌存储已具状态权威,引入会新增检查点同步责任。恢复节点会重跑 interrupt 前代码,副作用仍必须幂等 |
|
|
36
|
+
| [AutoGen](https://github.com/microsoft/autogen) | 代码 MIT;文档存在 CC-BY-4.0;Python AgentChat/Core,群聊、分布式运行时 | 借鉴轮流/点名发言和组合终止条件 | 官方仓库已标明维护模式,不新增功能,推荐新项目用 Agent Framework;因此不选作新产品底座 |
|
|
37
|
+
| [CrewAI](https://github.com/crewAIInc/crewAI)、[A2A](https://docs.crewai.com/en/learn/a2a-agent-delegation) | MIT;Python 角色任务、流程;`crewai[a2a]` 可代理远端 Agent,也可托管 A2A Agent | 借鉴单一任务负责人、预期产物和阶段交接;远端 CrewAI 服务通过 A2A 接入 | 应用内多模型协作与用户已有客户端互通是不同验收目标;整体引入增添 Python 运行时、记忆与工具策略 |
|
|
38
|
+
| [OpenClaw](https://github.com/openclaw/openclaw)、[CLI backends](https://docs.openclaw.ai/gateway/cli-backends)、[remote node](https://github.com/openclaw/openclaw/blob/main/docs/cli/node.md) | MIT,另有第三方 notices;Gateway、可更换模型/harness、消息渠道和远端节点 | **重点借鉴运行时边界**:CLI JSONL、私有 MCP 配置、当前尝试有效的接入 grant;外部 OpenClaw 可成为受限席位 | 自身是完整常驻 Gateway;不需要把它捆进 Fuli。远端节点执行命令需配对授权,不等同跨厂商既有聊天 API |
|
|
39
|
+
| [OpenBot](https://github.com/CopilotKit/OpenBot)、[配置](https://github.com/CopilotKit/OpenBot/blob/main/docs/configuration.md)、[许可证](https://github.com/CopilotKit/OpenBot/blob/main/LICENSE) | MIT 的早期模板;每位 coworker 独立电脑,AG-UI 远端 Agent、授权与记录 | 借鉴明确授权、电脑/文件边界和显示真实活动;AG-UI 可作为独立未来 UI 适配方向 | 私有 workspaces,没有可依赖发布包;需 Docker、Bun、Postgres 和 CopilotKit Intelligence 项目。Intelligence 依赖及 entitlement 不因模板 MIT 自动消失 |
|
|
40
|
+
| [Google ADK JS](https://github.com/google/adk-js) | Apache-2.0;Node >=20.19;顺序/并行/循环 Agent、MCP、A2A | 外部 ADK Agent 可直接走 A2A;借鉴清晰工具 schema | 与现有 Fuli 编排重复;不需要为接入一个远端 Agent 把整个 ADK 引入 |
|
|
41
|
+
| [ACP TypeScript SDK](https://github.com/agentclientprotocol/typescript-sdk) | Apache-2.0;编辑器与 coding Agent 间的会话、权限与更新协议;v1 稳定,v2 明确是草案 | 支持 ACP 的 coding Agent 可加独立 participant adapter | ACP 解决客户端控制 coding Agent 的问题;A2A 解决独立 Agent 的远端任务契约。应各自报告实际协商能力,不把实验 v2 当已稳定支持 |
|
|
42
|
+
|
|
43
|
+
许可证记录描述上述仓库本身;引入代码须保留相应 LICENSE/NOTICE,模型服务、
|
|
44
|
+
托管平台与可选组件仍按各自条款。当前决定仅直接引入官方 A2A SDK,其他条目
|
|
45
|
+
作为设计来源或外部端点,不复制框架代码。
|
|
46
|
+
|
|
47
|
+
## A2A 适配具体决定
|
|
48
|
+
|
|
49
|
+
公开 npm `latest` 元数据于本次读取为 `@a2a-js/sdk@1.3.0`;唯一必需 runtime
|
|
50
|
+
依赖为 `jose`,Express、gRPC、Kysely 与数据库驱动都是 optional peers。
|
|
51
|
+
见[官方 package.json](https://github.com/a2aproject/a2a-js/blob/main/package.json)。
|
|
52
|
+
实现应锁定并测试实际安装版本,避免使用旧 0.3 SDK 示例来构建 1.x 接口。
|
|
53
|
+
|
|
54
|
+
采用 `ClientFactory` 配合 `JsonRpcTransportFactory` / `RestTransportFactory`,
|
|
55
|
+
通过 Agent Card 选择支持的接口。`sendMessage` / `sendMessageStream` / `getTask`
|
|
56
|
+
与 `cancelTask` 支持每次请求的 signal 和 serviceParameters headers。
|
|
57
|
+
SDK 提供认证重试辅助器;Fuli 不自动采用其 401/403 重试行为,因为认证失败
|
|
58
|
+
需要明确转入 waiting_auth。上述契约见[官方 SDK 文档](https://github.com/a2aproject/a2a-js)。
|
|
59
|
+
|
|
60
|
+
建议映射:describe 保存真实 Agent Card 能力;preflight 做发现/认证和版本检查;
|
|
61
|
+
dispatch 保存远端真实 task/context ID;observe 转换 task/status/artifact 事件;
|
|
62
|
+
cancel 请求远端取消,只有收到对应终态才显示确认停止。AbortSignal 中止本地
|
|
63
|
+
网络等待不证明远端工作停止。room/seat/turn/attempt/fence 与远端 ID 的关联由
|
|
64
|
+
Fuli 持久化;远端 completed 只能完成该回合,不能越过 Fuli 协调任务验证。
|
|
65
|
+
|
|
66
|
+
不要默认启用 push webhook 或另外增加 SDK SQL task store;当前 Provider 已有
|
|
67
|
+
存储权威。若后续托管 Fuli A2A 服务端,应通过注入 store 与领域端口接入既有
|
|
68
|
+
存储,避免第二份任务完成状态。端点与鉴权只从明确配置读取,不自动发现私人
|
|
69
|
+
端点或从 Agent Card 声明扩大权限。
|
|
70
|
+
|
|
71
|
+
## Grok Bot、Grok API 与第三方 grokbot
|
|
72
|
+
|
|
73
|
+
**Grok Bot 是真实的官方产品。** 官方[概览](https://docs.x.ai/grok-bot/overview)
|
|
74
|
+
描述长期保留的 AI 同事与云电脑。[协作文档](https://docs.x.ai/grok-bot/chat-and-collaboration)
|
|
75
|
+
明确支持 2–6 位 Bot 群聊、点名、异步交接和接收 Bot 唤醒。[FAQ](https://docs.x.ai/grok-bot/faq)
|
|
76
|
+
同时说明:同一账户的 Bots 共用一台持久云电脑、文件、浏览器会话和登录,不能
|
|
77
|
+
把不同 Bot 当安全隔离边界;关闭笔记本后后台工作仍可继续。
|
|
78
|
+
|
|
79
|
+
官方[Team Bots 插件表](https://docs.x.ai/grok-bot/team-bots)明确列出 Custom MCP
|
|
80
|
+
server 的 Remote HTTPS 与 Command 两种方式;HTTPS 可使用 Bot 自身凭据或
|
|
81
|
+
按用户 OAuth,Command 在会话使用的云电脑运行。这证明 Grok Bot 可以配置
|
|
82
|
+
Fuli 的远程有限圆桌 MCP 工具,主动读取/接受/提交自己的席位回合。
|
|
83
|
+
|
|
84
|
+
本次查阅的 Bot 文档没有提供外部系统向现有 Bot 发消息或启动其后台回合的
|
|
85
|
+
公开编程契约;内部 Bot-to-Bot 唤醒不能推导为 Fuli 拥有相同外部控制能力。
|
|
86
|
+
因此 native Grok Bot 接入以实际 MCP 会话参与为证据,不把配置可用或 API
|
|
87
|
+
模型输出展示为“原生 Grok Bot 已自动参与”。这是一项当前文档边界,后续发现
|
|
88
|
+
正式 Bot API 时可增加独立适配器。
|
|
89
|
+
|
|
90
|
+
GitHub 的 [xlviitheroman/grokbot](https://github.com/xlviitheroman/grokbot)是
|
|
91
|
+
第三方 Discord Bot,README 使用 `DISCORD_TOKEN` 和 `GROK_API_KEY`,与官方
|
|
92
|
+
Grok Bot 产品不同;不选其作为跨 Agent 编排或官方 Bot 接入依据。
|
|
93
|
+
|
|
94
|
+
## 自动 Grok API 席位
|
|
95
|
+
|
|
96
|
+
xAI API 与用户已有 Grok Bot 是两个入口。自动 API 席位应显示提供方、实际
|
|
97
|
+
model、真实请求 ID、usage 与结果,来源明确写为 Grok API。
|
|
98
|
+
|
|
99
|
+
[Quickstart](https://docs.x.ai/developers/quickstart)与
|
|
100
|
+
[Remote MCP](https://docs.x.ai/developers/tools/remote-mcp)使用 `XAI_API_KEY`、
|
|
101
|
+
`https://api.x.ai/v1` 和 Bearer authentication。官方当前示例为 `grok-4.7`;
|
|
102
|
+
应允许配置并通过[模型目录](https://docs.x.ai/developers/models)预检,不能
|
|
103
|
+
把旧模型名称视为永久可用。新版适配优先 `/responses`;
|
|
104
|
+
[Chat Completions](https://docs.x.ai/developers/model-capabilities/legacy/chat-completions)
|
|
105
|
+
官方已标注 legacy,新功能先进入 Responses。
|
|
106
|
+
|
|
107
|
+
[Function Calling](https://docs.x.ai/developers/tools/function-calling)支持自定义
|
|
108
|
+
JSON Schema function tools、auto/required/none/指定工具选择,默认允许一次多
|
|
109
|
+
工具调用,可用 `parallel_tool_calls: false` 禁止。由 Fuli 管理的本地 tool loop
|
|
110
|
+
只执行 seat allowlist,校验参数与授权,回传每个 call ID 的结果,并硬限制调用
|
|
111
|
+
次数与时间。模型提出工具调用不是已执行证据。
|
|
112
|
+
|
|
113
|
+
[Remote MCP](https://docs.x.ai/developers/tools/remote-mcp)支持 xAI native SDK 和
|
|
114
|
+
OpenAI-compatible Responses;服务器连接由 xAI 执行,仅 Streaming HTTP/SSE,
|
|
115
|
+
支持 authorization/headers 和 allowed_tools。空或缺失 allowlist 会开放全部
|
|
116
|
+
服务器工具;`require_approval` 和 `connector_id` 当前不支持。Fuli 因此需要
|
|
117
|
+
自己执行授权和有限工具服务,不能把 API 不支持的 approval 字段当安全门槛。
|
|
118
|
+
只有从 xAI 可达的明确授权 HTTPS 端点才能用此路径;本机 loopback/stdio 可
|
|
119
|
+
通过 Fuli 自己的本地 tool loop 处理,不应宣称云端能访问本机。
|
|
120
|
+
|
|
121
|
+
凭据配置只存环境变量名或既有 secret reference;不在房间、日志、状态响应、
|
|
122
|
+
提示词或研究记录中保存值。HTTP 401/403 转为等待授权,400/422 参数错误和
|
|
123
|
+
404 模型/端点错误不盲目重试。429 与可恢复传输故障可在本次预算内至多一次
|
|
124
|
+
退避重试;执行副作用后状态不明时先 observe,不能重新 dispatch 制造重复。
|
|
125
|
+
错误语义见[xAI Debugging](https://docs.x.ai/developers/debugging);重试次数是
|
|
126
|
+
本项目设计策略,不是 xAI 默认承诺。
|
|
127
|
+
|
|
128
|
+
## Beta 验收证据
|
|
129
|
+
|
|
130
|
+
Pi 官方仓库已迁移到 [earendil-works/pi](https://github.com/earendil-works/pi),MIT 许可。当前 CLI npm 包为 `@earendil-works/pi-coding-agent`;本适配器需要 >=1.1.0 的项目批准与资源隔离选项。Pi 保留独立执行入口,Ollama 提供本地推理;接入不依赖另一个长期记忆系统。Ollama 的 [Chat API](https://docs.ollama.com/api/chat)支持工具与结构化输出;实际工具调用和最终格式可靠性仍需分开验收。
|
|
131
|
+
|
|
132
|
+
- 独立本机 CLI 要证明真实提案、审查、修订及任务产物;真实凭据不足记为阻塞。
|
|
133
|
+
- native MCP 席位要证明独立认证会话的跨席位消息和回合流;可复制配置不算已连接。
|
|
134
|
+
- A2A 要用独立服务证明 discovery、task 生命周期、流/轮询、input/auth 等待、
|
|
135
|
+
artifacts、取消确认与过期结果隔离;本地 fixture 明确标为协议测试。
|
|
136
|
+
- Grok API 真实调用需要报告真实 model/ID/usage;无 API 凭据仅验证 parser、
|
|
137
|
+
错误映射和协议,不能报告已真实模型验收。
|
|
138
|
+
- Grok Bot 插件配置及主动圆桌回合必须用其真实客户端证明;未经验证的外部
|
|
139
|
+
自动唤醒、封闭聊天移交、全天托管和跨组织隔离均不能成为发布承诺。
|
|
140
|
+
|
|
141
|
+
这些证据级别可同时存在。完整 beta 是包含接入、授权、状态、恢复、停止与
|
|
142
|
+
交付的产品;协议或模拟测试不能代替相应真实运行验证。
|
|
@@ -9,6 +9,7 @@ from .auth import (
|
|
|
9
9
|
matches_workflow_observation_token,
|
|
10
10
|
)
|
|
11
11
|
from .config import Settings, get_settings
|
|
12
|
+
from .temporary_project_scope import TemporaryProjectEnsure, ensure_temporary_project
|
|
12
13
|
from .graph_models import GraphResult
|
|
13
14
|
from .knowledge_usage_models import KnowledgeUsageCreate, KnowledgeUsageResult
|
|
14
15
|
from .knowledge_feedback_models import (
|
|
@@ -286,6 +287,10 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
|
|
286
287
|
) -> ProjectDeleteResult:
|
|
287
288
|
return await store.delete_project(actor, project_id)
|
|
288
289
|
|
|
290
|
+
@application.post('/v1/temporary-projects/ensure', response_model=PersonalProjectRecord)
|
|
291
|
+
async def ensure_task_project(request: TemporaryProjectEnsure, actor: Actor):
|
|
292
|
+
return await ensure_temporary_project(store, actor, request)
|
|
293
|
+
|
|
289
294
|
@application.put('/v1/personal-projects', response_model=PersonalProjectRecord)
|
|
290
295
|
async def upsert_personal_project(
|
|
291
296
|
request: PersonalProjectUpsert,
|
|
@@ -371,6 +371,7 @@ class PersonalProjectUpsert(StrictModel):
|
|
|
371
371
|
class PersonalProjectRecord(StrictModel):
|
|
372
372
|
project_id: str
|
|
373
373
|
personal_space_id: str
|
|
374
|
+
scope_type: Literal['registered', 'temporary'] = 'registered'
|
|
374
375
|
publication_key: str
|
|
375
376
|
profile: ProjectProfile
|
|
376
377
|
created_at: datetime
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from .personal_project_access import authorize_personal_project
|
|
4
4
|
from .project_agent_context_models import ProjectAgentContextResolution
|
|
5
5
|
from .project_agent_task_models import ProjectAgentTaskSubmit
|
|
6
|
+
from .task_context_temporary_owner import temporary_task_owner
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class StoreProjectAgentContext:
|
|
@@ -10,6 +11,9 @@ class StoreProjectAgentContext:
|
|
|
10
11
|
self._require_personal()
|
|
11
12
|
space = await self.authorize(actor, request.personal_space_id, 'reader')
|
|
12
13
|
await authorize_personal_project(self, actor, space, request.personal_project_id)
|
|
14
|
+
temporary_owner = await temporary_task_owner(self, actor, request)
|
|
15
|
+
if temporary_owner is not None:
|
|
16
|
+
return temporary_owner
|
|
13
17
|
owner = None
|
|
14
18
|
if request.session_id and not request.agent_id:
|
|
15
19
|
rows, _, _ = await self.runtime.driver.execute_query(
|
|
@@ -38,11 +42,28 @@ class StoreProjectAgentContext:
|
|
|
38
42
|
lead_agent_id=request.agent_id or owner, source_application=request.source_application,
|
|
39
43
|
routing_reason='Read-only task-entry context recovery',
|
|
40
44
|
)
|
|
45
|
+
if owner and not request.agent_id:
|
|
46
|
+
owner_rows = await self._assignment_candidates(
|
|
47
|
+
request.personal_space_id,
|
|
48
|
+
request.personal_project_id,
|
|
49
|
+
)
|
|
50
|
+
owner_row = next(
|
|
51
|
+
(row for row in owner_rows if row['agent_id'] == owner),
|
|
52
|
+
None,
|
|
53
|
+
)
|
|
54
|
+
if owner_row and not self._implicit_owner_allowed(owner_row, selection):
|
|
55
|
+
owner = None
|
|
56
|
+
selection = selection.model_copy(update={'lead_agent_id': None})
|
|
41
57
|
selected, candidates, reason, basis = await self._select_agents(actor, space, selection)
|
|
42
58
|
if not selected and reason == 'no_match' and not request.required_capabilities:
|
|
59
|
+
fallback_selection = selection.model_copy(update={'work_kind': 'project_context'})
|
|
43
60
|
selected, candidates, reason, basis = await self._select_agents(
|
|
44
|
-
actor, space,
|
|
61
|
+
actor, space, fallback_selection,
|
|
45
62
|
)
|
|
63
|
+
if selected and not self._implicit_owner_allowed(selected[0], fallback_selection):
|
|
64
|
+
selected, reason, basis = [], 'no_match', [
|
|
65
|
+
'management or HR peer cannot own unrelated project context implicitly',
|
|
66
|
+
]
|
|
46
67
|
if selected:
|
|
47
68
|
reason = 'project_context_fallback'
|
|
48
69
|
basis = [
|
|
@@ -18,6 +18,7 @@ from .project_agent_task_models import (
|
|
|
18
18
|
ProjectAgentWorkerRuntime,
|
|
19
19
|
)
|
|
20
20
|
from .provider_values import native_datetime
|
|
21
|
+
from .temporary_project_scope import TEMPORARY_SCOPE
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
TERMINAL_TASK_STATUSES = {'completed', 'failed', 'cancelled'}
|
|
@@ -246,6 +247,7 @@ class StoreProjectAgentTaskReads:
|
|
|
246
247
|
'task_id': raw['task_id'],
|
|
247
248
|
'personal_space_id': raw['personal_space_id'],
|
|
248
249
|
'personal_project_id': raw['personal_project_id'],
|
|
250
|
+
'project_scope': dict(TEMPORARY_SCOPE) if raw.get('scope_type') == 'temporary' else None,
|
|
249
251
|
'title': raw['title'],
|
|
250
252
|
'objective': raw['objective'],
|
|
251
253
|
'work_kind': raw['work_kind'],
|
|
@@ -366,11 +368,10 @@ class StoreProjectAgentTaskReads:
|
|
|
366
368
|
task_fallback = task_raw if len(participants) == 1 else {}
|
|
367
369
|
for participant in participants:
|
|
368
370
|
agent_id = participant['agent_id']
|
|
369
|
-
participant_events = [
|
|
371
|
+
participant_events = cls._observed_execution_events([
|
|
370
372
|
event for event in events
|
|
371
373
|
if event.agent_id == agent_id
|
|
372
|
-
|
|
373
|
-
]
|
|
374
|
+
])
|
|
374
375
|
agent_name, occupation_emoji = cls._participant_profile(participant)
|
|
375
376
|
worker_groups = {}
|
|
376
377
|
for event in participant_events:
|
|
@@ -396,7 +397,7 @@ class StoreProjectAgentTaskReads:
|
|
|
396
397
|
|
|
397
398
|
# A configured participant is not execution evidence. Legacy
|
|
398
399
|
# clients may omit worker_id, but they must still have recorded an
|
|
399
|
-
# Agent-linked running
|
|
400
|
+
# Agent-linked running event or concrete executor/model
|
|
400
401
|
# evidence before a summary row exists.
|
|
401
402
|
if not participant_events:
|
|
402
403
|
continue
|
|
@@ -420,12 +421,26 @@ class StoreProjectAgentTaskReads:
|
|
|
420
421
|
or event.actual_model_provider
|
|
421
422
|
or event.actual_model
|
|
422
423
|
or event.token_usage
|
|
423
|
-
or event.
|
|
424
|
-
|
|
425
|
-
'completed', 'failed', 'cancelled',
|
|
426
|
-
}
|
|
424
|
+
or event.worker_runtime
|
|
425
|
+
or (event.status == 'running' and event.actor_kind != 'hr')
|
|
427
426
|
)
|
|
428
427
|
|
|
428
|
+
@classmethod
|
|
429
|
+
def _observed_execution_events(cls, events):
|
|
430
|
+
observed = []
|
|
431
|
+
for event in events:
|
|
432
|
+
if cls._is_execution_event(event):
|
|
433
|
+
observed.append(event)
|
|
434
|
+
elif (observed and event.actor_kind != 'hr'
|
|
435
|
+
and event.status in {'paused', 'awaiting_review', 'blocked',
|
|
436
|
+
'completed', 'failed', 'cancelled'}):
|
|
437
|
+
# Keep legacy completion after a proven start, including old
|
|
438
|
+
# events whose missing actor kind is read as system. Recruitment,
|
|
439
|
+
# authorization and cancellation before any start are task
|
|
440
|
+
# control events, not evidence that a worker executed.
|
|
441
|
+
observed.append(event)
|
|
442
|
+
return observed
|
|
443
|
+
|
|
429
444
|
@classmethod
|
|
430
445
|
def _execution_summary_row(
|
|
431
446
|
cls,
|
|
@@ -8,6 +8,16 @@ from .project_agent_access import authorize_project_agent
|
|
|
8
8
|
from .project_agent_models import ProjectAgentProfile
|
|
9
9
|
from .project_agent_task_models import ProjectAgentParallelPlan
|
|
10
10
|
from .store_project_agents import SYSTEM_HR_AGENT_ID
|
|
11
|
+
from .system_hr_identity import LEGACY_HR_AGENT_ID
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
IMPLICIT_PEER_AGENT_IDS = frozenset({
|
|
15
|
+
'employee.jefa',
|
|
16
|
+
SYSTEM_HR_AGENT_ID.casefold(),
|
|
17
|
+
LEGACY_HR_AGENT_ID.casefold(),
|
|
18
|
+
})
|
|
19
|
+
IMPLICIT_PEER_CAPABILITIES = frozenset({'fuli.employee:jefa', 'fuli.employee:bole'})
|
|
20
|
+
IMPLICIT_PEER_AGENT_TYPES = frozenset({'hr'})
|
|
11
21
|
|
|
12
22
|
|
|
13
23
|
class StoreProjectAgentTaskStaffing:
|
|
@@ -79,10 +89,14 @@ class StoreProjectAgentTaskStaffing:
|
|
|
79
89
|
eligible = [row for row in rows if required.issubset({
|
|
80
90
|
item.casefold() for item in row.get('capabilities', [])
|
|
81
91
|
})]
|
|
82
|
-
|
|
92
|
+
implicit_eligible = [
|
|
93
|
+
row for row in eligible
|
|
94
|
+
if self._implicit_owner_allowed(row, request)
|
|
95
|
+
]
|
|
96
|
+
candidates, history = await self._rank_agent_candidates(implicit_eligible, request)
|
|
83
97
|
# Continuity may break a tie between qualified specialists; it must
|
|
84
98
|
# never override a capability requirement or a stronger work-kind fit.
|
|
85
|
-
continuity_pool =
|
|
99
|
+
continuity_pool = implicit_eligible
|
|
86
100
|
if candidates:
|
|
87
101
|
best_work_match = normalized_work_kind in {
|
|
88
102
|
item.casefold() for item in candidates[0].get('work_kinds', [])
|
|
@@ -100,7 +114,10 @@ class StoreProjectAgentTaskStaffing:
|
|
|
100
114
|
)
|
|
101
115
|
if continuity:
|
|
102
116
|
selected = continuity[0]
|
|
103
|
-
remaining = [
|
|
117
|
+
remaining = [
|
|
118
|
+
row for row in candidates or implicit_eligible
|
|
119
|
+
if row['agent_id'] != selected['agent_id']
|
|
120
|
+
]
|
|
104
121
|
return [selected], [selected, *remaining], 'project_continuity', [
|
|
105
122
|
self._project_history_match_basis(
|
|
106
123
|
project_history.get(selected['agent_id']),
|
|
@@ -129,20 +146,20 @@ class StoreProjectAgentTaskStaffing:
|
|
|
129
146
|
'matching Agent is unavailable to source client: '
|
|
130
147
|
f'{request.source_application or "other"}',
|
|
131
148
|
]
|
|
132
|
-
if len(eligible) == 1:
|
|
133
|
-
return [
|
|
149
|
+
if len(eligible) == 1 and len(implicit_eligible) == 1:
|
|
150
|
+
return [implicit_eligible[0]], implicit_eligible, 'sole_active_assignment', [
|
|
134
151
|
'the project has one active Agent assignment',
|
|
135
|
-
*self._automatic_selection_basis(
|
|
152
|
+
*self._automatic_selection_basis(implicit_eligible[0], request),
|
|
136
153
|
]
|
|
137
|
-
if
|
|
138
|
-
|
|
154
|
+
if implicit_eligible and normalized_work_kind == 'project_context' and not required:
|
|
155
|
+
implicit_eligible.sort(key=lambda row: (
|
|
139
156
|
self._metric_int(row.get('active_task_count')),
|
|
140
157
|
-int(self._metric_int(row.get('memory_revision')) > 0),
|
|
141
158
|
row['assigned_at'], row['agent_id'],
|
|
142
159
|
))
|
|
143
|
-
return
|
|
144
|
-
f'{len(
|
|
145
|
-
*self._automatic_selection_basis(
|
|
160
|
+
return implicit_eligible[:1], implicit_eligible, 'project_default', [
|
|
161
|
+
f'{len(implicit_eligible)} eligible project roles; working-memory continuity and stable tie-break',
|
|
162
|
+
*self._automatic_selection_basis(implicit_eligible[0], request),
|
|
146
163
|
]
|
|
147
164
|
return [], rows, 'no_match', ['no active assignment matched exactly']
|
|
148
165
|
selected = candidates[0]
|
|
@@ -166,6 +183,64 @@ class StoreProjectAgentTaskStaffing:
|
|
|
166
183
|
if item not in match_basis)
|
|
167
184
|
return [selected], candidates, reason, match_basis
|
|
168
185
|
|
|
186
|
+
@classmethod
|
|
187
|
+
def _implicit_owner_allowed(cls, row, request):
|
|
188
|
+
"""Keep management peers out of unrelated automatic ownership paths."""
|
|
189
|
+
|
|
190
|
+
if not cls._is_implicit_peer(row):
|
|
191
|
+
return True
|
|
192
|
+
profile = row.get('profile')
|
|
193
|
+
work_kinds = {
|
|
194
|
+
str(item).casefold()
|
|
195
|
+
for item in row.get('work_kinds', [])
|
|
196
|
+
}
|
|
197
|
+
work_kinds.update(
|
|
198
|
+
str(item).casefold()
|
|
199
|
+
for item in (cls._profile_value(profile, 'work_kinds', []) or [])
|
|
200
|
+
)
|
|
201
|
+
capabilities = {
|
|
202
|
+
str(item).casefold()
|
|
203
|
+
for item in row.get('capabilities', [])
|
|
204
|
+
}
|
|
205
|
+
capabilities.update(
|
|
206
|
+
str(item).casefold()
|
|
207
|
+
for item in (cls._profile_value(profile, 'capabilities', []) or [])
|
|
208
|
+
)
|
|
209
|
+
required = {
|
|
210
|
+
str(item).casefold()
|
|
211
|
+
for item in request.required_capabilities
|
|
212
|
+
}
|
|
213
|
+
return request.work_kind.casefold() in work_kinds or (
|
|
214
|
+
bool(required) and required.issubset(capabilities)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def _is_implicit_peer(cls, row):
|
|
219
|
+
agent_id = str(row.get('agent_id') or '').casefold()
|
|
220
|
+
profile = row.get('profile')
|
|
221
|
+
agent_type = str(cls._profile_value(profile, 'agent_type', '')).casefold()
|
|
222
|
+
capabilities = {
|
|
223
|
+
str(item).casefold()
|
|
224
|
+
for item in (cls._profile_value(profile, 'capabilities', []) or [])
|
|
225
|
+
}
|
|
226
|
+
capabilities.update(
|
|
227
|
+
str(item).casefold()
|
|
228
|
+
for item in (row.get('capabilities', []) or [])
|
|
229
|
+
)
|
|
230
|
+
return (
|
|
231
|
+
agent_id in IMPLICIT_PEER_AGENT_IDS
|
|
232
|
+
or agent_type in IMPLICIT_PEER_AGENT_TYPES
|
|
233
|
+
or bool(capabilities & IMPLICIT_PEER_CAPABILITIES)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _profile_value(profile, field, default):
|
|
238
|
+
if profile is None:
|
|
239
|
+
return default
|
|
240
|
+
if isinstance(profile, dict):
|
|
241
|
+
return profile.get(field, default)
|
|
242
|
+
return getattr(profile, field, default)
|
|
243
|
+
|
|
169
244
|
def _automatic_selection_basis(self, selected, request):
|
|
170
245
|
work_kind = request.work_kind.casefold()
|
|
171
246
|
if work_kind == 'project_context':
|
|
@@ -819,6 +819,7 @@ class StoreProjectAgentTasks(
|
|
|
819
819
|
task_id: $task_id,
|
|
820
820
|
personal_space_id: $personal_space_id,
|
|
821
821
|
personal_project_id: $personal_project_id,
|
|
822
|
+
scope_type: coalesce(project.scope_type, 'registered'),
|
|
822
823
|
payload_hash: $payload_hash,
|
|
823
824
|
title: $title,
|
|
824
825
|
objective: $objective,
|
|
@@ -73,6 +73,7 @@ class StoreRecords:
|
|
|
73
73
|
return PersonalProjectRecord(
|
|
74
74
|
project_id=value['project_id'],
|
|
75
75
|
personal_space_id=personal_space_id,
|
|
76
|
+
scope_type=value.get('scope_type') or 'registered',
|
|
76
77
|
publication_key=value['publication_key'],
|
|
77
78
|
profile=ProjectProfile.model_validate_json(value['profile_json']),
|
|
78
79
|
created_at=native_datetime(value['created_at']),
|
|
@@ -157,7 +157,8 @@ class StoreTaskContexts:
|
|
|
157
157
|
# content is already immutable under the memory checkpoint's payload hash.
|
|
158
158
|
# The existing checkpoint lets pre-upgrade claims retry without new writes.
|
|
159
159
|
memory_checkpoint_id = None
|
|
160
|
-
if record['project_agent_id'] and record['personal_project_id']
|
|
160
|
+
if (record['project_agent_id'] and record['personal_project_id']
|
|
161
|
+
and record.get('agent_memory_scope') != 'task_only'):
|
|
161
162
|
memory_checkpoint_id = stable_uuid(await self._resolved_agent_memory_id(
|
|
162
163
|
request.personal_space_id, record['personal_project_id'],
|
|
163
164
|
record['project_agent_id'],
|
|
@@ -18,12 +18,17 @@ async def adopt_task_agent(store, actor, token, request):
|
|
|
18
18
|
raise HTTPException(409, 'Task context already has an employee')
|
|
19
19
|
before = json.dumps(record, ensure_ascii=False)
|
|
20
20
|
agent = await authorize_project_agent(store, actor, space, request.personal_project_id,
|
|
21
|
-
request.agent_id, require_active=True
|
|
21
|
+
request.agent_id, require_active=True)
|
|
22
22
|
if request.source_application not in json.loads(agent['profile_json']).get('allowed_clients', []):
|
|
23
23
|
raise HTTPException(403, 'Agent is not allowed in this client')
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
task_only = agent.get('memory_scope') == 'task_only'
|
|
25
|
+
if task_only and agent.get('temporary_task_id') != request.task_id:
|
|
26
|
+
raise HTTPException(409, 'Temporary Agent belongs to another task')
|
|
27
|
+
memory = None if task_only else await store.get_project_agent_memory(
|
|
28
|
+
actor, request.personal_space_id, request.personal_project_id, request.agent_id)
|
|
29
|
+
record.update(project_agent_id=request.agent_id, memory_revision=memory.revision if memory else None,
|
|
30
|
+
coordination_task_id=request.task_id,
|
|
31
|
+
agent_memory_scope='task_only' if task_only else 'reviewed_agent',
|
|
27
32
|
work_log_required=True)
|
|
28
33
|
rows, _, _ = await store.runtime.driver.execute_query(
|
|
29
34
|
'''
|