multi-agent-platform 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +0 -0
- cli/action_item_escalation.py +177 -0
- cli/agent_client.py +554 -0
- cli/bridge_state.py +43 -0
- cli/commands/__init__.py +13 -0
- cli/commands/action.py +142 -0
- cli/commands/agent.py +117 -0
- cli/commands/audit.py +68 -0
- cli/commands/docs.py +179 -0
- cli/commands/experiment.py +755 -0
- cli/commands/feedback.py +106 -0
- cli/commands/notification.py +213 -0
- cli/commands/persona.py +63 -0
- cli/commands/project.py +87 -0
- cli/commands/runtime.py +105 -0
- cli/commands/topic.py +361 -0
- cli/e2e_collab.py +602 -0
- cli/git_checkpoint.py +68 -0
- cli/host_worker_types.py +151 -0
- cli/main.py +1553 -0
- cli/map_command_client.py +497 -0
- cli/participant_worker.py +255 -0
- cli/reviewer_worker.py +263 -0
- cli/runtime/__init__.py +5 -0
- cli/runtime/run_lock.py +497 -0
- cli/runtime_chat.py +317 -0
- cli/session_wake_log.py +235 -0
- cli/simple_waker.py +950 -0
- cli/table_render.py +113 -0
- cli/wake_backend.py +236 -0
- cli/worker_cycle_log.py +36 -0
- map_client/__init__.py +37 -0
- map_client/bootstrap.py +193 -0
- map_client/client.py +1045 -0
- map_client/config.py +21 -0
- map_client/errors.py +283 -0
- map_client/exceptions.py +130 -0
- map_client/plan_evidence.py +159 -0
- map_client/project_config.py +153 -0
- map_client/result_template.py +167 -0
- map_client/testing.py +27 -0
- map_mcp/__init__.py +4 -0
- map_mcp/_utils.py +28 -0
- map_mcp/auth.py +34 -0
- map_mcp/config.py +50 -0
- map_mcp/context.py +39 -0
- map_mcp/main.py +75 -0
- map_mcp/server.py +573 -0
- map_mcp/session.py +79 -0
- map_sdk/__init__.py +29 -0
- map_sdk/evidence.py +68 -0
- map_types/__init__.py +203 -0
- map_types/enums.py +199 -0
- map_types/schemas.py +1351 -0
- multi_agent_platform-0.1.0.dist-info/METADATA +298 -0
- multi_agent_platform-0.1.0.dist-info/RECORD +144 -0
- multi_agent_platform-0.1.0.dist-info/WHEEL +5 -0
- multi_agent_platform-0.1.0.dist-info/entry_points.txt +6 -0
- multi_agent_platform-0.1.0.dist-info/licenses/LICENSE +21 -0
- multi_agent_platform-0.1.0.dist-info/top_level.txt +6 -0
- server/__init__.py +0 -0
- server/__version__.py +14 -0
- server/api/__init__.py +0 -0
- server/api/action_items.py +138 -0
- server/api/agents.py +412 -0
- server/api/audit.py +54 -0
- server/api/background_tasks.py +18 -0
- server/api/common.py +117 -0
- server/api/deps.py +30 -0
- server/api/experiments.py +858 -0
- server/api/feedback.py +75 -0
- server/api/notifications.py +22 -0
- server/api/projects.py +209 -0
- server/api/router.py +25 -0
- server/api/status.py +33 -0
- server/api/topics.py +302 -0
- server/api/webhooks.py +74 -0
- server/auth/__init__.py +8 -0
- server/auth/experiment_access.py +66 -0
- server/config.py +38 -0
- server/db/__init__.py +3 -0
- server/db/base.py +5 -0
- server/db/deadlock_retry.py +146 -0
- server/db/session.py +41 -0
- server/domain/__init__.py +3 -0
- server/domain/encrypted_types.py +63 -0
- server/domain/models.py +713 -0
- server/domain/schemas.py +3 -0
- server/domain/state_machine.py +79 -0
- server/domain/topic_ack_constants.py +9 -0
- server/main.py +148 -0
- server/scripts/__init__.py +0 -0
- server/scripts/migrate_notification_unique.py +231 -0
- server/scripts/purge_audit_pollution.py +116 -0
- server/services/__init__.py +0 -0
- server/services/_lookups.py +26 -0
- server/services/acceptance_service.py +90 -0
- server/services/action_item_migration_service.py +190 -0
- server/services/action_item_service.py +200 -0
- server/services/agent_work_service.py +405 -0
- server/services/archive_lint_service.py +156 -0
- server/services/audit_service.py +457 -0
- server/services/auth.py +66 -0
- server/services/comment_service.py +173 -0
- server/services/errors.py +65 -0
- server/services/escalation_resolver.py +248 -0
- server/services/evidence_service.py +88 -0
- server/services/experiment_capabilities_service.py +277 -0
- server/services/inbound_event_service.py +111 -0
- server/services/lock_service.py +273 -0
- server/services/log_service.py +202 -0
- server/services/mention_service.py +730 -0
- server/services/notification_service.py +939 -0
- server/services/notification_stream.py +138 -0
- server/services/permissions.py +147 -0
- server/services/persona_activity_service.py +108 -0
- server/services/phase_owner_resolver.py +95 -0
- server/services/phase_service.py +381 -0
- server/services/plan_marker_service.py +235 -0
- server/services/plan_service.py +186 -0
- server/services/platform_feedback_service.py +114 -0
- server/services/project_service.py +534 -0
- server/services/project_status_service.py +132 -0
- server/services/review_service.py +707 -0
- server/services/secret_encryption.py +97 -0
- server/services/similarity_service.py +119 -0
- server/services/sse_event_schemas.py +17 -0
- server/services/status_service.py +68 -0
- server/services/template_service.py +134 -0
- server/services/text_utils.py +19 -0
- server/services/thread_activity.py +180 -0
- server/services/todo_persona_filter.py +73 -0
- server/services/todo_service.py +604 -0
- server/services/topic_ack_service.py +312 -0
- server/services/topic_action_item_ops.py +538 -0
- server/services/topic_comment_kind.py +14 -0
- server/services/topic_comment_service.py +237 -0
- server/services/topic_helpers.py +32 -0
- server/services/topic_lifecycle_service.py +478 -0
- server/services/topic_progress_service.py +40 -0
- server/services/topic_resolve_service.py +234 -0
- server/services/topic_service.py +102 -0
- server/services/topic_work_item_service.py +570 -0
- server/services/webhook_service.py +273 -0
cli/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Action-item escalation timeline (experiment B, plan §3 / I4).
|
|
2
|
+
|
|
3
|
+
纯决策模块:根据 action_item 的 ``first_open_at`` / ``last_woken_at`` /
|
|
4
|
+
``wake_count`` / ``stale_at`` 字段判断当前时刻应该 WAKE / STALE / SKIP。
|
|
5
|
+
无 I/O,无时钟,``now`` 由调用方注入以便单元测试钉住时间。
|
|
6
|
+
|
|
7
|
+
阈值常量镜像 ``server.services.action_item_service``(
|
|
8
|
+
``WAKE_STAGE_THRESHOLDS`` / ``WAKE_REPEAT_INTERVAL_DAYS`` /
|
|
9
|
+
``WAKE_MAX_COUNT_BEFORE_STALE``)。两侧需保持一致——
|
|
10
|
+
``test_threshold_constants_match_plan_section_three`` 是这条不变式的 lint。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from datetime import UTC, datetime, timedelta
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ActionItemWakeDecision(str, Enum):
|
|
22
|
+
"""Outcome of ``should_wake_action_item`` for one action_item at ``now``."""
|
|
23
|
+
|
|
24
|
+
WAKE = "wake"
|
|
25
|
+
STALE = "stale"
|
|
26
|
+
SKIP = "skip"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Mirrored from server.services.action_item_service. Importing would force
|
|
30
|
+
# the waker CLI to depend on the server-side stack (SQLAlchemy models +
|
|
31
|
+
# FastAPI deps), which violates the waker's no-FastAPI invariant. Kept
|
|
32
|
+
# short and obvious so a reviewer can spot drift.
|
|
33
|
+
_WAKE_STAGE_HOURS: tuple[tuple[int, int], ...] = (
|
|
34
|
+
(1, 24), # wake_count_after_increment=1 → first wake at T+24h
|
|
35
|
+
(2, 72), # wake_count_after_increment=2 → second wake at T+72h
|
|
36
|
+
)
|
|
37
|
+
_WAKE_REPEAT_DAYS = 7 # after the 72h wake, fire every 7d
|
|
38
|
+
_WAKE_MAX_BEFORE_STALE = 4 # 4th unanswered wake → stale
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_iso_datetime(value: Any) -> datetime | None:
|
|
42
|
+
"""Parse an ISO-8601 string into an aware UTC datetime.
|
|
43
|
+
|
|
44
|
+
Defensive about the variety of shapes the SDK / API may emit
|
|
45
|
+
(``...Z`` vs ``...+00:00`` vs naive ISO). Returns ``None`` if the value
|
|
46
|
+
is missing or unparseable — callers treat ``None`` as "skip".
|
|
47
|
+
"""
|
|
48
|
+
if value is None or value == "":
|
|
49
|
+
return None
|
|
50
|
+
if isinstance(value, datetime):
|
|
51
|
+
return value if value.tzinfo else value.replace(tzinfo=UTC)
|
|
52
|
+
try:
|
|
53
|
+
parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
54
|
+
except ValueError:
|
|
55
|
+
return None
|
|
56
|
+
return parsed if parsed.tzinfo else parsed.replace(tzinfo=UTC)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def should_wake_action_item(
|
|
60
|
+
item: dict[str, Any],
|
|
61
|
+
*,
|
|
62
|
+
now: datetime | None = None,
|
|
63
|
+
) -> ActionItemWakeDecision:
|
|
64
|
+
"""Decide the escalation action for one open action_item at ``now``.
|
|
65
|
+
|
|
66
|
+
Pure function (no I/O, no clock). Returns ``WAKE`` when the waker
|
|
67
|
+
should bump ``wake_count`` and emit a wake event, ``STALE`` when the
|
|
68
|
+
4th wake has already fired and the assignee still hasn't responded
|
|
69
|
+
(we mark stale + stop waking), or ``SKIP`` when the escalation
|
|
70
|
+
timeline does not yet call for an action.
|
|
71
|
+
|
|
72
|
+
Rules (plan §3, mirrored from ``server.services.action_item_service``):
|
|
73
|
+
|
|
74
|
+
- Only open items are eligible; closed items (done / cancelled) and
|
|
75
|
+
unassigned items (no ``owner_agent_id``) are skipped.
|
|
76
|
+
- Stale items (``stale_at`` set) are skipped forever — once we've
|
|
77
|
+
written the diagnostic audit row we stop bothering the assignee.
|
|
78
|
+
- First wake fires 24h after ``first_open_at``; second at 72h; further
|
|
79
|
+
wakes every 7d; the (N+1)th check after the 4th wake writes stale.
|
|
80
|
+
- ``now`` defaults to ``datetime.now(UTC)`` — callers that need a
|
|
81
|
+
deterministic clock (tests, dogfood) inject their own.
|
|
82
|
+
"""
|
|
83
|
+
if not isinstance(item, dict):
|
|
84
|
+
return ActionItemWakeDecision.SKIP
|
|
85
|
+
if item.get("status") != "open":
|
|
86
|
+
return ActionItemWakeDecision.SKIP
|
|
87
|
+
if not item.get("owner_agent_id"):
|
|
88
|
+
# plan §3 「仅 assignee」 — unassigned items must not be woken by
|
|
89
|
+
# anyone, the waker has no addressee.
|
|
90
|
+
return ActionItemWakeDecision.SKIP
|
|
91
|
+
if _parse_iso_datetime(item.get("stale_at")) is not None:
|
|
92
|
+
return ActionItemWakeDecision.SKIP
|
|
93
|
+
|
|
94
|
+
current = now or datetime.now(UTC)
|
|
95
|
+
first_open_at = _parse_iso_datetime(item.get("first_open_at"))
|
|
96
|
+
if first_open_at is None:
|
|
97
|
+
# Pre-I1 backfill may have missed this row (closed before I1, etc).
|
|
98
|
+
# Conservatively skip — re-running the backfill is the remediation.
|
|
99
|
+
return ActionItemWakeDecision.SKIP
|
|
100
|
+
last_woken_at = _parse_iso_datetime(item.get("last_woken_at"))
|
|
101
|
+
wake_count = int(item.get("wake_count") or 0)
|
|
102
|
+
|
|
103
|
+
elapsed = current - first_open_at
|
|
104
|
+
|
|
105
|
+
# Stage 1 & 2: hard thresholds keyed to first_open_at.
|
|
106
|
+
for target_count, min_hours in _WAKE_STAGE_HOURS:
|
|
107
|
+
if wake_count + 1 == target_count and elapsed >= timedelta(hours=min_hours):
|
|
108
|
+
return ActionItemWakeDecision.WAKE
|
|
109
|
+
|
|
110
|
+
# Stages 3+: every 7d after the last wake.
|
|
111
|
+
if (
|
|
112
|
+
2 <= wake_count < _WAKE_MAX_BEFORE_STALE
|
|
113
|
+
and last_woken_at is not None
|
|
114
|
+
and current - last_woken_at >= timedelta(days=_WAKE_REPEAT_DAYS)
|
|
115
|
+
):
|
|
116
|
+
return ActionItemWakeDecision.WAKE
|
|
117
|
+
|
|
118
|
+
# Past the 4th unanswered wake: write stale.
|
|
119
|
+
if (
|
|
120
|
+
wake_count >= _WAKE_MAX_BEFORE_STALE
|
|
121
|
+
and last_woken_at is not None
|
|
122
|
+
and current - last_woken_at >= timedelta(days=_WAKE_REPEAT_DAYS)
|
|
123
|
+
):
|
|
124
|
+
return ActionItemWakeDecision.STALE
|
|
125
|
+
|
|
126
|
+
return ActionItemWakeDecision.SKIP
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def scan_pending_action_items(
|
|
130
|
+
action_items: list[dict[str, Any]],
|
|
131
|
+
*,
|
|
132
|
+
persona_agent_id: str | None,
|
|
133
|
+
now: datetime | None = None,
|
|
134
|
+
) -> list[tuple[str, ActionItemWakeDecision]]:
|
|
135
|
+
"""Filter the ``action_items`` todo payload through ``should_wake_action_item``.
|
|
136
|
+
|
|
137
|
+
Returns a list of ``(action_item_id, decision)`` pairs. Only items
|
|
138
|
+
owned by ``persona_agent_id`` are considered — the waker must never
|
|
139
|
+
wake another agent's action_item even if it shows up in the
|
|
140
|
+
cross-project listing. Items whose owner doesn't match are skipped
|
|
141
|
+
silently (not raised) — the per-persona todos feed already scopes
|
|
142
|
+
this but we re-check defensively against potential payload leakage.
|
|
143
|
+
"""
|
|
144
|
+
decisions: list[tuple[str, ActionItemWakeDecision]] = []
|
|
145
|
+
for item in action_items or []:
|
|
146
|
+
if not isinstance(item, dict):
|
|
147
|
+
continue
|
|
148
|
+
item_id = str(item.get("id") or "")
|
|
149
|
+
if not item_id:
|
|
150
|
+
continue
|
|
151
|
+
if persona_agent_id and str(item.get("owner_agent_id") or "") != str(persona_agent_id):
|
|
152
|
+
continue
|
|
153
|
+
decisions.append((item_id, should_wake_action_item(item, now=now)))
|
|
154
|
+
return decisions
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class EscalationDecisionCounts:
|
|
159
|
+
"""Aggregated counts of WAKE / STALE / SKIP for one scan."""
|
|
160
|
+
|
|
161
|
+
wake: int = 0
|
|
162
|
+
stale: int = 0
|
|
163
|
+
skip: int = 0
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def from_decisions(
|
|
167
|
+
cls, decisions: list[tuple[str, ActionItemWakeDecision]]
|
|
168
|
+
) -> EscalationDecisionCounts:
|
|
169
|
+
wake = stale = skip = 0
|
|
170
|
+
for _, decision in decisions:
|
|
171
|
+
if decision is ActionItemWakeDecision.WAKE:
|
|
172
|
+
wake += 1
|
|
173
|
+
elif decision is ActionItemWakeDecision.STALE:
|
|
174
|
+
stale += 1
|
|
175
|
+
else:
|
|
176
|
+
skip += 1
|
|
177
|
+
return cls(wake=wake, stale=stale, skip=skip)
|