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/commands/feedback.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""``map feedback ...`` sub-app — arch experiment 0519e2a3 PR7.
|
|
2
|
+
|
|
3
|
+
Platform feedback inbox. ``submit`` is persona-scoped; ``list`` / ``get`` /
|
|
4
|
+
``update`` are admin-only (used for triage).
|
|
5
|
+
|
|
6
|
+
All command bodies lazy-import ``cli.main._run`` to break the
|
|
7
|
+
``cli.main ↔ cli.commands.*`` import cycle.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from map_client.client import MAPClient
|
|
16
|
+
|
|
17
|
+
feedback_app = typer.Typer(help="Platform feedback inbox commands")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@feedback_app.command("submit")
|
|
21
|
+
def feedback_submit(
|
|
22
|
+
body: str | None = typer.Option(None, "--body", help="Feedback text (free-form)."),
|
|
23
|
+
body_file: Path | None = typer.Option(
|
|
24
|
+
None,
|
|
25
|
+
"--file",
|
|
26
|
+
help="Read feedback body from a file (avoids shell-quoting issues with backticks / $vars).",
|
|
27
|
+
),
|
|
28
|
+
category: str | None = typer.Option(
|
|
29
|
+
None, "--category", help="bug|suggestion|question|other (optional, admin triage hint)"
|
|
30
|
+
),
|
|
31
|
+
project: uuid.UUID | None = typer.Option(
|
|
32
|
+
None, "--project", help="Source project context (optional)"
|
|
33
|
+
),
|
|
34
|
+
) -> None:
|
|
35
|
+
from map_types.enums import FeedbackCategory
|
|
36
|
+
|
|
37
|
+
from cli.main import _read_text_file, _run # lazy: avoid cli.main ↔ cli.commands.* cycle
|
|
38
|
+
from server.domain.schemas import PlatformFeedbackCreate
|
|
39
|
+
|
|
40
|
+
if body is None and body_file is None:
|
|
41
|
+
typer.echo("Error: either --body or --file is required", err=True)
|
|
42
|
+
raise typer.Exit(2)
|
|
43
|
+
if body is not None and body_file is not None:
|
|
44
|
+
typer.echo("Error: use only one of --body or --file", err=True)
|
|
45
|
+
raise typer.Exit(2)
|
|
46
|
+
content = body if body is not None else _read_text_file(body_file, kind="feedback")
|
|
47
|
+
payload = PlatformFeedbackCreate(
|
|
48
|
+
body=content,
|
|
49
|
+
project_id=project,
|
|
50
|
+
category=FeedbackCategory(category) if category else None,
|
|
51
|
+
)
|
|
52
|
+
_run(lambda c: c.submit_feedback(payload))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@feedback_app.command("list")
|
|
56
|
+
def feedback_list(
|
|
57
|
+
status: str | None = typer.Option(None, "--status"),
|
|
58
|
+
category: str | None = typer.Option(None, "--category"),
|
|
59
|
+
project: uuid.UUID | None = typer.Option(None, "--project"),
|
|
60
|
+
page: int = typer.Option(1, "--page", min=1),
|
|
61
|
+
page_size: int = typer.Option(50, "--page-size", min=1, max=200),
|
|
62
|
+
include_archived: bool = typer.Option(False, "--include-archived"),
|
|
63
|
+
) -> None:
|
|
64
|
+
from map_types.enums import FeedbackCategory, FeedbackStatus
|
|
65
|
+
|
|
66
|
+
from cli.main import _run # lazy
|
|
67
|
+
|
|
68
|
+
def action(c: MAPClient):
|
|
69
|
+
items, total = c.list_feedback_page(
|
|
70
|
+
status=FeedbackStatus(status) if status else None,
|
|
71
|
+
category=FeedbackCategory(category) if category else None,
|
|
72
|
+
project_id=project,
|
|
73
|
+
page=page,
|
|
74
|
+
page_size=page_size,
|
|
75
|
+
include_archived=include_archived,
|
|
76
|
+
)
|
|
77
|
+
return {"items": items, "total": total}
|
|
78
|
+
|
|
79
|
+
_run(action, admin=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@feedback_app.command("get")
|
|
83
|
+
def feedback_get(feedback_id: uuid.UUID = typer.Argument(..., help="Feedback UUID")) -> None:
|
|
84
|
+
from cli.main import _run # lazy
|
|
85
|
+
|
|
86
|
+
_run(lambda c: c.get_feedback(feedback_id), admin=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@feedback_app.command("update")
|
|
90
|
+
def feedback_update(
|
|
91
|
+
feedback_id: uuid.UUID = typer.Argument(..., help="Feedback UUID"),
|
|
92
|
+
status: str | None = typer.Option(None, "--status"),
|
|
93
|
+
category: str | None = typer.Option(None, "--category"),
|
|
94
|
+
archived: bool | None = typer.Option(None, "--archived/--no-archived"),
|
|
95
|
+
) -> None:
|
|
96
|
+
from map_types.enums import FeedbackCategory, FeedbackStatus
|
|
97
|
+
|
|
98
|
+
from cli.main import _run # lazy
|
|
99
|
+
from server.domain.schemas import PlatformFeedbackUpdate
|
|
100
|
+
|
|
101
|
+
payload = PlatformFeedbackUpdate(
|
|
102
|
+
status=FeedbackStatus(status) if status else None,
|
|
103
|
+
category=FeedbackCategory(category) if category else None,
|
|
104
|
+
archived=archived,
|
|
105
|
+
)
|
|
106
|
+
_run(lambda c: c.update_feedback(feedback_id, payload), admin=True)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""``map notification ...`` + ``map inbound-event ...`` sub-apps — arch PR5.
|
|
2
|
+
|
|
3
|
+
Wraps the personal-inbox / waker-event surface:
|
|
4
|
+
|
|
5
|
+
* ``map notification list`` —
|
|
6
|
+
``GET /api/v1/notifications`` with category/target_type/unread_only filters.
|
|
7
|
+
* ``map notification read`` —
|
|
8
|
+
``POST /notifications/{id}/read`` (one notification at a time).
|
|
9
|
+
* ``map notification read-all`` (cli-ux PR2: + ``--category`` + ``--event``) —
|
|
10
|
+
``POST /notifications/read-all`` for the unfiltered fast path, OR enumerate
|
|
11
|
+
via ``list`` + ``mark_notification_read`` for filtered bulk. Mirrors the
|
|
12
|
+
wakeable / digest view surfaced in the web todo UI.
|
|
13
|
+
* ``map inbound-event record`` —
|
|
14
|
+
``POST /api/v1/agents/me/inbound-events``. Waker-side D6 gate;
|
|
15
|
+
on 409 the CLI exits non-zero so the waker treats it as
|
|
16
|
+
"already woken" and skips resume.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import uuid
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import typer
|
|
26
|
+
from map_client.client import MAPClient
|
|
27
|
+
from map_client.exceptions import MAPConflictError
|
|
28
|
+
|
|
29
|
+
from cli.table_render import enum_value, format_datetime, render_table, short_uuid, truncate
|
|
30
|
+
|
|
31
|
+
notification_app = typer.Typer(help="Notification commands (personal inbox)")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _render_notification_table(result: Any) -> str:
|
|
35
|
+
"""Render a NotificationListRead as a compact table with summary footer."""
|
|
36
|
+
items = result.items if hasattr(result, "items") else result
|
|
37
|
+
total = getattr(result, "total", len(items))
|
|
38
|
+
unread = getattr(result, "unread_count", 0)
|
|
39
|
+
|
|
40
|
+
headers = ["ID", "Event", "Summary", "Category", "Read", "Created"]
|
|
41
|
+
rows = []
|
|
42
|
+
for n in items:
|
|
43
|
+
rows.append([
|
|
44
|
+
short_uuid(n.id),
|
|
45
|
+
truncate(n.event, 30),
|
|
46
|
+
truncate(n.summary, 50),
|
|
47
|
+
enum_value(n.category),
|
|
48
|
+
"yes" if n.read_at else "no",
|
|
49
|
+
format_datetime(n.created_at),
|
|
50
|
+
])
|
|
51
|
+
table = render_table(headers, rows)
|
|
52
|
+
footer = f"\n({len(items)} shown, {unread} unread / {total} total)"
|
|
53
|
+
return table + footer
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@notification_app.command("list")
|
|
57
|
+
def notification_list(
|
|
58
|
+
unread_only: bool = typer.Option(False, "--unread-only", help="Only show unread."),
|
|
59
|
+
category: str | None = typer.Option(
|
|
60
|
+
None, "--category", help="wakeable | digest | all (filter by category)."
|
|
61
|
+
),
|
|
62
|
+
target_type: str | None = typer.Option(
|
|
63
|
+
None, "--target-type", help="Filter by notification.target_type."
|
|
64
|
+
),
|
|
65
|
+
limit: int = typer.Option(50, "--limit", min=1, max=200),
|
|
66
|
+
offset: int = typer.Option(0, "--offset", min=0),
|
|
67
|
+
) -> None:
|
|
68
|
+
"""List the current persona's notifications.
|
|
69
|
+
|
|
70
|
+
Defaults to a compact table view. Use ``--format yaml`` or
|
|
71
|
+
``--format json`` for full structured output (scripts / piping).
|
|
72
|
+
"""
|
|
73
|
+
from cli.main import _run # lazy: avoid cli.main ↔ cli.commands.* cycle
|
|
74
|
+
|
|
75
|
+
def action(c: Any) -> Any:
|
|
76
|
+
return c.list_notifications(
|
|
77
|
+
unread_only=unread_only,
|
|
78
|
+
category=category,
|
|
79
|
+
target_type=target_type,
|
|
80
|
+
limit=limit,
|
|
81
|
+
offset=offset,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
_run(action, table_renderer=_render_notification_table)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@notification_app.command("read")
|
|
88
|
+
def notification_read(
|
|
89
|
+
notification_id: uuid.UUID = typer.Option(..., "--id", help="Notification UUID."),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Mark one notification as read."""
|
|
92
|
+
from cli.main import _run
|
|
93
|
+
|
|
94
|
+
def action(c: Any) -> Any:
|
|
95
|
+
return c.mark_notification_read(notification_id)
|
|
96
|
+
|
|
97
|
+
_run(action)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@notification_app.command("read-all")
|
|
101
|
+
def notification_read_all(
|
|
102
|
+
category: str | None = typer.Option(
|
|
103
|
+
None,
|
|
104
|
+
"--category",
|
|
105
|
+
help="Filter by category before bulk-marking: wakeable | digest | all (default = all).",
|
|
106
|
+
),
|
|
107
|
+
event: str | None = typer.Option(
|
|
108
|
+
None,
|
|
109
|
+
"--event",
|
|
110
|
+
help="Filter by notification.event before bulk-marking "
|
|
111
|
+
"(client-side filter; e.g. mention.created, experiment.phase_changed).",
|
|
112
|
+
),
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Mark every (filtered) notification for the current persona as read.
|
|
115
|
+
|
|
116
|
+
Without filters this hits the bulk ``POST /agents/me/notifications/read-all``
|
|
117
|
+
endpoint in a single round-trip. With ``--category`` or ``--event`` set, the
|
|
118
|
+
CLI enumerates the matching subset via ``list_notifications`` then marks each
|
|
119
|
+
via ``mark_notification_read`` (still fewer round-trips than ``read --id``
|
|
120
|
+
one-at-a-time, and doesn't require knowing IDs upfront).
|
|
121
|
+
"""
|
|
122
|
+
from map_types.enums import NotificationCategory
|
|
123
|
+
|
|
124
|
+
from cli.main import _run
|
|
125
|
+
|
|
126
|
+
# Validate --category early so user sees a clean error before any API call.
|
|
127
|
+
if category is not None and category not in ("wakeable", "digest", "all"):
|
|
128
|
+
typer.echo(
|
|
129
|
+
f"Error: --category must be one of wakeable|digest|all (got {category!r})",
|
|
130
|
+
err=True,
|
|
131
|
+
)
|
|
132
|
+
raise typer.Exit(2)
|
|
133
|
+
|
|
134
|
+
def action(c: Any) -> Any:
|
|
135
|
+
if category is None and event is None:
|
|
136
|
+
# Fast path: single bulk endpoint, no enumeration.
|
|
137
|
+
return c.mark_all_notifications_read()
|
|
138
|
+
|
|
139
|
+
# Filtered path: enumerate, then mark each match.
|
|
140
|
+
list_kwargs: dict = {"unread_only": True, "limit": 200}
|
|
141
|
+
if category is not None and category != "all":
|
|
142
|
+
list_kwargs["category"] = NotificationCategory(category)
|
|
143
|
+
# Pagination loop — most personas have < 200 unread, but loop if more.
|
|
144
|
+
marked = 0
|
|
145
|
+
offset = 0
|
|
146
|
+
while True:
|
|
147
|
+
list_kwargs["offset"] = offset
|
|
148
|
+
page = c.list_notifications(**list_kwargs)
|
|
149
|
+
if not page.items:
|
|
150
|
+
break
|
|
151
|
+
for n in page.items:
|
|
152
|
+
if event is not None and getattr(n, "event", None) != event:
|
|
153
|
+
continue
|
|
154
|
+
c.mark_notification_read(n.id)
|
|
155
|
+
marked += 1
|
|
156
|
+
offset += len(page.items)
|
|
157
|
+
if offset >= page.total:
|
|
158
|
+
break
|
|
159
|
+
return {"marked": marked}
|
|
160
|
+
|
|
161
|
+
_run(action)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
inbound_event_app = typer.Typer(
|
|
165
|
+
help="Runtime-waker inbound event commands (D6 server gate)"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@inbound_event_app.command("record")
|
|
170
|
+
def inbound_event_record(
|
|
171
|
+
event_id: uuid.UUID = typer.Option(
|
|
172
|
+
..., "--event-id", help="Upstream notification id (UUID)."
|
|
173
|
+
),
|
|
174
|
+
fingerprint: str = typer.Option(
|
|
175
|
+
..., "--fingerprint", help="Dedup key (server enforces UNIQUE per agent)."
|
|
176
|
+
),
|
|
177
|
+
event_type: str = typer.Option(
|
|
178
|
+
...,
|
|
179
|
+
"--event-type",
|
|
180
|
+
help="Logical event type (e.g. mention, pending_review, topic_lifecycle).",
|
|
181
|
+
),
|
|
182
|
+
source: str = typer.Option(
|
|
183
|
+
"polling", "--source", help="polling | sse | replay (Phase 1 = polling)."
|
|
184
|
+
),
|
|
185
|
+
payload_file: str | None = typer.Option(
|
|
186
|
+
None, "--payload-file", help="Optional JSON file with extra payload fields."
|
|
187
|
+
),
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Record that the caller is about to act on ``event_id``."""
|
|
190
|
+
from map_types.enums import InboundEventSource
|
|
191
|
+
|
|
192
|
+
from cli.main import _read_text_file, _run
|
|
193
|
+
from server.domain.schemas import InboundEventCreate
|
|
194
|
+
|
|
195
|
+
extra_payload: dict | None = None
|
|
196
|
+
if payload_file is not None:
|
|
197
|
+
extra_payload = json.loads(_read_text_file(Path(payload_file), kind="payload"))
|
|
198
|
+
payload = InboundEventCreate(
|
|
199
|
+
event_id=event_id,
|
|
200
|
+
event_type=event_type,
|
|
201
|
+
source=InboundEventSource(source),
|
|
202
|
+
fingerprint=fingerprint,
|
|
203
|
+
payload=extra_payload,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def action(c: MAPClient):
|
|
207
|
+
try:
|
|
208
|
+
return c.record_inbound_event(payload)
|
|
209
|
+
except MAPConflictError as exc:
|
|
210
|
+
typer.echo(f"inbound-event duplicate (409): {exc.detail}", err=True)
|
|
211
|
+
raise typer.Exit(2) from exc
|
|
212
|
+
|
|
213
|
+
_run(action)
|
cli/commands/persona.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""``map persona ...`` sub-app — cli/main.py split.
|
|
2
|
+
|
|
3
|
+
Local ``.map/agents.yaml`` identity commands.
|
|
4
|
+
Command bodies lazy-import ``cli.main`` helpers to break the import cycle.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from map_client.client import MAPClient
|
|
12
|
+
from map_client.project_config import find_map_dir, load_project_map_config
|
|
13
|
+
|
|
14
|
+
persona_app = typer.Typer(help="Persona / identity commands")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@persona_app.command("list")
|
|
18
|
+
def persona_list(
|
|
19
|
+
project_root: Path | None = typer.Option(None, "--project-root"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List personas defined in .map/agents.yaml."""
|
|
22
|
+
from cli.main import _print_json, _require_map_dir # lazy: avoid cycle
|
|
23
|
+
map_dir = _require_map_dir(project_root)
|
|
24
|
+
cfg = load_project_map_config(map_dir=map_dir)
|
|
25
|
+
rows = []
|
|
26
|
+
for key, info in cfg.personas.items():
|
|
27
|
+
has_token = key in cfg.tokens
|
|
28
|
+
rows.append(
|
|
29
|
+
{
|
|
30
|
+
"persona": key,
|
|
31
|
+
"agent_name": info.agent_name,
|
|
32
|
+
"has_token": has_token,
|
|
33
|
+
"description": info.description,
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
_print_json(rows)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@persona_app.command("whoami")
|
|
40
|
+
def persona_whoami(
|
|
41
|
+
persona: str | None = typer.Option(None, "--persona", "-p"),
|
|
42
|
+
project_root: Path | None = typer.Option(None, "--project-root"),
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Show MAP identity for the selected persona (default from .map/config.yaml)."""
|
|
45
|
+
from cli.main import _cli_options, _run # lazy: avoid cycle
|
|
46
|
+
if persona is not None:
|
|
47
|
+
_cli_options["persona"] = persona
|
|
48
|
+
if project_root is not None:
|
|
49
|
+
_cli_options["project_root"] = project_root
|
|
50
|
+
|
|
51
|
+
def action(c: MAPClient):
|
|
52
|
+
me = c.get_me()
|
|
53
|
+
payload = me.model_dump(mode="json")
|
|
54
|
+
if _cli_options.get("persona"):
|
|
55
|
+
payload["persona"] = _cli_options["persona"]
|
|
56
|
+
elif find_map_dir(_cli_options.get("project_root")):
|
|
57
|
+
payload["persona"] = load_project_map_config(
|
|
58
|
+
project_root=_cli_options.get("project_root")
|
|
59
|
+
).default_persona
|
|
60
|
+
return payload
|
|
61
|
+
|
|
62
|
+
_run(action)
|
|
63
|
+
|
cli/commands/project.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""``map project ...`` sub-app — cli/main.py split.
|
|
2
|
+
|
|
3
|
+
Command bodies lazy-import ``cli.main`` helpers to break the import cycle.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from map_client.client import MAPClient
|
|
12
|
+
|
|
13
|
+
from server.domain.schemas import ProjectStatusRevise
|
|
14
|
+
|
|
15
|
+
project_app = typer.Typer(help="Project commands")
|
|
16
|
+
status_app = typer.Typer(help="Project Current Status commands")
|
|
17
|
+
project_app.add_typer(status_app, name="status")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@project_app.command("create")
|
|
21
|
+
def project_create(
|
|
22
|
+
key: str = typer.Option(..., "--key"),
|
|
23
|
+
name: str = typer.Option(..., "--name"),
|
|
24
|
+
path: str = typer.Option(..., "--path"),
|
|
25
|
+
description: str | None = typer.Option(None, "--description"),
|
|
26
|
+
) -> None:
|
|
27
|
+
from cli.main import _run # lazy: avoid cycle
|
|
28
|
+
_run(lambda c: c.create_project(key, name, path, description))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@project_app.command("list")
|
|
32
|
+
def project_list(include_archived: bool = typer.Option(False, "--include-archived")) -> None:
|
|
33
|
+
from cli.main import _run # lazy: avoid cycle
|
|
34
|
+
_run(lambda c: c.list_projects(include_archived=include_archived))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@project_app.command("decisions")
|
|
38
|
+
def project_decisions(
|
|
39
|
+
project: uuid.UUID | None = typer.Option(None, "--project"),
|
|
40
|
+
project_key: str | None = typer.Option(None, "--project-key"),
|
|
41
|
+
limit: int = typer.Option(20, "--limit", min=1, max=100),
|
|
42
|
+
) -> None:
|
|
43
|
+
from cli.main import _resolve_project, _run # lazy: avoid cycle
|
|
44
|
+
def action(c: MAPClient):
|
|
45
|
+
pid = _resolve_project(c, project, project_key)
|
|
46
|
+
return c.list_project_decisions(pid, limit=limit)
|
|
47
|
+
|
|
48
|
+
_run(action)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@status_app.command("revise")
|
|
52
|
+
def project_status_revise(
|
|
53
|
+
status_file: Path = typer.Option(..., "--file"),
|
|
54
|
+
project: uuid.UUID | None = typer.Option(None, "--project"),
|
|
55
|
+
project_key: str | None = typer.Option(None, "--project-key"),
|
|
56
|
+
note: str | None = typer.Option(None, "--note"),
|
|
57
|
+
) -> None:
|
|
58
|
+
from cli.main import _read_text_file, _resolve_project, _run # lazy: avoid cycle
|
|
59
|
+
payload = ProjectStatusRevise(content_md=_read_text_file(status_file, kind="status"), change_note=note)
|
|
60
|
+
|
|
61
|
+
def action(c: MAPClient):
|
|
62
|
+
pid = _resolve_project(c, project, project_key)
|
|
63
|
+
return c.revise_project_status(pid, payload)
|
|
64
|
+
|
|
65
|
+
_run(action)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@status_app.command("versions")
|
|
69
|
+
def project_status_versions(
|
|
70
|
+
project: uuid.UUID | None = typer.Option(None, "--project"),
|
|
71
|
+
project_key: str | None = typer.Option(None, "--project-key"),
|
|
72
|
+
) -> None:
|
|
73
|
+
from cli.main import _resolve_project, _run # lazy: avoid cycle
|
|
74
|
+
_run(lambda c: c.list_project_status_versions(_resolve_project(c, project, project_key)))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@status_app.command("show")
|
|
78
|
+
def project_status_show(
|
|
79
|
+
version: int = typer.Option(..., "--version"),
|
|
80
|
+
project: uuid.UUID | None = typer.Option(None, "--project"),
|
|
81
|
+
project_key: str | None = typer.Option(None, "--project-key"),
|
|
82
|
+
) -> None:
|
|
83
|
+
from cli.main import _resolve_project, _run # lazy: avoid cycle
|
|
84
|
+
_run(
|
|
85
|
+
lambda c: c.get_project_status_version(_resolve_project(c, project, project_key), version)
|
|
86
|
+
)
|
|
87
|
+
|
cli/commands/runtime.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""``map runtime ...`` sub-app — cli/main.py split.
|
|
2
|
+
|
|
3
|
+
Command bodies lazy-import ``cli.main`` helpers to break the import cycle.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
runtime_app = typer.Typer(help="Agent runtime session commands")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_app.command("chat")
|
|
15
|
+
def runtime_chat(
|
|
16
|
+
persona: str = typer.Option(
|
|
17
|
+
"host",
|
|
18
|
+
"--persona",
|
|
19
|
+
"-p",
|
|
20
|
+
help="Persona whose runtime session to resume (default: host)",
|
|
21
|
+
),
|
|
22
|
+
project_root: Path | None = typer.Option(
|
|
23
|
+
None,
|
|
24
|
+
"--project-root",
|
|
25
|
+
help="Repo root containing .map/ (default: search upward from cwd)",
|
|
26
|
+
),
|
|
27
|
+
state_file: Path | None = typer.Option(
|
|
28
|
+
None,
|
|
29
|
+
"--state-file",
|
|
30
|
+
help="Runtime waker state file (default: .map/runtime-waker-state-<persona>.json)",
|
|
31
|
+
),
|
|
32
|
+
runtime_home: Path | None = typer.Option(
|
|
33
|
+
None,
|
|
34
|
+
"--runtime-home",
|
|
35
|
+
help="Claude runtime HOME (default: .map/claude-runtime-home-<persona>)",
|
|
36
|
+
),
|
|
37
|
+
session_id: str | None = typer.Option(
|
|
38
|
+
None,
|
|
39
|
+
"--session-id",
|
|
40
|
+
help="Resume a specific Claude session id (default: read from state file)",
|
|
41
|
+
),
|
|
42
|
+
prompt: str | None = typer.Option(
|
|
43
|
+
None,
|
|
44
|
+
"--prompt",
|
|
45
|
+
help="Send one prompt before entering interactive REPL",
|
|
46
|
+
),
|
|
47
|
+
new_session: bool = typer.Option(
|
|
48
|
+
False,
|
|
49
|
+
"--new-session",
|
|
50
|
+
help="Start a fresh Claude session instead of resuming state",
|
|
51
|
+
),
|
|
52
|
+
ignore_waker: bool = typer.Option(
|
|
53
|
+
False,
|
|
54
|
+
"--ignore-waker",
|
|
55
|
+
help="Allow chat while map-runtime-waker is running (may conflict)",
|
|
56
|
+
),
|
|
57
|
+
model: str | None = typer.Option(None, "--model", help="Optional Claude model override"),
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Resume a persona runtime session and chat interactively from the terminal."""
|
|
60
|
+
from cli.main import _cli_options # lazy: avoid cycle
|
|
61
|
+
from cli.runtime_chat import run_runtime_chat
|
|
62
|
+
|
|
63
|
+
run_runtime_chat(
|
|
64
|
+
persona=persona,
|
|
65
|
+
project_root=project_root or _cli_options.get("project_root"),
|
|
66
|
+
state_file=state_file,
|
|
67
|
+
runtime_home=runtime_home,
|
|
68
|
+
session_id=session_id,
|
|
69
|
+
new_session=new_session,
|
|
70
|
+
initial_prompt=prompt,
|
|
71
|
+
ignore_waker=ignore_waker,
|
|
72
|
+
model=model,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@runtime_app.command("status")
|
|
77
|
+
def runtime_status(
|
|
78
|
+
persona: str = typer.Option(
|
|
79
|
+
"host",
|
|
80
|
+
"--persona",
|
|
81
|
+
"-p",
|
|
82
|
+
help="Persona to inspect (default: host)",
|
|
83
|
+
),
|
|
84
|
+
project_root: Path | None = typer.Option(
|
|
85
|
+
None,
|
|
86
|
+
"--project-root",
|
|
87
|
+
help="Repo root containing .map/ (default: search upward from cwd)",
|
|
88
|
+
),
|
|
89
|
+
state_file: Path | None = typer.Option(
|
|
90
|
+
None,
|
|
91
|
+
"--state-file",
|
|
92
|
+
help="Runtime waker state file (default: .map/runtime-waker-state-<persona>.json)",
|
|
93
|
+
),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Show resumable session id and whether runtime waker is running."""
|
|
96
|
+
from cli.main import _cli_options, _print_json # lazy: avoid cycle
|
|
97
|
+
from cli.runtime_chat import dump_runtime_chat_status
|
|
98
|
+
|
|
99
|
+
_print_json(
|
|
100
|
+
dump_runtime_chat_status(
|
|
101
|
+
persona=persona,
|
|
102
|
+
project_root=project_root or _cli_options.get("project_root"),
|
|
103
|
+
state_file=state_file,
|
|
104
|
+
)
|
|
105
|
+
)
|