fix(agent): 修复 skill action 卡片调用约定、memory 强类型校验和死代码清理
- 所有 calendar action .md: skill/action 替换为 module/method + mode 字段 - handler_memory: 新增 Pydantic extra=forbid 模型替代手工 dict 校验 - memory/SKILL.md: 补充 UserMemoryContent/WorkProfileContent 全字段文档 - 移除 handler_calendar 死代码 _batch_status 和 runner 旧别名 AgentScopeReActRunner - PRD §5.2-5.6 和 sse-events 协议对齐实际 module/method 实现
This commit is contained in:
@@ -324,13 +324,14 @@ The worker still sees only:
|
||||
|
||||
## 5.2 New `project_cli` model-facing input contract
|
||||
|
||||
The new canonical model-facing payload is:
|
||||
The canonical model-facing payload is:
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "get_event",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "event",
|
||||
"event_id": "<uuid>"
|
||||
}
|
||||
}
|
||||
@@ -338,91 +339,79 @@ The new canonical model-facing payload is:
|
||||
|
||||
Field meanings:
|
||||
|
||||
- `skill`: enabled business skill namespace
|
||||
- `action`: concrete business operation inside the skill
|
||||
- `input`: strict action-specific payload
|
||||
- `module`: enabled business module namespace (calendar, contacts, memory)
|
||||
- `method`: concrete business operation inside the module
|
||||
- `input`: strict method-specific payload
|
||||
|
||||
This is still one tool call. The worker is not choosing among many tools.
|
||||
|
||||
## 5.3 Calendar action protocol
|
||||
## 5.3 Calendar method protocol
|
||||
|
||||
The calendar skill should be redesigned around real business actions derived from `schedule_items` and `schedule_subscriptions`.
|
||||
The calendar module exposes the following methods registered in the CLI router:
|
||||
|
||||
### Event actions
|
||||
| Module | Method | Handler | Input Shape |
|
||||
|----------|----------------|----------------------------------|-------------|
|
||||
| calendar | read | `handle_calendar_list_day` | discriminated by `mode` |
|
||||
| calendar | create | `handle_calendar_create_event` | title, start_at, timezone, ... |
|
||||
| calendar | update | `handle_calendar_update_event` | event_id + patch |
|
||||
| calendar | delete | `handle_calendar_delete_event` | event_id |
|
||||
| calendar | share | `handle_calendar_invite_subscriber` | event_id, invitee, permissions |
|
||||
| calendar | accept_invite | `handle_calendar_accept_invite` | event_id |
|
||||
| calendar | reject_invite | `handle_calendar_reject_invite` | event_id |
|
||||
|
||||
1. `list_day`
|
||||
2. `list_range`
|
||||
3. `get_event`
|
||||
4. `create_event`
|
||||
5. `update_event`
|
||||
6. `delete_event`
|
||||
|
||||
### Subscription actions
|
||||
|
||||
1. `invite_subscriber`
|
||||
2. `accept_invite`
|
||||
3. `reject_invite`
|
||||
|
||||
### Why this action set
|
||||
|
||||
This set directly maps to current product behavior:
|
||||
|
||||
- user asks what is scheduled today -> `list_day`
|
||||
- user asks what is scheduled this week -> `list_range`
|
||||
- user asks for a known event's details -> `get_event`
|
||||
- user creates or edits a schedule item -> `create_event` / `update_event`
|
||||
- user removes a schedule item -> `delete_event`
|
||||
- user invites another person -> `invite_subscriber`
|
||||
- invite recipient responds -> `accept_invite` / `reject_invite`
|
||||
The `read` method uses a discriminated union with `mode` field to dispatch to list_day, list_range, or get_event internally.
|
||||
|
||||
This avoids overloading one label like `read` for two distinct business tasks.
|
||||
|
||||
## 5.4 Canonical calendar action shapes
|
||||
## 5.4 Canonical calendar method shapes
|
||||
|
||||
### `list_day`
|
||||
### `read` with mode=day (list one day)
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "list_day",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "day",
|
||||
"date": "2026-04-23",
|
||||
"timezone": "Asia/Shanghai"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `list_range`
|
||||
### `read` with mode=range (list time range)
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "list_range",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "range",
|
||||
"start_at": "2026-04-23T00:00:00+08:00",
|
||||
"end_at": "2026-04-24T00:00:00+08:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `get_event`
|
||||
### `read` with mode=event (get by ID)
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "get_event",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "event",
|
||||
"event_id": "<uuid>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `create_event`
|
||||
### `create`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "create_event",
|
||||
"module": "calendar",
|
||||
"method": "create",
|
||||
"input": {
|
||||
"title": "Project sync",
|
||||
"start_at": "2026-04-23T16:00:00+08:00",
|
||||
@@ -439,12 +428,12 @@ This avoids overloading one label like `read` for two distinct business tasks.
|
||||
}
|
||||
```
|
||||
|
||||
### `update_event`
|
||||
### `update`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "update_event",
|
||||
"module": "calendar",
|
||||
"method": "update",
|
||||
"input": {
|
||||
"event_id": "<uuid>",
|
||||
"patch": {
|
||||
@@ -457,24 +446,24 @@ This avoids overloading one label like `read` for two distinct business tasks.
|
||||
}
|
||||
```
|
||||
|
||||
### `delete_event`
|
||||
### `delete`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "delete_event",
|
||||
"module": "calendar",
|
||||
"method": "delete",
|
||||
"input": {
|
||||
"event_id": "<uuid>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `invite_subscriber`
|
||||
### `share`
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "invite_subscriber",
|
||||
"module": "calendar",
|
||||
"method": "share",
|
||||
"input": {
|
||||
"event_id": "<uuid>",
|
||||
"invitee": {
|
||||
@@ -493,8 +482,8 @@ This avoids overloading one label like `read` for two distinct business tasks.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "accept_invite",
|
||||
"module": "calendar",
|
||||
"method": "accept_invite",
|
||||
"input": {
|
||||
"event_id": "<uuid>"
|
||||
}
|
||||
@@ -505,8 +494,8 @@ This avoids overloading one label like `read` for two distinct business tasks.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "reject_invite",
|
||||
"module": "calendar",
|
||||
"method": "reject_invite",
|
||||
"input": {
|
||||
"event_id": "<uuid>"
|
||||
}
|
||||
@@ -556,21 +545,26 @@ This makes `view_skill_file` a real progressive-disclosure mechanism instead of
|
||||
|
||||
## 5.6 Error contract for self-correction
|
||||
|
||||
The redesigned CLI should return structured action-level validation feedback.
|
||||
The redesigned CLI returns structured validation feedback with field-level detail.
|
||||
|
||||
Canonical error example:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "failure",
|
||||
"ok": false,
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"error": {
|
||||
"code": "INVALID_ACTION_INPUT",
|
||||
"message": "action list_range requires start_at and end_at",
|
||||
"skill": "calendar",
|
||||
"action": "list_range",
|
||||
"missing_fields": ["start_at", "end_at"],
|
||||
"unexpected_fields": ["event_id"],
|
||||
"suggested_alternative_actions": ["get_event"]
|
||||
"message": "input does not match method schema",
|
||||
"retryable": false,
|
||||
"details": {
|
||||
"missing_fields": ["start_at", "end_at"],
|
||||
"invalid_fields": [],
|
||||
"alias_corrections": {
|
||||
"start_time": "start_at"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
__all__ = [
|
||||
"AgentScopeRuntimeOrchestrator",
|
||||
"AgentScopeRunner",
|
||||
"AgentScopeReActRunner",
|
||||
]
|
||||
|
||||
|
||||
@@ -14,8 +13,4 @@ def __getattr__(name: str):
|
||||
from core.agentscope.runtime.runner import AgentScopeRunner
|
||||
|
||||
return AgentScopeRunner
|
||||
if name == "AgentScopeReActRunner":
|
||||
from core.agentscope.runtime.runner import AgentScopeReActRunner
|
||||
|
||||
return AgentScopeReActRunner
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -611,6 +611,3 @@ class SystemAgentRuntimeConfig:
|
||||
api_key: str
|
||||
llm_config: SystemAgentLLMConfig
|
||||
extra_context: str | None = None
|
||||
|
||||
|
||||
AgentScopeReActRunner = AgentScopeRunner
|
||||
|
||||
@@ -644,14 +644,6 @@ def _normalize_phone(raw: str) -> str:
|
||||
return phone
|
||||
|
||||
|
||||
def _batch_status(success: int, failed: int) -> str:
|
||||
if failed == 0:
|
||||
return "success"
|
||||
if success == 0:
|
||||
return "failure"
|
||||
return "partial"
|
||||
|
||||
|
||||
def _day_input_to_range_input(input_payload: _CalendarReadDayInput) -> dict[str, str]:
|
||||
timezone_name = input_payload.timezone.strip() or "Asia/Shanghai"
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Any, cast
|
||||
from typing import Any, Literal, cast
|
||||
from uuid import UUID
|
||||
|
||||
from core.agentscope.tools.cli.models import CliCommand, CliCommandResult
|
||||
@@ -9,24 +9,34 @@ from core.agentscope.tools.utils.memory_domain import (
|
||||
create_memories_service,
|
||||
map_memory_exception,
|
||||
)
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.agent.runtime_models import ErrorInfo
|
||||
from schemas.enums import MemoryType
|
||||
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
|
||||
|
||||
|
||||
class _UpdateOperation(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
action: Literal["update", "delete"]
|
||||
memory_type: MemoryType
|
||||
user_content: dict[str, Any] | None = None
|
||||
work_content: dict[str, Any] | None = None
|
||||
forget_paths: list[str] | None = None
|
||||
|
||||
|
||||
class _MemoryUpdateInput(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
operations: list[_UpdateOperation] = Field(..., min_length=1)
|
||||
|
||||
|
||||
async def handle_memory_update(request: CliCommand) -> CliCommandResult:
|
||||
from core.db.session import AsyncSessionLocal
|
||||
|
||||
operations = request.input.get("operations")
|
||||
if not isinstance(operations, list) or not operations:
|
||||
return _invalid_argument(
|
||||
request=request,
|
||||
message="operations must be a non-empty list",
|
||||
details={
|
||||
"required_fields": ["operations"],
|
||||
"field_types": {"operations": "array of objects"},
|
||||
},
|
||||
)
|
||||
validated = _validate_input(request)
|
||||
if validated is None:
|
||||
return _validation_error(request)
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
service = create_memories_service(session=session, owner_id=UUID(request.owner_id))
|
||||
@@ -37,49 +47,9 @@ async def handle_memory_update(request: CliCommand) -> CliCommandResult:
|
||||
failed_ops: list[dict[str, Any]] = []
|
||||
result_items: list[dict[str, Any]] = []
|
||||
|
||||
for idx, op in enumerate(operations):
|
||||
if not isinstance(op, dict):
|
||||
failed_count += 1
|
||||
failed_ops.append(
|
||||
{
|
||||
"code": "INVALID_ARGUMENT",
|
||||
"message": "operation item must be object",
|
||||
"retryable": False,
|
||||
}
|
||||
)
|
||||
result_items.append(
|
||||
{
|
||||
"idx": idx,
|
||||
"memoryType": "unknown",
|
||||
"action": "invalid",
|
||||
"status": "failure",
|
||||
"code": "INVALID_ARGUMENT",
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
action = str(op.get("action") or "").strip().lower()
|
||||
if action not in {"update", "delete"}:
|
||||
failed_count += 1
|
||||
failed_ops.append(
|
||||
{
|
||||
"code": "INVALID_ARGUMENT",
|
||||
"message": "action must be update or delete",
|
||||
"retryable": False,
|
||||
}
|
||||
)
|
||||
result_items.append(
|
||||
{
|
||||
"idx": idx,
|
||||
"memoryType": str(op.get("memory_type") or "unknown"),
|
||||
"action": action or "invalid",
|
||||
"status": "failure",
|
||||
"code": "INVALID_ARGUMENT",
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
memory_type = MemoryType(str(op.get("memory_type") or "user"))
|
||||
for idx, op in enumerate(validated.operations):
|
||||
memory_type = op.memory_type
|
||||
action = op.action
|
||||
try:
|
||||
if action == "update":
|
||||
result = await _apply_update_operation(
|
||||
@@ -153,15 +123,43 @@ async def handle_memory_update(request: CliCommand) -> CliCommandResult:
|
||||
)
|
||||
|
||||
|
||||
def _validate_input(request: CliCommand) -> _MemoryUpdateInput | None:
|
||||
try:
|
||||
return _MemoryUpdateInput.model_validate(request.input)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _validation_error(request: CliCommand) -> CliCommandResult:
|
||||
return CliCommandResult(
|
||||
ok=False,
|
||||
module=request.module,
|
||||
method=request.method,
|
||||
error=ErrorInfo(
|
||||
code="INVALID_ARGUMENT",
|
||||
message="operations must be a non-empty list of objects with action (update|delete) and memory_type (user|work)",
|
||||
retryable=False,
|
||||
details={
|
||||
"required_fields": ["operations"],
|
||||
"field_types": {
|
||||
"operations": "array of objects",
|
||||
"operations[].action": "update or delete",
|
||||
"operations[].memory_type": "user or work",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def _apply_update_operation(
|
||||
*,
|
||||
service: Any,
|
||||
memory_type: MemoryType,
|
||||
op: dict[str, Any],
|
||||
op: _UpdateOperation,
|
||||
) -> dict[str, Any]:
|
||||
existing = await service.get_memory_model(memory_type=memory_type)
|
||||
if memory_type == MemoryType.USER:
|
||||
content_data = op.get("user_content")
|
||||
content_data = op.user_content
|
||||
if not isinstance(content_data, dict):
|
||||
raise ValueError("update action for user memory requires user_content")
|
||||
base = (
|
||||
@@ -177,7 +175,7 @@ async def _apply_update_operation(
|
||||
validated = UserMemoryContent.model_validate(merged)
|
||||
updated = await service.update_user_memory(content=validated)
|
||||
else:
|
||||
content_data = op.get("work_content")
|
||||
content_data = op.work_content
|
||||
if not isinstance(content_data, dict):
|
||||
raise ValueError("update action for work memory requires work_content")
|
||||
base = (
|
||||
@@ -205,9 +203,9 @@ async def _apply_delete_operation(
|
||||
*,
|
||||
service: Any,
|
||||
memory_type: MemoryType,
|
||||
op: dict[str, Any],
|
||||
op: _UpdateOperation,
|
||||
) -> dict[str, Any]:
|
||||
forget_paths_raw = op.get("forget_paths")
|
||||
forget_paths_raw = op.forget_paths
|
||||
if not isinstance(forget_paths_raw, list) or not forget_paths_raw:
|
||||
raise ValueError("delete action requires non-empty forget_paths")
|
||||
forget_paths = [
|
||||
@@ -237,25 +235,6 @@ async def _apply_delete_operation(
|
||||
}
|
||||
|
||||
|
||||
def _invalid_argument(
|
||||
*,
|
||||
request: CliCommand,
|
||||
message: str,
|
||||
details: dict[str, Any] | None,
|
||||
) -> CliCommandResult:
|
||||
return CliCommandResult(
|
||||
ok=False,
|
||||
module=request.module,
|
||||
method=request.method,
|
||||
error=ErrorInfo(
|
||||
code="INVALID_ARGUMENT",
|
||||
message=message,
|
||||
retryable=False,
|
||||
details=details,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _deep_merge_dict(base: dict[str, Any], patch: dict[str, Any]) -> dict[str, Any]:
|
||||
merged = deepcopy(base)
|
||||
for key, value in patch.items():
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# accept_invite
|
||||
|
||||
Use when accepting a shared event invitation.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
@@ -9,12 +11,10 @@
|
||||
- success: subscription response object
|
||||
- failure: `error.code`, `error.message`, `error.details`
|
||||
|
||||
Use when accepting a shared event invitation.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "accept_invite",
|
||||
"module": "calendar",
|
||||
"method": "accept_invite",
|
||||
"input": {
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ Use when creating a new event.
|
||||
## Input Schema
|
||||
|
||||
- `input.title`: required, `string`
|
||||
- `input.start_at`: required, `string`, ISO 8601 datetime
|
||||
- `input.start_at`: required, `string`, ISO 8601 datetime with timezone
|
||||
- `input.timezone`: required, `string`, IANA timezone
|
||||
- `input.end_at`: optional, `string | null`, ISO 8601 datetime
|
||||
- `input.description`: optional, `string | null`
|
||||
- `input.metadata`: optional, `object | null`
|
||||
- `input.end_at`: optional, `string`, ISO 8601 datetime with timezone
|
||||
- `input.description`: optional, `string`
|
||||
- `input.metadata`: optional, `object` with keys `location`, `reminder_minutes`, `color`, `notes`
|
||||
|
||||
## Output Shape
|
||||
|
||||
@@ -18,8 +18,8 @@ Use when creating a new event.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "create_event",
|
||||
"module": "calendar",
|
||||
"method": "create",
|
||||
"input": {
|
||||
"title": "Project sync",
|
||||
"start_at": "2026-04-23T10:00:00+08:00",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# delete_event
|
||||
|
||||
Use when deleting one known event.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
@@ -9,12 +11,10 @@
|
||||
- success: `data.status`, `data.success`, `data.failed`, `data.ids`, `data.results`
|
||||
- failure: `error.code`, `error.message`, `error.details`
|
||||
|
||||
Use when deleting one known event.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "delete_event",
|
||||
"module": "calendar",
|
||||
"method": "delete",
|
||||
"input": {
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ Use when the user already knows the target event identity.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.mode`: required, must be `"event"`
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
|
||||
## Output Shape
|
||||
@@ -13,9 +14,10 @@ Use when the user already knows the target event identity.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "get_event",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "event",
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
}
|
||||
@@ -24,3 +26,4 @@ Use when the user already knows the target event identity.
|
||||
## Rules
|
||||
|
||||
- Prefer this over list actions when an `event_id` is already available.
|
||||
- Do not use old field names: `command`, `subcommand`, `args`.
|
||||
|
||||
@@ -6,11 +6,11 @@ Use when sharing an event with one phone number.
|
||||
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
- `input.invitee`: required, `object`
|
||||
- `input.invitee.phone`: required, `string`
|
||||
- `input.invitee.phone`: required, `string`, phone number like `+8613800138000`
|
||||
- `input.permissions`: optional, `object`
|
||||
- `input.permissions.view`: optional, `bool`
|
||||
- `input.permissions.edit`: optional, `bool`
|
||||
- `input.permissions.invite`: optional, `bool`
|
||||
- `input.permissions.view`: optional, `bool`, default `true`
|
||||
- `input.permissions.edit`: optional, `bool`, default `false`
|
||||
- `input.permissions.invite`: optional, `bool`, default `false`
|
||||
|
||||
## Output Shape
|
||||
|
||||
@@ -19,8 +19,8 @@ Use when sharing an event with one phone number.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "invite_subscriber",
|
||||
"module": "calendar",
|
||||
"method": "share",
|
||||
"input": {
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"invitee": {
|
||||
|
||||
@@ -4,6 +4,7 @@ Use when the user asks about one calendar day in a local timezone.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.mode`: required, must be `"day"`
|
||||
- `input.date`: required, `string`, format `YYYY-MM-DD`
|
||||
- `input.timezone`: optional, `string`, IANA timezone like `Asia/Shanghai`
|
||||
|
||||
@@ -14,9 +15,10 @@ Use when the user asks about one calendar day in a local timezone.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "list_day",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "day",
|
||||
"date": "2026-04-23",
|
||||
"timezone": "Asia/Shanghai"
|
||||
}
|
||||
@@ -28,4 +30,5 @@ Use when the user asks about one calendar day in a local timezone.
|
||||
- `input` must not be empty.
|
||||
- `date` must be a concrete date string, not an empty object.
|
||||
- For words like today or tomorrow, convert them to a concrete `YYYY-MM-DD` date from `system_time_local` before calling `project_cli`.
|
||||
- Use `get_event` instead if you already have an `event_id`.
|
||||
- Use `mode: "event"` instead if you already have an `event_id`.
|
||||
- Do not use old field names: `start_time`, `end_time`, `event_timezone`.
|
||||
|
||||
@@ -4,8 +4,9 @@ Use when the user asks for a specific time range.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.start_at`: required, `string`, ISO 8601 datetime
|
||||
- `input.end_at`: required, `string`, ISO 8601 datetime
|
||||
- `input.mode`: required, must be `"range"`
|
||||
- `input.start_at`: required, `string`, ISO 8601 datetime with timezone
|
||||
- `input.end_at`: required, `string`, ISO 8601 datetime with timezone
|
||||
|
||||
## Output Shape
|
||||
|
||||
@@ -14,9 +15,10 @@ Use when the user asks for a specific time range.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "list_range",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "range",
|
||||
"start_at": "2026-04-23T09:00:00+08:00",
|
||||
"end_at": "2026-04-23T18:00:00+08:00"
|
||||
}
|
||||
@@ -25,5 +27,6 @@ Use when the user asks for a specific time range.
|
||||
|
||||
## Rules
|
||||
|
||||
- `start_at` and `end_at` must both be present.
|
||||
- `start_at` and `end_at` must both be present with timezone offset.
|
||||
- Do not send `event_id` to list actions.
|
||||
- Do not use old field names: `start_time`, `end_time`.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# reject_invite
|
||||
|
||||
Use when rejecting a shared event invitation.
|
||||
|
||||
## Input Schema
|
||||
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
@@ -9,12 +11,10 @@
|
||||
- success: subscription response object
|
||||
- failure: `error.code`, `error.message`, `error.details`
|
||||
|
||||
Use when rejecting a shared event invitation.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "reject_invite",
|
||||
"module": "calendar",
|
||||
"method": "reject_invite",
|
||||
"input": {
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ Use when changing one known event.
|
||||
- `input.event_id`: required, `string`, UUID
|
||||
- `input.patch`: required, `object`
|
||||
- `input.patch.title`: optional, `string`
|
||||
- `input.patch.description`: optional, `string | null`
|
||||
- `input.patch.start_at`: optional, `string | null`, ISO 8601 datetime
|
||||
- `input.patch.end_at`: optional, `string | null`, ISO 8601 datetime
|
||||
- `input.patch.timezone`: optional, `string`
|
||||
- `input.patch.metadata`: optional, `object | null`
|
||||
- `input.patch.description`: optional, `string`
|
||||
- `input.patch.start_at`: optional, `string`, ISO 8601 datetime with timezone
|
||||
- `input.patch.end_at`: optional, `string`, ISO 8601 datetime with timezone
|
||||
- `input.patch.timezone`: optional, `string`, IANA timezone
|
||||
- `input.patch.metadata`: optional, `object`
|
||||
- `input.patch.status`: optional, `string`
|
||||
|
||||
## Output Shape
|
||||
@@ -21,8 +21,8 @@ Use when changing one known event.
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "update_event",
|
||||
"module": "calendar",
|
||||
"method": "update",
|
||||
"input": {
|
||||
"event_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"patch": {
|
||||
@@ -37,3 +37,4 @@ Use when changing one known event.
|
||||
|
||||
- All mutable fields go inside `patch`.
|
||||
- Do not put mutable fields at the top level.
|
||||
- Use `start_at`/`end_at`, not `start_time`/`end_time`.
|
||||
|
||||
@@ -18,23 +18,16 @@ description: User memory management - store and forget personal facts and work p
|
||||
- User wants to remove previously stored information
|
||||
- Agent needs to recall user preferences for personalization
|
||||
|
||||
## Available Tool
|
||||
|
||||
Use the single tool `project_cli`.
|
||||
|
||||
Read this file first with `view_skill_file` when memory is the relevant skill.
|
||||
|
||||
## Calling Contract
|
||||
|
||||
- `module`: required, must be `memory`
|
||||
- `method`: required, must be `update`
|
||||
- `input.operations`: required, non-empty array
|
||||
- `input.operations`: required, non-empty array of operation objects
|
||||
- Each operation: `action` (update|delete), `memory_type` (user|work), plus action-specific fields
|
||||
- Output success fields: `data.status`, `data.success`, `data.failed`, `data.results`
|
||||
- Output failure fields: `error.code`, `error.message`, `error.details`
|
||||
|
||||
### Update Memory
|
||||
|
||||
Call `project_cli` with:
|
||||
### Update Memory (user)
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -45,39 +38,113 @@ Call `project_cli` with:
|
||||
{
|
||||
"action": "update",
|
||||
"memory_type": "user",
|
||||
"user_content": {}
|
||||
"user_content": {
|
||||
"occupation": "software engineer",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"preferences": {
|
||||
"communication_style": "concise"
|
||||
},
|
||||
"interests": ["reading", "hiking"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Operation object fields:
|
||||
- `action`: `update` or `delete`
|
||||
- `memory_type`: `user` or `work`
|
||||
- `update` requires matching content payload (`user_content` / `work_content`)
|
||||
- `delete` requires `forget_paths`
|
||||
### Update Memory (work)
|
||||
|
||||
Field requirements:
|
||||
- `operations[].action`: required, `string`
|
||||
- `operations[].memory_type`: required, `string`
|
||||
- `operations[].user_content`: required for `memory_type=user` and `action=update`, `object`
|
||||
- `operations[].work_content`: required for `memory_type=work` and `action=update`, `object`
|
||||
- `operations[].forget_paths`: required for `action=delete`, `array[string]`
|
||||
```json
|
||||
{
|
||||
"module": "memory",
|
||||
"method": "update",
|
||||
"input": {
|
||||
"operations": [
|
||||
{
|
||||
"action": "update",
|
||||
"memory_type": "work",
|
||||
"work_content": {
|
||||
"occupation": "software engineer",
|
||||
"expertise": ["backend", "distributed systems"],
|
||||
"current_projects": [
|
||||
{ "name": "Project X", "status": "active" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Composition Patterns
|
||||
### Delete Memory Paths
|
||||
|
||||
1. When user says "remember that I prefer morning meetings":
|
||||
- Call `project_cli` with `module="memory"`, `method="update"`, and appropriate content
|
||||
```json
|
||||
{
|
||||
"module": "memory",
|
||||
"method": "update",
|
||||
"input": {
|
||||
"operations": [
|
||||
{
|
||||
"action": "delete",
|
||||
"memory_type": "user",
|
||||
"forget_paths": ["preferences.communication_style", "interests"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. When user says "forget my old address":
|
||||
- Call `project_cli` with `module="memory"`, `method="update"`, `operations[0].action="delete"`, and the specific dot-path
|
||||
## Operation Fields
|
||||
|
||||
## Protocol Reminder
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `action` | yes | `"update"` or `"delete"` | Operation type |
|
||||
| `memory_type` | yes | `"user"` or `"work"` | Which memory to modify |
|
||||
| `user_content` | update+user | object | User memory content (see below) |
|
||||
| `work_content` | update+work | object | Work memory content (see below) |
|
||||
| `forget_paths` | delete | array of dot-path strings | Paths to remove, e.g. `["interests", "preferences.communication_style"]` |
|
||||
|
||||
- Never use old `command/subcommand/args` fields for memory writes.
|
||||
## User Content Fields (`user_content`)
|
||||
|
||||
Top-level fields for `memory_type=user` updates. All fields optional; only include what changes.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `occupation` | string | 职业 |
|
||||
| `timezone` | string | 时区 |
|
||||
| `primary_language` | string | 主要语言 |
|
||||
| `people` | array | 重要人物: `{name, relationship?, role?, notes?}` |
|
||||
| `places` | array | 常去地点: `{name, category?, address?, timezone?, notes?}` |
|
||||
| `preferences` | object | 偏好: `{communication_style?, language_preference?, work_lifestyle?, notification_preference?}` |
|
||||
| `scheduling_preferences` | object | 排程偏好: `{productive_windows?, preferred_meeting_windows?, no_meeting_windows?, meeting_buffer_minutes?, max_meetings_per_day?, notes?}` |
|
||||
| `interests` | array of strings | 兴趣爱好 |
|
||||
| `avoid_topics` | array of strings | 不想讨论的话题 |
|
||||
| `custom_rules` | array of strings | 用户自定义规则 |
|
||||
| `recurring_routines` | array | 周期性安排: `{name, description?, cadence?, time_windows?, importance?}` |
|
||||
|
||||
## Work Content Fields (`work_content`)
|
||||
|
||||
Top-level fields for `memory_type=work` updates. All fields optional; only include what changes.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `occupation` | string | 职业身份 |
|
||||
| `expertise` | array of strings | 专业领域 |
|
||||
| `preferred_tools` | array of strings | 惯用工具 |
|
||||
| `current_projects` | array | 项目: `{name, description?, status?, priority?, deadline?, collaborators?, notes?}` |
|
||||
| `work_habits` | object | 工作习惯: `{available_hours?, deep_work_blocks?, preferred_meeting_windows?, no_meeting_windows?, notification_channel?, notes?}` |
|
||||
| `team_members` | array | 团队成员: `{name, role?, relationship?, preferred_contact_channel?, notes?}` |
|
||||
| `team_context` | string | 团队背景 |
|
||||
| `work_rules` | array of strings | 工作规则 |
|
||||
|
||||
## Rules
|
||||
|
||||
- Never use old `command/subcommand/args` fields.
|
||||
- For `update`, provide only the fields that changed. Existing fields are merged, not replaced.
|
||||
- For `delete`, use dot-separated paths like `"preferences.communication_style"`.
|
||||
- Extra fields not listed above will be rejected.
|
||||
|
||||
## Failure Recovery
|
||||
|
||||
- If write fails, inspect `error.details` and retry with the documented field shape only
|
||||
- If forget path is invalid, suggest checking the data structure
|
||||
- If write fails, inspect `error.details` and retry with the documented field shape only.
|
||||
- If forget path is invalid, suggest checking the data structure.
|
||||
|
||||
@@ -216,9 +216,10 @@ data: <json>
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "calendar",
|
||||
"action": "get_event",
|
||||
"module": "calendar",
|
||||
"method": "read",
|
||||
"input": {
|
||||
"mode": "event",
|
||||
"event_id": "evt_123"
|
||||
}
|
||||
}
|
||||
@@ -233,18 +234,16 @@ SSE 协议中的工具名字段保持后端原样,不做服务端翻译:
|
||||
|
||||
前端展示层统一通过工具名本地化映射进行中文渲染,要求兼容两类命名风格:
|
||||
|
||||
- dot 风格:`memory.update`、`calendar.get_event`
|
||||
- snake 风格:`memory_update`、`calendar_get_event`
|
||||
- dot 风格:`memory.update`、`calendar.read`
|
||||
- snake 风格:`memory_update`、`calendar_read`
|
||||
|
||||
当前规范映射(canonical -> 中文)如下:
|
||||
|
||||
- `calendar.list_day` -> `读取当日日程`
|
||||
- `calendar.list_range` -> `读取区间日程`
|
||||
- `calendar.get_event` -> `读取日程详情`
|
||||
- `calendar.create_event` -> `创建日程`
|
||||
- `calendar.update_event` -> `更新日程`
|
||||
- `calendar.delete_event` -> `删除日程`
|
||||
- `calendar.invite_subscriber` -> `邀请参与者`
|
||||
- `calendar.read` -> `读取日程`
|
||||
- `calendar.create` -> `创建日程`
|
||||
- `calendar.update` -> `更新日程`
|
||||
- `calendar.delete` -> `删除日程`
|
||||
- `calendar.share` -> `邀请参与者`
|
||||
- `calendar.accept_invite` -> `接受邀请`
|
||||
- `calendar.reject_invite` -> `拒绝邀请`
|
||||
- `contacts.read` -> `读取联系人`
|
||||
|
||||
Reference in New Issue
Block a user