Skip to content

Commit 3c42260

Browse files
committed
Refine actor modals and help prompt delivery
1 parent 320a913 commit 3c42260

50 files changed

Lines changed: 3128 additions & 1036 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/guide/best-practices.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ You can show different content to different roles using conditional markers:
7373
- `## @role: foreman` - Only foreman sees this section
7474
- `## @role: peer` - Only peers see this section
7575
- `## @actor: <actor_id>` - Only the specific actor sees this section
76-
- Sections without markers are visible to all roles
76+
- Sections without markers are the shared/common content visible to everyone
77+
78+
In the Web structured Help editor, these untagged sections are surfaced as `Common Notes`, while the tagged blocks are surfaced as role/actor notes.
7779

7880
#### How Agents Consume Help
7981

src/cccc/daemon/messaging/delivery.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030

3131
logger = logging.getLogger("cccc.delivery")
3232

33+
from ...contracts.v1 import SystemNotifyData
3334
from ...kernel.actors import find_actor, list_actors
3435
from ...kernel.group import Group, get_group_state, set_group_state
3536
from ...kernel.inbox import is_message_for_actor, set_cursor
37+
from ...kernel.ledger import append_event
3638
from ...kernel.system_prompt import render_system_prompt
3739
from ...paths import ensure_home
3840
from ...runners import pty as pty_runner
@@ -748,6 +750,65 @@ def queue_system_notify(
748750
)
749751

750752

753+
def emit_system_notify(
754+
group: Group,
755+
*,
756+
by: str,
757+
notify: SystemNotifyData,
758+
) -> Dict[str, Any]:
759+
"""Append a system.notify event and dispatch it to running PTY targets."""
760+
event = append_event(
761+
group.ledger_path,
762+
kind="system.notify",
763+
group_id=group.group_id,
764+
scope_key="",
765+
by=str(by or "system").strip() or "system",
766+
data=notify.model_dump(),
767+
)
768+
769+
target_actor_id = str(notify.target_actor_id or "").strip()
770+
if target_actor_id:
771+
target_actor_ids = [target_actor_id]
772+
else:
773+
target_actor_ids: List[str] = []
774+
seen: set[str] = set()
775+
for actor in list_actors(group):
776+
if not isinstance(actor, dict):
777+
continue
778+
aid = str(actor.get("id") or "").strip()
779+
if not aid or aid == "user" or aid in seen:
780+
continue
781+
seen.add(aid)
782+
target_actor_ids.append(aid)
783+
784+
event_id = str(event.get("id") or "").strip()
785+
event_ts = str(event.get("ts") or "").strip()
786+
if not event_id:
787+
return event
788+
789+
for aid in target_actor_ids:
790+
actor = find_actor(group, aid)
791+
if not isinstance(actor, dict):
792+
continue
793+
runner_kind = str(actor.get("runner") or "pty").strip()
794+
if runner_kind != "pty":
795+
continue
796+
if not pty_runner.SUPERVISOR.actor_running(group.group_id, aid):
797+
continue
798+
queue_system_notify(
799+
group,
800+
actor_id=aid,
801+
event_id=event_id,
802+
notify_kind=str(notify.kind),
803+
title=str(notify.title),
804+
message=str(notify.message),
805+
ts=event_ts,
806+
)
807+
flush_pending_messages(group, actor_id=aid)
808+
809+
return event
810+
811+
751812
def flush_pending_messages(group: Group, *, actor_id: str) -> bool:
752813
"""Flush pending messages for an actor if ready.
753814

src/cccc/daemon/messaging/system_notify_ops.py

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
from typing import Any, Callable, Dict, Optional
66

7-
from ...contracts.v1 import DaemonError, DaemonResponse
8-
from ...kernel.actors import list_actors
7+
from ...contracts.v1 import DaemonError, DaemonResponse, SystemNotifyData
98
from ...kernel.group import load_group
109
from ...kernel.inbox import find_event
1110
from ...kernel.ledger import append_event
11+
from .delivery import emit_system_notify
1212

1313

1414
def _error(code: str, message: str, *, details: Optional[Dict[str, Any]] = None) -> DaemonResponse:
@@ -19,7 +19,6 @@ def handle_system_notify(
1919
args: Dict[str, Any],
2020
*,
2121
coerce_bool: Callable[[Any], bool],
22-
queue_system_notify: Callable[..., None],
2322
) -> DaemonResponse:
2423
group_id = str(args.get("group_id") or "").strip()
2524
by = str(args.get("by") or "system").strip()
@@ -44,47 +43,16 @@ def handle_system_notify(
4443
if priority not in valid_priorities:
4544
priority = "normal"
4645

47-
event = append_event(
48-
group.ledger_path,
49-
kind="system.notify",
50-
group_id=group.group_id,
51-
scope_key="",
52-
by=by,
53-
data={
54-
"kind": kind,
55-
"priority": priority,
56-
"title": title,
57-
"message": message,
58-
"target_actor_id": target_actor_id,
59-
"requires_ack": requires_ack,
60-
"context": context,
61-
},
46+
notify = SystemNotifyData(
47+
kind=kind,
48+
priority=priority,
49+
title=title,
50+
message=message,
51+
target_actor_id=target_actor_id,
52+
requires_ack=requires_ack,
53+
context=context,
6254
)
63-
64-
if priority in ("high", "urgent"):
65-
event_id = str(event.get("id") or "").strip()
66-
event_ts = str(event.get("ts") or "").strip()
67-
for actor in list_actors(group):
68-
if not isinstance(actor, dict):
69-
continue
70-
aid = str(actor.get("id") or "").strip()
71-
if not aid or aid == "user":
72-
continue
73-
if target_actor_id and aid != target_actor_id:
74-
continue
75-
runner_kind = str(actor.get("runner") or "pty").strip()
76-
if runner_kind != "pty":
77-
continue
78-
queue_system_notify(
79-
group,
80-
actor_id=aid,
81-
event_id=event_id,
82-
notify_kind=kind,
83-
title=title,
84-
message=message,
85-
ts=event_ts,
86-
)
87-
55+
event = emit_system_notify(group, by=by, notify=notify)
8856
return DaemonResponse(ok=True, result={"event": event})
8957

9058

@@ -131,10 +99,9 @@ def try_handle_system_notify_op(
13199
args: Dict[str, Any],
132100
*,
133101
coerce_bool: Callable[[Any], bool],
134-
queue_system_notify: Callable[..., None],
135102
) -> Optional[DaemonResponse]:
136103
if op == "system_notify":
137-
return handle_system_notify(args, coerce_bool=coerce_bool, queue_system_notify=queue_system_notify)
104+
return handle_system_notify(args, coerce_bool=coerce_bool)
138105
if op == "notify_ack":
139106
return handle_notify_ack(args)
140107
return None

src/cccc/daemon/request_dispatch_ops.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ class RequestDispatchDeps:
9191
auto_wake_recipients: Callable[..., None]
9292
automation_on_new_message: Callable[[Any], None]
9393
clear_pending_system_notifies_chat: Callable[[str, set[str]], None]
94-
queue_system_notify: Callable[..., None]
9594
error_factory: Callable[[str, str], DaemonResponse]
9695

9796

@@ -365,7 +364,6 @@ def dispatch_request(
365364
op,
366365
args,
367366
coerce_bool=deps.coerce_bool_default_false,
368-
queue_system_notify=deps.queue_system_notify,
369367
)
370368
if system_notify_resp is not None:
371369
return system_notify_resp, False

src/cccc/daemon/server.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@
7979
pty_submit_text,
8080
render_delivery_text,
8181
deliver_message_with_preamble,
82-
queue_system_notify,
8382
flush_pending_messages,
8483
tick_delivery,
8584
clear_preamble_sent,
@@ -292,6 +291,7 @@ def _inject_actor_context_env(env: Dict[str, Any], *, group_id: str, actor_id: s
292291
This is runtime-only (not persisted to group docs).
293292
"""
294293
out: Dict[str, Any] = dict(env or {})
294+
out["CCCC_HOME"] = str(ensure_home())
295295
out["CCCC_GROUP_ID"] = str(group_id or "").strip()
296296
out["CCCC_ACTOR_ID"] = str(actor_id or "").strip()
297297
return out
@@ -730,7 +730,6 @@ def _request_dispatch_deps() -> RequestDispatchDeps:
730730
group_id,
731731
notify_kinds=notify_kinds,
732732
),
733-
queue_system_notify=queue_system_notify,
734733
error_factory=_error,
735734
)
736735
return _REQUEST_DISPATCH_DEPS

src/cccc/daemon/space/group_space_ops.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import threading
1313
from typing import Any, Deque, Dict, Optional, Tuple
1414

15-
from ...contracts.v1 import DaemonError, DaemonResponse, SpaceBinding, SpaceLane
15+
from ...contracts.v1 import DaemonError, DaemonResponse, SpaceBinding, SpaceLane, SystemNotifyData
1616
from ...kernel.group import load_group
1717
from ...kernel.ledger import append_event
1818
from ...kernel.permissions import require_group_permission
@@ -32,6 +32,7 @@
3232
)
3333
from ...providers.notebooklm.errors import NotebookLMProviderError
3434
from ...providers.notebooklm.health import notebooklm_health_check, parse_notebooklm_auth_json
35+
from ..messaging.delivery import emit_system_notify
3536
from .notebooklm_auth_flow import (
3637
cancel_notebooklm_auth_flow,
3738
get_notebooklm_auth_flow_status,
@@ -764,22 +765,16 @@ def _emit_artifact_async_notify(
764765
"output_path": str(output_path or ""),
765766
"error": {"code": str(error_code or ""), "message": str(error_message or "")},
766767
}
767-
_ = append_event(
768-
group.ledger_path,
769-
kind="system.notify",
770-
group_id=group.group_id,
771-
scope_key="",
772-
by="system",
773-
data={
774-
"kind": ("info" if ok else "error"),
775-
"priority": ("normal" if ok else "high"),
776-
"title": title,
777-
"message": message,
778-
"target_actor_id": _notify_target_from_by(by),
779-
"requires_ack": False,
780-
"context": context,
781-
},
768+
notify = SystemNotifyData(
769+
kind=("info" if ok else "error"),
770+
priority=("normal" if ok else "high"),
771+
title=title,
772+
message=message,
773+
target_actor_id=_notify_target_from_by(by),
774+
requires_ack=False,
775+
context=context,
782776
)
777+
emit_system_notify(group, by="system", notify=notify)
783778
except Exception as e:
784779
_LOG.warning("group-space async artifact notify failed group=%s: %s", group_id, e)
785780

src/cccc/daemon/space/group_space_sync.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
from typing import Any, Dict, Iterable, List, Optional, Tuple
99
from urllib.parse import urlparse
1010

11+
from ...contracts.v1 import SystemNotifyData
1112
from ...kernel.actors import find_actor, find_foreman
1213
from ...kernel.group import load_group
1314
from ...kernel.ledger import append_event
1415
from ...util.fs import atomic_write_json, read_json
16+
from ..messaging.delivery import emit_system_notify
1517
from .group_space_paths import (
1618
resolve_space_root_from_group,
1719
resolve_space_root,
@@ -816,22 +818,16 @@ def _emit_sync_notification(
816818
}
817819
for actor_id in targets:
818820
try:
819-
append_event(
820-
group.ledger_path,
821-
kind="system.notify",
822-
group_id=str(group.group_id),
823-
scope_key="",
824-
by=notify_by,
825-
data={
826-
"kind": kind,
827-
"priority": priority,
828-
"title": title,
829-
"message": message,
830-
"target_actor_id": actor_id,
831-
"requires_ack": False,
832-
"context": context,
833-
},
821+
notify = SystemNotifyData(
822+
kind=kind,
823+
priority=priority,
824+
title=title,
825+
message=message,
826+
target_actor_id=actor_id,
827+
requires_ack=False,
828+
context=context,
834829
)
830+
emit_system_notify(group, by=notify_by, notify=notify)
835831
except Exception:
836832
continue
837833

0 commit comments

Comments
 (0)