Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ tool registry, including changes to a reused model connection. In-flight image
requests retain their original snapshot. The settings test action explicitly
generates one standard-quality image and must never run on load or save.

**智能定时任务(`create_scheduled_task`,只在 daemon 注册)的两道闸。** 该工具按 `activation_skill=scheduled-task` 延迟开放,模型只能先从技能索引发现技能。用户反馈「技能经常不触发、触发了又建出子定时任务」(提示词「建立定时任务,每天帮我生成代码」被整句写进任务内容,到点执行时再建一个),现在:(1) 技能索引对所有带 `ToolImpl::activation_hint` 的门控工具单列「Skill-gated built-in tools」一节(`ToolExecutor::skill_gated_tools` 按会话策略过滤,需要 `skill_view` 且技能已安装),不受目录截断影响,门控技能也不进休眠;(2) 带 `ToolImpl::loop_execution_refusal` 的工具在 `loop.active` 会话(定时任务执行及其子代理)里经 `capture_request_source` 并入 `hidden_builtin_tools`,schema 与执行同一谓词,旧任务的坏 prompt 也不会再派生;(3) AI 创建时 `prompt_requests_scheduled_task_creation`(手写扫描「动词[+量词]紧挨定时任务」、`/scheduled-task` 等标记)拒绝仍带创建请求的 prompt(`PROMPT_CONTAINS_CREATION_REQUEST`,在权限提问之前),手动表单不经过它;执行上下文 `build_loop_system_context` 也说明任务文本里的建任务 / 时间描述只是创建时的原话。**不要**把隐藏改成只靠提示词:执行会话里权限提问会照常弹给用户,一答就建出子任务。回归:`scheduled_task_tool_test.cpp` 的 Prompt* 两条、`agent_loop_tool_lifecycle_events_test.cpp::LoopExecutionHidesScheduledTaskCreation`、`skills_index_prompt_test.cpp` 的 GatedTool* 三条、`api_request_builder_test.cpp::SkillIndexAdvertisesGatedToolUntilPolicyHidesIt`、`loop_scheduler_test.cpp::SystemContextTreatsCreationWordingAsOneExecution`。

[AgentLoop](src/engine/agent/agent_loop.hpp) is the public facade. [TurnRunner](src/engine/agent/turn/turn_runner.cpp) owns the multi-turn state machine; [TurnFinalizer](src/engine/agent/turn/turn_finalizer.cpp) owns ordered completion. Fixed dependencies enter through [AgentLoopServices](src/engine/agent/agent_loop_services.hpp); event-backed prompters are installed before explicit start(). A text-only assistant reply ends the loop. `task_complete` is an optional explicit terminator that renders a concise completion row. `AskUserQuestion` is not a terminator; its answer returns as a tool result. `config.agent_loop.max_iterations` is an optional hard cap; `0` or omitted means unlimited.

**输出损坏重发一次(`<arg_value>`)。** 回复正文里(代码块与行内代码之外)出现工具参数模板标记 `<arg_key>` / `<arg_value>` 及闭合标签(`find_leaked_tool_argument_markup`),说明网关把模板残片吐进了输出流,这一步的正文与同一回复里的原生工具调用都不可信:TurnRunner 在执行工具之前整条丢弃(不入历史、不执行)、清掉 TUI / Web 的流式草稿、发 `response_corrupted_retry` 通知后原样重发;每步连续最多 1 次(`ResponseRecoveryState::corrupted_output_retries`,产出有效工具批次后清零),重发后仍损坏就按原流程继续。`</think>` 刻意不算 —— 把推理写进正文的模型会合法输出它(用户拍板:think 残片不处理)。起因:反馈 huangyuan816,坏回复里的 bash 命令也夹着乱码照样执行,之后整个回合答非所问。
Expand Down Expand Up @@ -897,6 +899,8 @@ The existing region detector still probes DuckDuckGo once at startup through `Pr
`saved_models` is a named registry; `default_model_name` points into it. Each entry needs `name` (must NOT start with `(` — reserved for synthesized `(session:<id>)`), `provider`, `model`. OpenAI entries also need `base_url` + `api_key`. On load, array order is oldest-to-newest: for duplicate names, every occurrence except the last is atomically persisted as `<name>-<6-lowercase-hex-hash>`, while the last keeps the original name. Duplicate-repair write failures remain fatal. A non-empty dangling `default_model_name` is instead replaced with the first saved-model name before validation and atomically written back; a write failure is logged but startup continues with the in-memory fallback and retries on the next load. If the main logger is not initialized yet, this repair silently opens `~/.acecode/logs/config-<date>.log`; an active logger is never redirected. This repair does not rewrite an empty default and emits no UI message. Successful full loads and saves refresh a restricted `config-backups/last-good/config.json` snapshot. Later JSON parse/type/range/semantic failures archive the exact invalid bytes under `config-backups/invalid`, atomically restore and validate that snapshot, and persist a one-time Web/Desktop warning; recovery logs contain paths and categories only. Without a valid prior snapshot, startup keeps the invalid active file and follows the existing fatal boundary.
`acecode configure` upserts one named `saved_models` entry from the selected provider/model and sets it as `default_model_name`; normal startup no longer derives a selectable model from top-level provider fields.

Each saved model may carry an optional `sampling` object (`temperature` [0,2], `top_p` (0,1], `top_k` ≥ 1, `min_p` [0,1]). Unset keys are never sent, so a profile without sampling produces byte-identical requests. Support is per provider/protocol and validated in `validate_model_sampling_support` (`saved_models.cpp`): Chat Completions sends all four (`top_k`/`min_p` are vLLM/SGLang extensions the official OpenAI API rejects), Responses only `temperature`/`top_p`, Anthropic rejects `min_p` and caps `temperature` at 1, managed Copilot/Grok reject sampling entirely — a value that would be silently dropped is a validation error, not a no-op. Edits replace the whole object (`null`/`{}` clears; a provider switch clears it). Sampling is part of the provider construction fingerprint, so running sessions rebuild on the next turn after an edit. Settings > Models > 高级设置 shows only the fields the selected provider/protocol supports (`modelSamplingFieldSupport` in `web/src/lib/modelSettings.js`). Added for the intranet wizard-ai gateway, whose GLM model ships recommended values (top_p 0.95 / top_k 20 / temperature 1 / min_p 0) that ACECode previously never sent.

### Model profile resolution

At startup and on every `--resume` / `/resume`, `src/adapters/provider/model_resolver.cpp` resolves the effective `ModelEntry` in three layers. CLI `-r` is not a `--resume` short alias; it starts the TUI normally, runs the startup hook path, then opens the same `/resume` picker immediately.
Expand Down
4 changes: 2 additions & 2 deletions assets/seed/MANIFEST.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"bundle_version": "2026-10-10.1",
"bundle_version": "2026-10-15.1",
"description": "Versioned default ACECode global Skill, expert, and managed hook seed bundle reconciled at startup.",
"skills": [
{
Expand Down Expand Up @@ -66,7 +66,7 @@
"name": "scheduled-task",
"source_id": "acecode:scheduled-task@2026-10-04",
"relative_path": "acecode/scheduled-task",
"skill_md_sha256": "89354de4dc007479bf5a39aba0179928f6f53b39ead451fddcdbb5c9a65b691e"
"skill_md_sha256": "fb18793a5d66f9fe54300a9a00eaa18860aba33caa1e91d80da006dd1fa1d49f"
}
],
"experts": [
Expand Down
2 changes: 1 addition & 1 deletion assets/seed/seed.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2026-10-10.1
2026-10-15.1
9 changes: 6 additions & 3 deletions assets/seed/skills/acecode/scheduled-task/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: scheduled-task
description: 通过自然语言创建 ACECode 内置定时任务。用于定时提醒、每天或每周执行、每隔一段时间检查、重复若干天等请求,以及“智能添加定时任务”入口。只讨论计划或立即执行普通任务时不创建定时任务。
description: 创建 ACECode 内置定时任务。用户要在某个时间点、每天/每周/工作日、每隔一段时间或连续若干天自动执行某件事或收到提醒时使用,包括“建立/添加/设置定时任务”“明天9点提醒我”“每天帮我…”与“智能添加定时任务”入口。只讨论计划或要求立即执行时不创建。
---

# 创建定时任务
Expand All @@ -11,7 +11,10 @@ description: 通过自然语言创建 ACECode 内置定时任务。用于定时

- 提取任务内容和执行时间。`X点`、`X天` 都是待用户填写的占位符,不能自行猜测或照字面创建。用简短问题补齐,必要时合并询问具体时刻、重复天数和会议内容。
- 用执行环境当前时间解析“明天”等相对日期;默认沿用 daemon 所在时区,远程连接或用户另指定时区时明确核对。不要硬编码今天、UTC 偏移或猜测 epoch 毫秒值;可用系统时间工具或本地日期计算命令计算。
- 名称保持简短;`prompt` 是到点要做的事。提醒任务应明确要求提醒用户,而不是让执行任务再次创建定时任务。不要把整段创建对话复制为执行提示词。
- 名称保持简短。`prompt` 是**每次到点执行一次**要做的事,写成直接指令:去掉“建立/创建/添加定时任务”和“每天、每周、明天9点”等时间描述,时间只放在 `schedule` 里。绝不能把用户原话或整段创建对话复制进 `prompt`,否则每次执行都会被当成一次新的建任务请求。
- 用户说“建立定时任务,每天帮我生成代码” → `prompt` 写“生成代码:<具体生成什么、放在哪里>”,内容不明确时先问清楚。
- 用户说“明天9点提醒我参加会议” → `prompt` 写“提醒用户:参加会议”。
- 工具会拒绝仍含创建请求的 `prompt`(`PROMPT_CONTAINS_CREATION_REQUEST`),按提示改写后再调用。
- 未指定模型时省略 `model_name`,沿用手动表单默认模型。纯提醒省略工作区;项目任务使用现有工作区的 `workspace_hash` 与 `workspace_cwd`,可用工作区查询工具核对。`use_worktree` 默认为 false,只在用户要求隔离时开启。

## 时间规则
Expand All @@ -32,6 +35,6 @@ description: 通过自然语言创建 ACECode 内置定时任务。用于定时

## 后续执行

任务在现有“定时任务”页面查看、修改、停用和删除;到期运行方式由调度器负责,不承诺每次新建或复用聊天。daemon 需运行,离线错过不补跑;同工作区已有定时任务运行时跳过该次。可使用截止时间或手动停用,停止正在执行的任务需打开对应执行会话。
任务在现有“定时任务”页面查看、修改、停用和删除;到期运行方式由调度器负责,不承诺每次新建或复用聊天。定时任务的执行会话(含其子代理)中没有创建工具,执行时不会再派生新的定时任务。daemon 需运行,离线错过不补跑;同工作区已有定时任务运行时跳过该次。可使用截止时间或手动停用,停止正在执行的任务需打开对应执行会话。

普通任务沿用现有完成提示。监控类任务的执行提示词要保留用户的通知意图:无变化时保持安静,只在有实质变化、完成、失败或需要用户处理时报告;不要额外要求每次输出状态。
72 changes: 56 additions & 16 deletions docs/daemon-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -816,13 +816,24 @@ visible_roots}`. `total` and `visible_roots` count non-pinned roots, while
`sessions` is a flat array containing those roots, the children of expanded
roots, and separately pinned rows. Every non-pinned row adds `sidebar_key`,
`sidebar_parent_key` (empty for roots), `sidebar_child_count`, `sidebar_rank`
(within siblings), and `sidebar_expanded` to the normal session summary.
(within siblings), and `sidebar_expanded`.
Clients must not compare `sessions.length` with root totals.

Automatic group keys use `loop:<task-id>`; leaf keys use `session:<session-id>`.
The newest non-detached execution by creation time represents an automatic
group. Selecting old history does not replace it. Metadata parsing is cached
by file modification time and size; transcript bodies are never read here.
Automatic group keys use `loop:<task-id>`; manual groups anchored to a detached
scheduled session use `group:<anchor-session-id>`. Real session keys always use
`session:<session-id>`. Every execution, including the newest, is a child.
Group headers are presentation nodes with `sidebar_group:true` and the stable
synthetic `id` `sidebar-group:<sidebar_key>`; never navigate to, subscribe to,
mark read, pin, or archive that ID as a session. Clicking a header only toggles it.
`sidebar_group_name` and `title` contain the task name when available (clients
use a localized scheduled-task fallback otherwise). `sidebar_latest_session_id`
identifies the member selected by newest creation time; its timestamps supply
the header time. A rename or navigation to older history never replaces the
task name. `sidebar_members` contains compact attention snapshots for all members,
including hidden ones, so clients can aggregate subsequent status events while
collapsed. Headers are never busy: unread members produce `unread`, otherwise
`read`, even if a member is running. Metadata parsing is cached by modification
time and size; neither transcripts nor hidden members' full summaries are read.

### `POST /api/workspaces/:hash/session-sidebar` and `POST /api/session-sidebar`

Expand All @@ -836,9 +847,11 @@ The same query parameters apply. Body:
scope. Only a scheduled root with `loop_execution.loop_id` (the alarm marker)
can accept children, including an empty group or a detached scheduled execution.
Ordinary sessions may reorder or become children but cannot become parents.
A root with children can only reorder at the root; a child cannot accept
children. Moving a single-run automatic group into a scheduled parent detaches that
execution; future scheduled executions form their own automatic group.
A group header can only reorder at the root; a child cannot accept children.
Moving a single execution into a scheduled group detaches that execution from
its previous group; future executions still join their task's automatic group.
Dropping onto a detached scheduled root creates a manual group containing that
root's real session and the incoming child. Empty group headers disappear.
Self moves, stale targets, and third-level relationships return HTTP 409 with
`SIDEBAR_TARGET_CHANGED` or `SIDEBAR_TWO_LEVELS_ONLY`. An ordinary destination
parent returns 409/`SIDEBAR_SCHEDULED_PARENT_ONLY`; malformed bodies return 400.
Expand All @@ -848,10 +861,14 @@ sidebar page and reveal the destination.
Relationships and sibling order are atomically persisted in the scope's
`sidebar_sessions.json`, separately from task provenance and subagent
`parent_session_id`. An explicit empty parent keeps a detached execution at the
root after reload and future runs. Missing or archived parents promote their
manual children; removing an automatic representative selects the next run.
root after reload and future runs. Missing or archived manual anchors promote
their remaining members; removing the newest automatic execution selects the
next member for header time without changing its task identity.
Reading a legacy layout that assigns children to an ordinary session promotes
those children to roots without changing their execution provenance.
Legacy scheduled-session parent keys are projected into manual groups with
the original parent execution included as a child. The next move persists the
canonical relationships and ordering using layout version 2.

### `POST /api/workspaces/:hash/sessions`

Expand Down Expand Up @@ -3019,7 +3036,7 @@ cannot be disabled and return `409 {"error":"HOOK_MANAGED"}`.
原值和 `has_api_key` 布尔值,供编辑表单默认遮罩回填;调用方必须把整个响应视为
敏感数据,日志与错误消息仍不得包含密钥。高级字段有值时会原样返回,包括
`endpoint_mode`、`api_protocol`、`max_output_tokens`、`capabilities_source`、`reasoning`、
`request_headers`、`context_window` 与 `stream_timeout_ms`。
`sampling`、`request_headers`、`context_window` 与 `stream_timeout_ms`。

### `POST /api/models`

Expand Down Expand Up @@ -3048,10 +3065,22 @@ cannot be disabled and return `409 {"error":"HOOK_MANAGED"}`.
"default_effort": "medium",
"effort": "high",
"supports_max_tokens": false
}
},
"sampling": {"temperature": 1, "top_p": 0.95, "top_k": 20, "min_p": 0}
}
```

`sampling` 是可选的采样参数对象,四个键都可省略或为 `null`(视为未设置):
`temperature` ∈ [0, 2]、`top_p` ∈ (0, 1]、`top_k` 为正整数、`min_p` ∈ [0, 1]。
未设置的键不会出现在发往模型服务的请求体里,沿用服务端默认值;整个对象省略或
没有任何已设置的键时,请求体与不支持该功能的版本完全一致。支持范围按 Provider /
API 协议区分:OpenAI Chat Completions 四个都发送(`top_k` / `min_p` 是 vLLM、
SGLang 等推理服务的扩展字段,官方 OpenAI 接口会拒绝);Responses 只支持
`temperature` / `top_p`;Anthropic 支持 `temperature`(上限 1)、`top_p`、`top_k`,
不支持 `min_p`;Copilot / Grok 托管 Provider 不接受采样参数。越界值或当前 Provider /
协议不支持的键返回 `INVALID_SAMPLING`,托管 Provider 返回 `UNSUPPORTED_MODEL_OPTION`。
响应只返回已设置的键。

`api_protocol` 仅适用于 `provider:"openai"`,接受 `chat_completions` 或
`responses`;省略时,目录身份 `models_dev_provider_id:"acemodel"` 使用 Responses,
其他 OpenAI 配置继续使用 Chat Completions。模型设置的 **API 协议** 下拉框可
Expand All @@ -3075,9 +3104,10 @@ cannot be disabled and return `409 {"error":"HOOK_MANAGED"}`.
路径仍区分大小写。

省略 `base_url`、`context_window`、`stream_timeout_ms`、`capabilities`、
`endpoint_mode`、`api_protocol`、`max_output_tokens`、`capabilities_source`、`reasoning` 或
`request_headers` 会保留原值。显式空 `request_headers` 会清空请求头;对应高级
字段传 `null` 时按各字段合同清除。外部登录器留下的 legacy `readonly:true` 只是
`endpoint_mode`、`api_protocol`、`max_output_tokens`、`capabilities_source`、`reasoning`、
`sampling` 或 `request_headers` 会保留原值。显式空 `request_headers` 会清空请求头;
`sampling` 传入时整体替换(旧值中未出现的键会被清掉),传 `null` 或 `{}` 清除全部
采样参数,切换到其他 Provider 时同样清除;其余高级字段传 `null` 时按各字段合同清除。外部登录器留下的 legacy `readonly:true` 只是
兼容元数据,不阻止编辑。响应包含 `api_key` 原值与 `has_api_key`,并仅允许经
Web 认证的调用方读取。

Expand Down Expand Up @@ -4673,7 +4703,17 @@ rather than a single newest file: every upgrade log modified within the last
three days (72 hours) is merged oldest-first into `logs/upgrade.log.tail.txt`,
truncated as a whole to the same 512 KiB tail; with no upgrade activity in that
window the entry is omitted. If `session_id` is empty, the package contains
those logs only. The upload target is derived from `upgrade.base_url`.
those logs only. When a session is selected, the package also carries that
session's request trajectory (`<project_dir>/<session_id>/trajectory.jsonl`:
every model request's full messages and tool list, plus each response's text,
tool calls and usage) as `session/<session_id>.trajectory.jsonl`. It keeps the
newest 4 MiB, starting at the first complete line after the cut so every packed
line is valid JSON; a single record larger than the cap keeps its partial tail.
A missing trajectory file is skipped without failing the upload. `feedback.json`
records it under `trajectory` (`entry_name`, `path`, `available`, `size_bytes`,
`tail_bytes`, `truncated`), and `included_files` lists the entry right after the
session JSONL when it was packed. Terminal `/feedback` attaches the active
session's trajectory the same way. The upload target is derived from `upgrade.base_url`.

Success:

Expand Down
Loading
Loading