mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-18 17:13:25 +08:00
fix(scheduler): inherit creator specialist in schedule_create
When the model omits specialist, inject selected_specialist from the current tool executor context so scheduled jobs run under the expert that created them. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ad052daaf0
commit
a27bca5648
2 changed files with 77 additions and 0 deletions
|
|
@ -505,6 +505,21 @@ class ToolExecutor:
|
||||||
path_policy_tenant_id=ctx.path_policy_tenant_id,
|
path_policy_tenant_id=ctx.path_policy_tenant_id,
|
||||||
path_policy_user_id=ctx.path_policy_user_id,
|
path_policy_user_id=ctx.path_policy_user_id,
|
||||||
)
|
)
|
||||||
|
# Default specialist context: if the model did not explicitly provide a specialist for
|
||||||
|
# schedule tools, inherit from the current executor specialist.
|
||||||
|
#
|
||||||
|
# This makes scheduled jobs created by ops/generalist reflect the creator specialist by default,
|
||||||
|
# which is important for "created-by specialist" semantics and for later scheduled runs.
|
||||||
|
try:
|
||||||
|
tname = str(tc.name or "")
|
||||||
|
if tname == "schedule_create":
|
||||||
|
cur_spec = str(ctx.specialist or "").strip().lower()
|
||||||
|
if cur_spec and not str(tool_args.get("specialist") or "").strip() and not str(
|
||||||
|
tool_args.get("selected_specialist") or ""
|
||||||
|
).strip():
|
||||||
|
tool_args["selected_specialist"] = cur_spec
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
tool_args = filter_arguments_to_schema(tool.parameters, tool_args)
|
tool_args = filter_arguments_to_schema(tool.parameters, tool_args)
|
||||||
|
|
||||||
ok, v_err = validate_tool_arguments(tool.parameters, tool_args)
|
ok, v_err = validate_tool_arguments(tool.parameters, tool_args)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ from pathlib import Path
|
||||||
|
|
||||||
from runtime.tools.context_inject import enrich_tool_arguments
|
from runtime.tools.context_inject import enrich_tool_arguments
|
||||||
from runtime.tools.experts.productivity.schedule_tools import schedule_create_tool
|
from runtime.tools.experts.productivity.schedule_tools import schedule_create_tool
|
||||||
|
from runtime.chat.tool_runtime import ToolExecutionContext, ToolExecutor
|
||||||
|
from runtime.tools.base import ToolRegistry, ToolSpec
|
||||||
|
from svc.llm.transports.base import LLMToolCall
|
||||||
from svc.persistence.assistant_store import reset_assistant_store_singleton
|
from svc.persistence.assistant_store import reset_assistant_store_singleton
|
||||||
from svc.persistence.sqlite_store import SqliteStore
|
from svc.persistence.sqlite_store import SqliteStore
|
||||||
|
|
||||||
|
|
@ -88,6 +91,65 @@ class ToolContextInjectTests(unittest.TestCase):
|
||||||
self.assertNotIn("user_id", filtered)
|
self.assertNotIn("user_id", filtered)
|
||||||
self.assertIn("name", filtered)
|
self.assertIn("name", filtered)
|
||||||
|
|
||||||
|
def test_schedule_create_inherits_executor_specialist_when_missing(self) -> None:
|
||||||
|
# Tool args from the model commonly omit "specialist". In that case, schedule_create should
|
||||||
|
# receive selected_specialist inherited from executor context (ops/generalist/...).
|
||||||
|
seen: dict[str, object] = {}
|
||||||
|
|
||||||
|
def _capture(args: dict[str, object]) -> dict[str, object]:
|
||||||
|
seen.update(args)
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
fake_schedule_create = ToolSpec(
|
||||||
|
name="schedule_create",
|
||||||
|
description="capture args",
|
||||||
|
parameters={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"prompt_text": {"type": "string"},
|
||||||
|
"schedule_kind": {"type": "string"},
|
||||||
|
"schedule_expr": {"type": "string"},
|
||||||
|
"selected_specialist": {"type": "string"},
|
||||||
|
},
|
||||||
|
"required": ["name", "prompt_text", "schedule_kind", "schedule_expr"],
|
||||||
|
"additionalProperties": True,
|
||||||
|
},
|
||||||
|
handler=_capture,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.store.add_message(
|
||||||
|
session_id=self.session_id,
|
||||||
|
role="assistant",
|
||||||
|
content="hi",
|
||||||
|
event_type="assistant_text",
|
||||||
|
turn_uuid="t0",
|
||||||
|
)
|
||||||
|
assistant_msg_id = int(self.store.get_messages(session_id=self.session_id, limit=1)[0].id)
|
||||||
|
|
||||||
|
ctx = ToolExecutionContext(
|
||||||
|
store=self.store,
|
||||||
|
tools=ToolRegistry([fake_schedule_create]),
|
||||||
|
session_id=self.session_id,
|
||||||
|
lang="en",
|
||||||
|
specialist="ops",
|
||||||
|
turn_uuid="turn-1",
|
||||||
|
)
|
||||||
|
tc = LLMToolCall(
|
||||||
|
id="tc1",
|
||||||
|
name="schedule_create",
|
||||||
|
arguments={
|
||||||
|
"name": "drink water",
|
||||||
|
"prompt_text": "drink water",
|
||||||
|
"schedule_kind": "interval",
|
||||||
|
"schedule_expr": "300",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_msgs, _ = ToolExecutor().execute_tool_uses(ctx=ctx, assistant_msg_id=assistant_msg_id, tool_uses=[tc])
|
||||||
|
self.assertEqual(len(tool_msgs), 1)
|
||||||
|
self.assertEqual(str(seen.get("selected_specialist") or ""), "ops")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue