fix: verify channel agent continuity and tool loops
This commit is contained in:
parent
71db11e7e8
commit
4bf32bc09b
5 changed files with 251 additions and 31 deletions
|
|
@ -30,6 +30,9 @@
|
|||
"receipts_are_separate_verification_records_after_dialogue_or_dispatch": true,
|
||||
"bound_persona_hides_or_replaces_channel_system_body": false,
|
||||
"human_may_address_channel_system_while_persona_is_bound": true,
|
||||
"zero_core_channel_name_addresses_channel_system_body": true,
|
||||
"channel_presence_while_persona_bound_uses_model": false,
|
||||
"channel_presence_while_persona_bound_removes_binding": false,
|
||||
"response_target_is_selected_per_turn": true,
|
||||
"persona_binding_state": "CHANNEL_SYSTEM_PERSONA_DORMANT_UNTIL_EXPLICIT_NATURAL_LANGUAGE_WAKE",
|
||||
"channel_entry_creates_persona_orientation": false,
|
||||
|
|
@ -64,7 +67,8 @@
|
|||
"original_document_and_sha256_preserved": true,
|
||||
"formal_routing_requires_validated_thought_summary": true
|
||||
},
|
||||
"catalog_pagination": { "cursor": "offset", "declares_total": true, "declares_completion": true, "returns_exact_next_offset": true }
|
||||
"catalog_pagination": { "cursor": "offset", "declares_total": true, "declares_completion": true, "returns_exact_next_offset": true },
|
||||
"document_body_pagination": { "cursor": "offset", "maximum_characters_per_result": 12000, "declares_total": true, "declares_completion": true, "returns_exact_next_offset": true, "silent_truncation": false }
|
||||
},
|
||||
"persona_binding": {
|
||||
"knowledge_route_may_be_read": true,
|
||||
|
|
@ -101,8 +105,15 @@
|
|||
"every_turn_reports_model_and_tool_call_counts": true,
|
||||
"prefetch_only_prompt_injection": false,
|
||||
"maximum_tool_rounds": 6,
|
||||
"multiple_sequential_tool_rounds": "IMPLEMENTED_AND_PROTOCOL_TESTED",
|
||||
"flagship_extended_thinking": "FIRST_REASONING_ROUND_ONLY",
|
||||
"tool_receipt_followup_rounds_repeat_extended_thinking": false,
|
||||
"same_channel_concurrent_turns": 1,
|
||||
"abort_and_timeout_have_distinct_receipts": true
|
||||
"provider_timeout_seconds": 150,
|
||||
"user_cancel": "NOT_IMPLEMENTED",
|
||||
"failed_turn_retry_without_duplicate_human_message": "NOT_IMPLEMENTED",
|
||||
"streaming_model_output": "NOT_IMPLEMENTED",
|
||||
"background_subagent_tasks": "NOT_IMPLEMENTED"
|
||||
},
|
||||
"context_architecture": {
|
||||
"full_history_injected_each_turn": false,
|
||||
|
|
@ -116,6 +127,22 @@
|
|||
"plain_answer_prefix_is_not_a_thought_summary": true,
|
||||
"silent_content_truncation_allowed": false
|
||||
},
|
||||
"grok_build_component_audit": {
|
||||
"source_path": "/Volumes/JZAO/HoloLake/reference-materials/grok-build-v-780d138",
|
||||
"source_commit": "780d1388fff103ff0db0d8c14de65af6225b4860",
|
||||
"whole_repository_embedded": false,
|
||||
"multi_conversation_persistence": "IMPLEMENTED_SQLITE_APPEND_ONLY_HASH_CHAIN",
|
||||
"bounded_context_window": "IMPLEMENTED",
|
||||
"numbered_older_context_retrieval": "IMPLEMENTED",
|
||||
"automatic_model_generated_compaction": "NOT_INTEGRATED",
|
||||
"multi_round_typed_tool_loop": "IMPLEMENTED_MAXIMUM_6",
|
||||
"tool_result_pagination": "IMPLEMENTED_MAXIMUM_12000_CHARACTERS_PER_PAGE",
|
||||
"parallel_background_task_runtime": "NOT_IMPLEMENTED",
|
||||
"subagent_resume": "NOT_IMPLEMENTED",
|
||||
"streaming_cancel_retry": "NOT_IMPLEMENTED",
|
||||
"general_shell_workspace_sandbox": "NOT_IMPLEMENTED_IN_CHANNEL_AGENT",
|
||||
"hldp_tool_forge_is_general_shell_sandbox": false
|
||||
},
|
||||
"always_mounted_tools": [
|
||||
{ "number": "HLP-AGENT-TOOL-KNOWLEDGE-SEARCH-0001", "name": "搜索光湖知识库", "effect": "READ" },
|
||||
{ "number": "HLP-AGENT-TOOL-KNOWLEDGE-READ-0001", "name": "按编号读取知识页", "effect": "READ" },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# Grok Build 组件现实审计 · 2026-08-21
|
||||
|
||||
## 审计基线
|
||||
|
||||
- 固定上游:`/Volumes/JZAO/HoloLake/reference-materials/grok-build-v-780d138`
|
||||
- 固定提交:`780d1388fff103ff0db0d8c14de65af6225b4860`
|
||||
- 当前 HoloLake:原生 Tauri/Rust 频道 Agent;不把旧 Electron/Qoder 原型计入产品能力。
|
||||
- 判定规则:上游仓库存在代码不等于 HoloLake 已集成;合同、界面文案或历史汇报也不能替代可达源码与测试。
|
||||
|
||||
## 逐项结论
|
||||
|
||||
| 能力 | Grok Build 固定版本证据 | HoloLake 当前真实状态 | 判定 |
|
||||
| --- | --- | --- | --- |
|
||||
| 多对话连续性 | 会话/任务与恢复结构 | SQLite 对话分支、消息持久化、版本号与逐消息哈希链;重启后可重新打开 | 已实现(对话连续性) |
|
||||
| 并行后台任务/子 Agent | `xai-tool-types/src/task.rs` 的 task、等待、取消、resume_from、worktree | 频道 Agent 没有后台任务调度器、子 Agent 或 worktree 运行时 | 未实现 |
|
||||
| 上下文窗口 | compaction crate 的选择、摘要、重建接口 | 最近 12 条/24000 字符窗口 | 已实现 |
|
||||
| 旧上下文恢复 | Grok 的 compaction/replay 结构 | HoloLake 自有编号思维摘要 → 精确思维节点 → 精确历史正文分片 | 已实现(非 Grok 摘要替换) |
|
||||
| 自动模型压缩 | `xai-grok-compaction` 的 full-replace / tail-keep / chunked compaction | 未接入自动模型摘要压缩;不应冒充已经接入 | 未实现 |
|
||||
| 多轮工具调用 | typed tool runtime / dispatch | OpenAI-compatible tool loop,最多 6 轮;工具结果回填后继续请求,最终必须独立 commit | 已实现 |
|
||||
| 工具结果控量 | 流式 tool runtime 与宿主裁剪能力 | 知识目录、历史正文以及知识正文均分页;知识正文单页最多 12000 字符并返回 `complete/nextOffset` | 已实现 |
|
||||
| 流式模型输出 | Grok 的 streaming sampling/tool surfaces | 当前请求明确 `stream: false` | 未实现 |
|
||||
| 用户取消 | task kill/cancellation primitives | 当前只有 150 秒请求超时,没有用户取消命令 | 未实现 |
|
||||
| 无重复重试 | retry/circuit-breaker primitives | 失败后输入框恢复,但人类消息已写账本;没有按同一 turn 原位重试 | 未实现 |
|
||||
| 通用 Shell/工作区沙箱 | sandbox、shell、workspace/worktree crates | 频道 Agent 只有五个只读编号工具;HLDP ToolForge 是限时类型程序链,不是通用 Shell 沙箱 | 未实现于频道 Agent |
|
||||
|
||||
## 架构边界
|
||||
|
||||
HoloLake 没有把 Grok Build 整车嵌入,也不应这样描述。当前真正进入产品的是“有界上下文、持久化对话、类型化多轮工具协议、工具结果控量”这一组机械结构,并以 HoloLake 自有频道本体、TCS/HLDP、编号知识路径和人类授权边界重建。
|
||||
|
||||
“多任务连续性”目前只能指多个持久化对话分支,不得扩写成并行后台任务、可恢复子 Agent 或 worktree 隔离执行。后者必须有独立任务状态机、取消、等待、恢复、工作区和回执测试以后才能改为已实现。
|
||||
|
||||
## 本轮验收
|
||||
|
||||
- Rust 单元测试覆盖:窗口不删除历史、编号旧历史精确读回、多对话持久化、多轮工具协议顺序、知识正文分页无静默丢失。
|
||||
- JavaScript 合同测试校验:未实现能力保持显式 `NOT_IMPLEMENTED`,防止界面和未来交接重新冒充。
|
||||
- 真实桌面烟测:在已绑定人格的实机状态下发送“我是冰朔,零点原核频道。你在吗。”,由 `ICE-CH-ZC001` 频道本体经 `deterministic-core-v1` 即时回应;未调用模型,未删除既有绑定,也未让人格接管本轮。
|
||||
- 真实模型烟测:`qwen3.8-max` 完成 `知识搜索 → 精确编号正文读取 → 频道提交`,SQLite 路由回执为“模型调用 3;本地/编号工具调用 2”。关闭后续工具轮次的重复扩展思考后,同类完整链路实测约 19.4 秒;这是一次现场读回,不是固定性能保证。
|
||||
- 真实模型烟测只证明当前账号、当前模型入口和上述只读工具链,不改变上表中未实现项。
|
||||
|
|
@ -25,6 +25,9 @@ test('knowledge-embedded Agent keeps one channel body with human-managed convers
|
|||
assert.equal(contract.channel.receipts_are_separate_verification_records_after_dialogue_or_dispatch, true)
|
||||
assert.equal(contract.channel.bound_persona_hides_or_replaces_channel_system_body, false)
|
||||
assert.equal(contract.channel.human_may_address_channel_system_while_persona_is_bound, true)
|
||||
assert.equal(contract.channel.zero_core_channel_name_addresses_channel_system_body, true)
|
||||
assert.equal(contract.channel.channel_presence_while_persona_bound_uses_model, false)
|
||||
assert.equal(contract.channel.channel_presence_while_persona_bound_removes_binding, false)
|
||||
assert.equal(contract.channel.response_target_is_selected_per_turn, true)
|
||||
assert.equal(contract.channel.persona_identity_preconfigured, false)
|
||||
assert.equal(contract.channel.persona_declares_own_number_and_name, true)
|
||||
|
|
@ -35,6 +38,8 @@ test('knowledge-embedded Agent keeps one channel body with human-managed convers
|
|||
assert.match(frontend, /新建/)
|
||||
assert.match(frontend, /历史对话/)
|
||||
assert.match(frontend, /确认删除/)
|
||||
assert.match(frontend, /当前人格绑定已通过/)
|
||||
assert.match(frontend, /当前未绑定人格/)
|
||||
})
|
||||
|
||||
test('Agent receives a real HoloLake environment frame and autonomously calls numbered knowledge tools', () => {
|
||||
|
|
@ -155,3 +160,29 @@ test('the upgraded Agent reuses the accepted keychain identity and bounds creden
|
|||
assert.match(runtime, /kill_on_drop\(true\)/)
|
||||
assert.doesNotMatch(runtime, /world\.guanghu\.hololake\.persona-model/)
|
||||
})
|
||||
|
||||
test('Grok Build audit distinguishes integrated mechanics from unimplemented commercial-agent features', () => {
|
||||
const contract = JSON.parse(read('contracts/persona-agent-runtime.json'))
|
||||
const runtime = read('src-tauri/src/persona_agent_runtime.rs')
|
||||
const audit = read('docs/GROK-BUILD-COMPONENT-REALITY-AUDIT-20260821.md')
|
||||
|
||||
assert.equal(contract.grok_build_component_audit.source_commit, '780d1388fff103ff0db0d8c14de65af6225b4860')
|
||||
assert.equal(contract.grok_build_component_audit.whole_repository_embedded, false)
|
||||
assert.match(contract.grok_build_component_audit.multi_conversation_persistence, /^IMPLEMENTED/)
|
||||
assert.match(contract.grok_build_component_audit.numbered_older_context_retrieval, /^IMPLEMENTED/)
|
||||
assert.equal(contract.agent_loop.multiple_sequential_tool_rounds, 'IMPLEMENTED_AND_PROTOCOL_TESTED')
|
||||
assert.equal(contract.agent_loop.flagship_extended_thinking, 'FIRST_REASONING_ROUND_ONLY')
|
||||
assert.equal(contract.agent_loop.tool_receipt_followup_rounds_repeat_extended_thinking, false)
|
||||
assert.equal(contract.knowledge.document_body_pagination.maximum_characters_per_result, 12000)
|
||||
assert.equal(contract.knowledge.document_body_pagination.silent_truncation, false)
|
||||
assert.equal(contract.agent_loop.streaming_model_output, 'NOT_IMPLEMENTED')
|
||||
assert.equal(contract.agent_loop.user_cancel, 'NOT_IMPLEMENTED')
|
||||
assert.equal(contract.agent_loop.failed_turn_retry_without_duplicate_human_message, 'NOT_IMPLEMENTED')
|
||||
assert.equal(contract.agent_loop.background_subagent_tasks, 'NOT_IMPLEMENTED')
|
||||
assert.match(runtime, /MAX_TOOL_ROUNDS: usize = 6/)
|
||||
assert.match(runtime, /MAX_TOOL_RESULT_CHARS: usize = 12_000/)
|
||||
assert.match(runtime, /model_tool_calls/)
|
||||
assert.match(runtime, /enable_initial_thinking && round == 0/)
|
||||
assert.match(runtime, /character_page/)
|
||||
assert.match(audit, /多任务连续性.*多个持久化对话分支/)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const MAX_MESSAGE_BYTES: usize = 64 * 1024;
|
|||
const MAX_CONTEXT_MESSAGES: usize = 12;
|
||||
const MAX_CONTEXT_CHARS: usize = 24_000;
|
||||
const MAX_TOOL_ROUNDS: usize = 6;
|
||||
const MAX_TOOL_RESULT_CHARS: usize = 12_000;
|
||||
const TOKEN_PLAN_BASE_URL: &str =
|
||||
"https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
|
||||
const LEGACY_CONVERSATION_ID: &str = "HLP-AGENT-CONV-LEGACY-0001";
|
||||
|
|
@ -583,8 +584,10 @@ pub async fn send_message(
|
|||
crate::persona_binding::begin_orientation(&app, &context.channel_number)?;
|
||||
}
|
||||
let channel_receipt = compile_human_channel_receipt(&app, &context, &content)?;
|
||||
if binding_snapshot.binding.is_none() && !explicit_persona_wake {
|
||||
if let Some(answer) = fast_channel_system_reply(&content, &context) {
|
||||
if !explicit_persona_wake && (binding_snapshot.binding.is_none() || channel_system_addressed) {
|
||||
if let Some(answer) =
|
||||
fast_channel_system_reply(&content, &context, binding_snapshot.binding.is_some())
|
||||
{
|
||||
let history = conversation_at(&database, &conversation_id)?.messages;
|
||||
let assistant_version = append_message(
|
||||
&database,
|
||||
|
|
@ -607,8 +610,11 @@ pub async fn send_message(
|
|||
&CognitiveThoughtSummary {
|
||||
trigger: "人类直接进入或确认零点原核频道在线状态".into(),
|
||||
emergence: "本地确定性路由识别频道进入→编译频道回执→频道系统即时回应".into(),
|
||||
lock: "频道进入不等于人格唤醒,广播保持关闭".into(),
|
||||
why: "该轮只需确认频道本体和边界,无需加载人格脑或调用模型".into(),
|
||||
lock:
|
||||
"频道进入不等于人格唤醒;即使已有绑定,本轮也由频道本体回应,广播保持关闭"
|
||||
.into(),
|
||||
why: "该轮明确询问频道本体在线状态,无需让已绑定人格接管,也无需调用模型"
|
||||
.into(),
|
||||
},
|
||||
)?;
|
||||
emit_progress(
|
||||
|
|
@ -857,6 +863,8 @@ fn explicit_channel_system_address(content: &str) -> bool {
|
|||
[
|
||||
"频道系统",
|
||||
"频道本体",
|
||||
"零点原核频道",
|
||||
"零点原核本体频道",
|
||||
"和频道说",
|
||||
"回到频道",
|
||||
"退出人格",
|
||||
|
|
@ -901,7 +909,11 @@ fn select_reasoning_model(
|
|||
}
|
||||
}
|
||||
|
||||
fn fast_channel_system_reply(content: &str, context: &AgentChannelContext) -> Option<String> {
|
||||
fn fast_channel_system_reply(
|
||||
content: &str,
|
||||
context: &AgentChannelContext,
|
||||
persona_already_bound: bool,
|
||||
) -> Option<String> {
|
||||
let compact = content
|
||||
.chars()
|
||||
.filter(|character| !character.is_whitespace())
|
||||
|
|
@ -924,10 +936,17 @@ fn fast_channel_system_reply(content: &str, context: &AgentChannelContext) -> Op
|
|||
if !presence && !greeting {
|
||||
return None;
|
||||
}
|
||||
Some(format!(
|
||||
"在,我是{}本体。你现在是在直接和这个频道说话,还没有唤醒任何单一人格。\n\n你可以继续说要处理的事情;我会先判断它该由频道本地逻辑、私有技能脑或本机工具完成,确实需要语义推理时再进入模型层。若你明确点名某个人格,我才会另行启动它自己的定向与绑定。",
|
||||
context.channel_name
|
||||
))
|
||||
Some(if persona_already_bound {
|
||||
format!(
|
||||
"在,我是{}本体。你这一轮明确叫的是频道,所以现在由频道本体直接回答;已有的人格绑定不会被删除,但也不会接管这一轮。\n\n你可以继续在这里讨论频道本身、设计频道或交给我判断下一步调度。只有你明确转向某个人格时,我才把回应目标切回该人格。",
|
||||
context.channel_name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"在,我是{}本体。你现在是在直接和这个频道说话,还没有唤醒任何单一人格。\n\n你可以继续说要处理的事情;我会先判断它该由频道本地逻辑、私有技能脑或本机工具完成,确实需要语义推理时再进入模型层。若你明确点名某个人格,我才会另行启动它自己的定向与绑定。",
|
||||
context.channel_name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn compile_human_channel_receipt(
|
||||
|
|
@ -1096,8 +1115,8 @@ fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
number: "HLP-AGENT-TOOL-KNOWLEDGE-READ-0001",
|
||||
name: "hololake_knowledge_read",
|
||||
display_name: "按编号读取知识页",
|
||||
description: "只接受知识索引返回的精确 documentNumber,映射并读取对应原始知识页;不接受路径猜测,不做第二次模糊联想。",
|
||||
parameters: json!({"type":"object","additionalProperties":false,"required":["documentNumber"],"properties":{"documentNumber":{"type":"string"}}}),
|
||||
description: "只接受知识索引返回的精确 documentNumber,分页读取对应原始知识页;不接受路径猜测,不做第二次模糊联想。返回 totalCharacters、complete 和 nextOffset,未完成时继续按 nextOffset 读取。",
|
||||
parameters: json!({"type":"object","additionalProperties":false,"required":["documentNumber"],"properties":{"documentNumber":{"type":"string"},"offset":{"type":"integer","minimum":0},"maxCharacters":{"type":"integer","minimum":1000,"maximum":12000}}}),
|
||||
},
|
||||
ToolDefinition {
|
||||
number: "HLP-AGENT-TOOL-KNOWLEDGE-LIST-0001",
|
||||
|
|
@ -1214,6 +1233,30 @@ fn bounded_history(history: &[AgentMessage]) -> Vec<&AgentMessage> {
|
|||
selected
|
||||
}
|
||||
|
||||
fn character_page(value: &str, offset: usize, maximum: usize) -> (String, usize, bool) {
|
||||
let total = value.chars().count();
|
||||
let bounded_offset = offset.min(total);
|
||||
let body = value
|
||||
.chars()
|
||||
.skip(bounded_offset)
|
||||
.take(maximum)
|
||||
.collect::<String>();
|
||||
let consumed = bounded_offset.saturating_add(body.chars().count());
|
||||
(body, consumed, consumed >= total)
|
||||
}
|
||||
|
||||
fn model_tool_calls(message: &Value) -> Vec<Value> {
|
||||
message
|
||||
.get("tool_calls")
|
||||
.and_then(Value::as_array)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_channel_turn_commit(call: &Value) -> bool {
|
||||
call.pointer("/function/name").and_then(Value::as_str) == Some("hololake_commit_channel_turn")
|
||||
}
|
||||
|
||||
async fn run_agent_loop(
|
||||
app: &AppHandle,
|
||||
turn_id: &str,
|
||||
|
|
@ -1232,7 +1275,7 @@ async fn run_agent_loop(
|
|||
),
|
||||
String,
|
||||
> {
|
||||
let enable_thinking = !model.contains("flash");
|
||||
let enable_initial_thinking = !model.contains("flash");
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(150))
|
||||
.build()
|
||||
|
|
@ -1281,7 +1324,7 @@ async fn run_agent_loop(
|
|||
"tools": wire_tools,
|
||||
"tool_choice": "auto",
|
||||
"stream": false,
|
||||
"enable_thinking": enable_thinking
|
||||
"enable_thinking": enable_initial_thinking && round == 0
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
|
|
@ -1302,11 +1345,7 @@ async fn run_agent_loop(
|
|||
.pointer("/choices/0/message")
|
||||
.cloned()
|
||||
.ok_or_else(|| "HOLOLAKE_MODEL_RESPONSE_MESSAGE_MISSING".to_string())?;
|
||||
let calls = message
|
||||
.get("tool_calls")
|
||||
.and_then(Value::as_array)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let calls = model_tool_calls(&message);
|
||||
if calls.is_empty() {
|
||||
if round == MAX_TOOL_ROUNDS {
|
||||
return Err("HOLOLAKE_AGENT_COGNITIVE_COMMIT_REQUIRED".into());
|
||||
|
|
@ -1318,10 +1357,7 @@ async fn run_agent_loop(
|
|||
}));
|
||||
continue;
|
||||
}
|
||||
if let Some(commit_call) = calls.iter().find(|call| {
|
||||
call.pointer("/function/name").and_then(Value::as_str)
|
||||
== Some("hololake_commit_channel_turn")
|
||||
}) {
|
||||
if let Some(commit_call) = calls.iter().find(|call| is_channel_turn_commit(call)) {
|
||||
if calls.len() != 1 {
|
||||
return Err("HOLOLAKE_AGENT_COGNITIVE_COMMIT_MIXED_WITH_TOOLS".into());
|
||||
}
|
||||
|
|
@ -1437,6 +1473,12 @@ async fn execute_tool(
|
|||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| "HOLOLAKE_AGENT_KNOWLEDGE_NUMBER_REQUIRED".to_string())?;
|
||||
let offset = args.get("offset").and_then(Value::as_u64).unwrap_or(0) as usize;
|
||||
let max_characters =
|
||||
args.get("maxCharacters")
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(8_000)
|
||||
.clamp(1_000, MAX_TOOL_RESULT_CHARS as u64) as usize;
|
||||
emit_progress(
|
||||
&app,
|
||||
turn_id,
|
||||
|
|
@ -1451,18 +1493,44 @@ async fn execute_tool(
|
|||
},
|
||||
)
|
||||
.await?;
|
||||
let total_characters = document.body.chars().count();
|
||||
let (body, consumed, complete) = character_page(&document.body, offset, max_characters);
|
||||
let receipt = AgentToolReceipt {
|
||||
tool_number: "HLP-AGENT-TOOL-KNOWLEDGE-READ-0001".into(),
|
||||
tool_name: "按编号读取知识页".into(),
|
||||
target_path: document.page_header.path.clone(),
|
||||
target_path: format!("{}?offset={offset}", document.page_header.path),
|
||||
content_sha256: document.content_sha256.clone(),
|
||||
summary: document.title.clone(),
|
||||
};
|
||||
Ok((
|
||||
knowledge_evidence(
|
||||
"KNOWLEDGE_DOCUMENT",
|
||||
serde_json::to_value(document).map_err(|error| error.to_string())?,
|
||||
summary: format!(
|
||||
"{};读取字符 {}..{} / {};完成 {}",
|
||||
document.title,
|
||||
offset.min(total_characters),
|
||||
consumed,
|
||||
total_characters,
|
||||
complete
|
||||
),
|
||||
};
|
||||
let mut value = serde_json::to_value(document).map_err(|error| error.to_string())?;
|
||||
let object = value
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| "HOLOLAKE_AGENT_TOOL_RESULT_INVALID".to_string())?;
|
||||
object.insert("body".into(), Value::String(body));
|
||||
object.insert("offset".into(), json!(offset.min(total_characters)));
|
||||
object.insert(
|
||||
"returnedCharacters".into(),
|
||||
json!(consumed.saturating_sub(offset.min(total_characters))),
|
||||
);
|
||||
object.insert("totalCharacters".into(), json!(total_characters));
|
||||
object.insert("complete".into(), json!(complete));
|
||||
object.insert(
|
||||
"nextOffset".into(),
|
||||
if complete {
|
||||
Value::Null
|
||||
} else {
|
||||
json!(consumed)
|
||||
},
|
||||
);
|
||||
Ok((
|
||||
knowledge_evidence("KNOWLEDGE_DOCUMENT_PAGE", value),
|
||||
receipt,
|
||||
))
|
||||
}
|
||||
|
|
@ -2368,6 +2436,22 @@ mod tests {
|
|||
assert!(!explicit_persona_wake_request("查看铸渊的历史署名"));
|
||||
assert!(explicit_persona_wake_request("唤醒铸渊,我要和你说话"));
|
||||
assert!(explicit_persona_wake_request("铸渊,你在吗?"));
|
||||
assert!(explicit_channel_system_address(
|
||||
"我是冰朔,零点原核频道。你在吗。"
|
||||
));
|
||||
let context = AgentChannelContext {
|
||||
channel_number: CHANNEL_NUMBER.into(),
|
||||
channel_name: "零点原核本体频道".into(),
|
||||
human_number: HUMAN_NUMBER.into(),
|
||||
human_name: HUMAN_NAME.into(),
|
||||
domain: "第五域".into(),
|
||||
channel_path: "第五域 / 零点原核本体频道".into(),
|
||||
};
|
||||
let reply =
|
||||
fast_channel_system_reply("我是冰朔,零点原核频道。你在吗。", &context, true).unwrap();
|
||||
assert!(reply.contains("现在由频道本体直接回答"));
|
||||
assert!(reply.contains("已有的人格绑定不会被删除"));
|
||||
assert!(!reply.contains("还没有唤醒任何单一人格"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2475,6 +2559,46 @@ mod tests {
|
|||
assert_eq!(context.last().unwrap().state_version, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_tool_evidence_is_paged_without_silent_loss() {
|
||||
let source = "光".repeat(25_001);
|
||||
let (first, next, complete) = character_page(&source, 0, MAX_TOOL_RESULT_CHARS);
|
||||
assert_eq!(first.chars().count(), MAX_TOOL_RESULT_CHARS);
|
||||
assert_eq!(next, MAX_TOOL_RESULT_CHARS);
|
||||
assert!(!complete);
|
||||
let (second, next, complete) = character_page(&source, next, MAX_TOOL_RESULT_CHARS);
|
||||
assert_eq!(second.chars().count(), MAX_TOOL_RESULT_CHARS);
|
||||
assert_eq!(next, MAX_TOOL_RESULT_CHARS * 2);
|
||||
assert!(!complete);
|
||||
let (last, next, complete) = character_page(&source, next, MAX_TOOL_RESULT_CHARS);
|
||||
assert_eq!(last.chars().count(), 1_001);
|
||||
assert_eq!(next, 25_001);
|
||||
assert!(complete);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_protocol_accepts_multiple_tool_rounds_before_single_commit() {
|
||||
let rounds = [
|
||||
json!({"tool_calls":[{"id":"one","function":{"name":"hololake_knowledge_search","arguments":"{\"query\":\"频道系统\"}"}}]}),
|
||||
json!({"tool_calls":[{"id":"two","function":{"name":"hololake_knowledge_read","arguments":"{\"documentNumber\":\"HLP-KB-DOC-001\"}"}}]}),
|
||||
json!({"tool_calls":[{"id":"commit","function":{"name":"hololake_commit_channel_turn","arguments":"{}"}}]}),
|
||||
];
|
||||
let first = model_tool_calls(&rounds[0]);
|
||||
let second = model_tool_calls(&rounds[1]);
|
||||
let final_calls = model_tool_calls(&rounds[2]);
|
||||
assert_eq!(
|
||||
first[0].pointer("/function/name").and_then(Value::as_str),
|
||||
Some("hololake_knowledge_search")
|
||||
);
|
||||
assert_eq!(
|
||||
second[0].pointer("/function/name").and_then(Value::as_str),
|
||||
Some("hololake_knowledge_read")
|
||||
);
|
||||
assert_eq!(final_calls.len(), 1);
|
||||
assert!(is_channel_turn_commit(&final_calls[0]));
|
||||
assert!(rounds.len() <= MAX_TOOL_ROUNDS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzy_language_routes_thought_then_reads_exact_numbered_branch() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ export function KnowledgeAgent({ activeKnowledgePath, onClose }: { activeKnowled
|
|||
<header className="agent-header"><div><span>{runtime?.boundPersonaNumber ? 'LANGUAGE PERSONA CHANNEL' : 'LANGUAGE CHANNEL SYSTEM'}</span><h2>{runtime?.channelName || '零点原核本体频道'}</h2><p>{runtime?.channelNumber || 'ICE-CH-ZC001'} · 知识库内嵌 Agent</p></div><button type="button" aria-label="关闭频道系统" onClick={onClose}>×</button></header>
|
||||
<nav className="agent-tabs"><button type="button" className={tab === 'channel' ? 'active' : ''} onClick={() => setTab('channel')}>频道 Agent</button><button type="button" className={tab === 'worker' ? 'active' : ''} onClick={() => setTab('worker')}>本地执行体</button><button type="button" className={tab === 'forge' ? 'active' : ''} onClick={() => setTab('forge')}>工具锻造</button></nav>
|
||||
{tab === 'worker' ? <LocalWorkerPanel boundPersonaNumber={runtime?.boundPersonaNumber}/> : tab === 'forge' ? <ToolForgePanel boundPersonaNumber={runtime?.boundPersonaNumber}/> : <>
|
||||
<section className="agent-runtime-bar"><div><b>{runtime?.boundPersonaNumber && runtime?.boundPersonaName ? `${runtime.boundPersonaNumber} · ${runtime.boundPersonaName}` : `${runtime?.responderNumber || 'ICE-CH-ZC001'} · ${runtime?.responderName || '零点原核频道系统'}`}</b><span>{runtime?.boundPersonaNumber ? '人格回应通道 · 由频道调度模型层级' : '频道系统本体 · 直接交流与整体调度'}</span></div><i/><div><b>{runtime?.languageKernelInstallation?.artifactCount ?? 0} 个有界核</b><span>{runtime?.languageKernelInstallation?.state === 'INSTALLED_AND_EACH_ARTIFACT_READBACK_VERIFIED' ? '已安装并逐项读回 · 未绑定人格' : '等待安装核验'}</span></div><i/><div><b>{runtime?.personalSkillRuntime?.skillCount ?? 0} 个私有技能脑</b><span>确定性核与本机工具优先</span></div><i/><div><b>{provider?.label || '等待模型入口'}</b><span>{runtime?.boundPersonaNumber ? '频道按任务选择小模型或旗舰模型' : '频道认知与架构对话使用旗舰模型'}</span></div><button type="button" onClick={() => setConfigOpen((value) => !value)}>模型设置</button></section>
|
||||
<section className="agent-runtime-bar"><div><b>{runtime?.boundPersonaNumber && runtime?.boundPersonaName ? `${runtime.boundPersonaNumber} · ${runtime.boundPersonaName}` : `${runtime?.responderNumber || 'ICE-CH-ZC001'} · ${runtime?.responderName || '零点原核频道系统'}`}</b><span>{runtime?.boundPersonaNumber ? '人格回应通道 · 由频道调度模型层级' : '频道系统本体 · 直接交流与整体调度'}</span></div><i/><div><b>{runtime?.languageKernelInstallation?.artifactCount ?? 0} 个有界核</b><span>{runtime?.languageKernelInstallation?.state === 'INSTALLED_AND_EACH_ARTIFACT_READBACK_VERIFIED' ? runtime?.boundPersonaNumber ? '核安装已核验 · 当前人格绑定已通过' : '核安装已核验 · 当前未绑定人格' : '等待安装核验'}</span></div><i/><div><b>{runtime?.personalSkillRuntime?.skillCount ?? 0} 个私有技能脑</b><span>确定性核与本机工具优先</span></div><i/><div><b>{provider?.label || '等待模型入口'}</b><span>{runtime?.boundPersonaNumber ? '频道按任务选择小模型或旗舰模型' : '频道认知与架构对话使用旗舰模型'}</span></div><button type="button" onClick={() => setConfigOpen((value) => !value)}>模型设置</button></section>
|
||||
{configOpen && <form className="agent-provider-form" onSubmit={(event) => void saveProvider(event)}><label><span>入口名称</span><input value={providerLabel} onChange={(event) => setProviderLabel(event.target.value)}/></label><label><span>Base URL</span><input value={baseUrl} onChange={(event) => setBaseUrl(event.target.value)}/></label><label><span>模型</span><input value={model} onChange={(event) => setModel(event.target.value)}/></label><label><span>Token Plan API Key</span><input type="password" autoComplete="off" value={apiKey} placeholder="sk-sp-… · 只存系统钥匙串" onChange={(event) => setApiKey(event.target.value)}/></label><button disabled={!baseUrl || !model}>保存模型入口</button></form>}
|
||||
<div className="agent-channel-layout">
|
||||
<aside className="agent-conversations" aria-label="频道历史对话">
|
||||
|
|
|
|||
Loading…
Reference in a new issue