mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-11 06:30:25 +00:00
Personas: requires_folder/subagents/scheduling traits replace family and the workspace enum
Manifest keeps a legacy family shim; telemetry wire fields unchanged. No behavior change; spec in ocw-context/docs/workspace-scratch-design.md.
This commit is contained in:
@@ -25,7 +25,7 @@ def test_code_persona_matches_builder(tmp_path):
|
||||
reg = PersonaRegistry()
|
||||
ctx = _ctx(tmp_path)
|
||||
assert _names(reg.agent("code"), ctx) == _names(code_agent(), ctx)
|
||||
assert reg.agent("code").family == "code"
|
||||
assert reg.agent("code").requires_folder and reg.agent("code").subagents
|
||||
|
||||
|
||||
def test_cowork_persona_matches_builder(tmp_path):
|
||||
@@ -42,7 +42,7 @@ def test_ops_persona_composes_knowledge_toolset(tmp_path):
|
||||
# Ops uses the same capability list as Cowork (files/search/shell/todo).
|
||||
assert _names(reg.agent("ops"), ctx) == _names(cowork_agent(), ctx)
|
||||
a = reg.agent("ops")
|
||||
assert a.family == "knowledge" and a.messaging and a.connectors
|
||||
assert not a.requires_folder and a.scheduling and a.messaging and a.connectors
|
||||
assert "read_file_lines" in _names(a, ctx) # multi-root knowledge files
|
||||
|
||||
|
||||
|
||||
@@ -66,9 +66,7 @@ def test_persona_detail_endpoint(tmp_path, monkeypatch):
|
||||
assert detail["id"] == "ops"
|
||||
assert detail["name"] == "Ops Coworker"
|
||||
assert detail["enabled"] is True # builtins ship enabled (UX-029)
|
||||
assert (
|
||||
detail["workspace"] == "deliverable"
|
||||
) # §16 collapse: ops is a scratch persona now
|
||||
assert detail["requires_folder"] is False # ops is a scratch persona
|
||||
assert detail["default_permission_mode"] == "interactive"
|
||||
assert "anthropic:claude-opus-4-8" in detail["recommended_models"]
|
||||
assert set(detail["tools"]) == {"files", "search", "shell", "todo"}
|
||||
|
||||
@@ -54,7 +54,7 @@ def test_install_from_dir_lands_disabled_pending_consent(tmp_path):
|
||||
reg.set_enabled("acme-ops", True)
|
||||
reg.set_surfaced("acme-ops", True)
|
||||
assert "acme-ops" in [e["name"] for e in reg.sidebar()]
|
||||
assert reg.agent("acme-ops").family == "knowledge"
|
||||
assert reg.agent("acme-ops").requires_folder is False
|
||||
|
||||
|
||||
def test_installed_persona_persists_across_restart(tmp_path):
|
||||
@@ -131,7 +131,7 @@ def test_install_snapshots_independently_of_source(tmp_path):
|
||||
shutil.rmtree(src) # source gone
|
||||
reg2 = PersonaRegistry(state_path=tmp_path / "personas.json")
|
||||
assert "acme-ops" in reg2.ids() and reg2.is_enabled("acme-ops")
|
||||
assert reg2.agent("acme-ops").family == "knowledge"
|
||||
assert reg2.agent("acme-ops").requires_folder is False
|
||||
|
||||
|
||||
def test_adding_a_connector_grows_capabilities_and_forces_reconsent(tmp_path):
|
||||
|
||||
@@ -27,10 +27,9 @@ def test_parse_valid():
|
||||
m = parse_manifest(VALID)
|
||||
assert m.id == "demo" and m.name == "Demo Coworker"
|
||||
assert m.tools == ["files", "search", "shell", "todo"]
|
||||
assert m.family == "knowledge" and m.workspace == "deliverable"
|
||||
assert m.requires_folder is False and m.scheduling is True
|
||||
assert m.messaging is True and m.connectors == ("github",)
|
||||
assert m.recommended_models == ["anthropic:claude-opus-4-8"]
|
||||
assert m.needs_workspace is True
|
||||
assert m.system_prompt.startswith("You are a demo coworker")
|
||||
|
||||
|
||||
@@ -81,7 +80,7 @@ def test_to_agent_carries_traits_and_tools(tmp_path):
|
||||
from coworker.tools.todo import TodoList
|
||||
|
||||
agent = parse_manifest(VALID).to_agent()
|
||||
assert agent.name == "demo" and agent.family == "knowledge"
|
||||
assert agent.name == "demo" and agent.requires_folder is False
|
||||
assert agent.messaging and agent.connectors
|
||||
ctx = AgentContext(workspace=tmp_path, executor=object(), todo=TodoList())
|
||||
names = {getattr(t, "__name__", "") for t in agent.build_tools(ctx)}
|
||||
@@ -93,10 +92,10 @@ def test_list_field_accepts_comma_string():
|
||||
assert parse_manifest(text).tools == ["files", "search"]
|
||||
|
||||
|
||||
def test_workspace_key_is_accepted_but_derived_from_family():
|
||||
# §16 collapse: the old enum still parses (back-compat + typo detection) but behavior
|
||||
# derives from family — knowledge → scratch ("deliverable"), code → "git". A manifest
|
||||
# can no longer demand a folder gate (`project`) or opt out of a workspace (`none`).
|
||||
def test_legacy_family_key_maps_to_traits():
|
||||
# Legacy shim (workspace-scratch-design.md): pre-trait bundles declared
|
||||
# `family: code|knowledge` (plus a dead `workspace:` enum, ignored). `family: code`
|
||||
# maps to the folder-gated profile; explicit new keys always win.
|
||||
text = """---
|
||||
id: opsy
|
||||
workspace: project
|
||||
@@ -105,12 +104,18 @@ tools: [files, search, shell, todo]
|
||||
Operate things.
|
||||
"""
|
||||
m = parse_manifest(text)
|
||||
assert m.workspace == "deliverable" and m.needs_workspace is True
|
||||
assert m.requires_folder is False and m.subagents is False and m.scheduling is True
|
||||
|
||||
coded = parse_manifest(
|
||||
"---\nid: dev\nfamily: code\nworkspace: none\ntools: [git]\n---\nCode."
|
||||
)
|
||||
assert coded.workspace == "git" and coded.needs_workspace is True
|
||||
assert coded.requires_folder and coded.subagents and not coded.scheduling
|
||||
|
||||
# New keys override the shim.
|
||||
mixed = parse_manifest(
|
||||
"---\nid: dev2\nfamily: code\nrequires_folder: false\ntools: [git]\n---\nCode."
|
||||
)
|
||||
assert mixed.requires_folder is False and mixed.subagents is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -122,7 +127,6 @@ Operate things.
|
||||
("---\nid: x\ntools: [files]\n---\n", "no body"),
|
||||
("---\nid: x\ntools: [nope]\n---\nbody", "unknown tool"),
|
||||
("---\nid: x\nfamily: alien\ntools: []\n---\nbody", "family"),
|
||||
("---\nid: x\nworkspace: cloud\ntools: []\n---\nbody", "workspace"),
|
||||
(
|
||||
"---\nid: x\ndefault_permission_mode: yolo\ntools: []\n---\nbody",
|
||||
"permission",
|
||||
|
||||
@@ -137,20 +137,20 @@ def test_set_default_enables_and_persists(tmp_path):
|
||||
|
||||
def test_agent_resolution(tmp_path):
|
||||
reg = _reg(tmp_path)
|
||||
assert reg.agent("ops").family == "knowledge"
|
||||
assert reg.agent("code").family == "code"
|
||||
assert reg.agent("ops").requires_folder is False
|
||||
assert reg.agent("code").requires_folder is True
|
||||
# Unknown id → default persona.
|
||||
assert reg.agent("does-not-exist").name == reg.default_id()
|
||||
|
||||
|
||||
def test_list_all_carries_workspace_enum(tmp_path, internal):
|
||||
# Post-§16 collapse: workspace derives from family — code → git, knowledge →
|
||||
# deliverable (scratch). Ops is a scratch persona now.
|
||||
def test_list_all_carries_requires_folder(tmp_path, internal):
|
||||
# The workspace enum collapsed into the requires_folder trait
|
||||
# (workspace-scratch-design.md): Code gates a folder; scratch personas don't.
|
||||
reg = _reg(tmp_path)
|
||||
ws = {p["id"]: p["workspace"] for p in reg.list_all()}
|
||||
assert ws["code"] == "git"
|
||||
assert ws["cowork"] == "deliverable"
|
||||
assert ws["ops"] == "deliverable"
|
||||
gated = {p["id"]: p["requires_folder"] for p in reg.list_all()}
|
||||
assert gated["code"] is True
|
||||
assert gated["cowork"] is False
|
||||
assert gated["ops"] is False
|
||||
|
||||
|
||||
def test_set_unknown_persona_raises(tmp_path):
|
||||
|
||||
@@ -38,10 +38,10 @@ def test_bundles_register_as_enabled_code_builtins(tmp_path):
|
||||
for pid in BUNDLES:
|
||||
entry = reg.get(pid)
|
||||
assert entry is not None and entry.builtin
|
||||
assert entry.family == "code" # folder pick at send, like Code
|
||||
assert entry.requires_folder # folder pick at send, like Code
|
||||
assert reg.is_enabled(pid) is True # in the picker out of the box
|
||||
agent = reg.agent(pid) # catalog-expanded tools materialize
|
||||
assert agent.family == "code" and agent.needs_workspace
|
||||
assert agent.requires_folder and agent.subagents
|
||||
|
||||
|
||||
def test_bundle_skill_folders_match_their_manifests(tmp_path):
|
||||
|
||||
@@ -40,8 +40,8 @@ def test_code_agent_tools(tmp_path):
|
||||
|
||||
def test_chat_agent_has_no_workspace_tools():
|
||||
assert chat_agent().build_tools(AgentContext()) == []
|
||||
assert chat_agent().needs_workspace is False
|
||||
assert code_agent().needs_workspace is True
|
||||
assert chat_agent().requires_folder is False
|
||||
assert code_agent().requires_folder is True
|
||||
|
||||
|
||||
def test_get_agent_fallback():
|
||||
|
||||
Reference in New Issue
Block a user