agent-run-supervisor 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.
- agent_run_supervisor/__init__.py +5 -0
- agent_run_supervisor/__main__.py +3 -0
- agent_run_supervisor/caller.py +301 -0
- agent_run_supervisor/cli.py +192 -0
- agent_run_supervisor/commands.py +381 -0
- agent_run_supervisor/event_store.py +159 -0
- agent_run_supervisor/exit_classifier.py +137 -0
- agent_run_supervisor/fixtures/acpx-0.12.0/success-codex-sentinel/stdout.ndjson +15 -0
- agent_run_supervisor/hermes_caller/__init__.py +74 -0
- agent_run_supervisor/hermes_caller/events.py +245 -0
- agent_run_supervisor/hermes_caller/feishu_adapter.py +61 -0
- agent_run_supervisor/hermes_caller/hermes.py +135 -0
- agent_run_supervisor/hermes_caller/intake.py +136 -0
- agent_run_supervisor/hermes_caller/task.py +21 -0
- agent_run_supervisor/hermes_caller/verdict.py +93 -0
- agent_run_supervisor/hermes_caller/view_model.py +144 -0
- agent_run_supervisor/live_stream.py +131 -0
- agent_run_supervisor/parser.py +487 -0
- agent_run_supervisor/policy.py +291 -0
- agent_run_supervisor/preflight.py +493 -0
- agent_run_supervisor/process_liveness.py +240 -0
- agent_run_supervisor/redaction.py +134 -0
- agent_run_supervisor/result.py +77 -0
- agent_run_supervisor/retention.py +455 -0
- agent_run_supervisor/role.py +327 -0
- agent_run_supervisor/runner.py +800 -0
- agent_run_supervisor/session.py +583 -0
- agent_run_supervisor/session_runtime.py +780 -0
- agent_run_supervisor/workspace.py +105 -0
- agent_run_supervisor-0.1.0.dist-info/METADATA +311 -0
- agent_run_supervisor-0.1.0.dist-info/RECORD +35 -0
- agent_run_supervisor-0.1.0.dist-info/WHEEL +5 -0
- agent_run_supervisor-0.1.0.dist-info/entry_points.txt +2 -0
- agent_run_supervisor-0.1.0.dist-info/licenses/LICENSE +21 -0
- agent_run_supervisor-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Generic local caller boundary for library integrations.
|
|
2
|
+
|
|
3
|
+
The caller boundary is intentionally thin: it validates a local invocation
|
|
4
|
+
specification, loads the role when supplied by file, combines caller-owned
|
|
5
|
+
context with the prompt, and delegates execution to the existing supervisor
|
|
6
|
+
surfaces. It does not parse acpx streams, interpret business success, deliver
|
|
7
|
+
messages, or carry platform identifiers.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from agent_run_supervisor.role import AgentRoleSpec, load_role
|
|
17
|
+
from agent_run_supervisor.runner import DryRunResult, RunOutcome, SupervisorRunner
|
|
18
|
+
from agent_run_supervisor.session_runtime import (
|
|
19
|
+
SessionCloseOutcome,
|
|
20
|
+
SessionCreateOutcome,
|
|
21
|
+
SessionRuntime,
|
|
22
|
+
SessionStatusOutcome,
|
|
23
|
+
SessionTurnOutcome,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
DEFAULT_SESSIONS_DIR_NAME = Path(".agent-run-supervisor") / "sessions"
|
|
27
|
+
|
|
28
|
+
EXEC_MODE = "exec"
|
|
29
|
+
EXEC_DRY_RUN_MODE = "exec_dry_run"
|
|
30
|
+
SESSION_CREATE_MODE = "session_create"
|
|
31
|
+
SESSION_SEND_MODE = "session_send"
|
|
32
|
+
SESSION_STATUS_MODE = "session_status"
|
|
33
|
+
SESSION_CLOSE_MODE = "session_close"
|
|
34
|
+
|
|
35
|
+
SUPPORTED_MODES = {
|
|
36
|
+
EXEC_MODE,
|
|
37
|
+
EXEC_DRY_RUN_MODE,
|
|
38
|
+
SESSION_CREATE_MODE,
|
|
39
|
+
SESSION_SEND_MODE,
|
|
40
|
+
SESSION_STATUS_MODE,
|
|
41
|
+
SESSION_CLOSE_MODE,
|
|
42
|
+
}
|
|
43
|
+
PROMPT_REQUIRED_MODES = {EXEC_MODE, EXEC_DRY_RUN_MODE, SESSION_SEND_MODE}
|
|
44
|
+
SESSION_MODES = {
|
|
45
|
+
SESSION_CREATE_MODE,
|
|
46
|
+
SESSION_SEND_MODE,
|
|
47
|
+
SESSION_STATUS_MODE,
|
|
48
|
+
SESSION_CLOSE_MODE,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CallerInvocationError(ValueError):
|
|
53
|
+
"""Raised when a local caller invocation is invalid before delegation."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class CallerInvocationSpec:
|
|
58
|
+
mode: str
|
|
59
|
+
role: AgentRoleSpec | None = None
|
|
60
|
+
role_file: str | Path | None = None
|
|
61
|
+
prompt: str | None = None
|
|
62
|
+
context: str | None = None
|
|
63
|
+
cwd: str | Path | None = None
|
|
64
|
+
runs_dir: str | Path | None = None
|
|
65
|
+
sessions_dir: str | Path | None = None
|
|
66
|
+
session_id: str | None = None
|
|
67
|
+
session_name: str | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class CallerResult:
|
|
72
|
+
mode: str
|
|
73
|
+
supervisor_status: str | None
|
|
74
|
+
result: dict[str, Any]
|
|
75
|
+
artifact_dir: str | None
|
|
76
|
+
run_dir: str | None
|
|
77
|
+
session_dir: str | None
|
|
78
|
+
business_verdict: None = None
|
|
79
|
+
|
|
80
|
+
def to_dict(self) -> dict[str, Any]:
|
|
81
|
+
return {
|
|
82
|
+
"mode": self.mode,
|
|
83
|
+
"supervisor_status": self.supervisor_status,
|
|
84
|
+
"result": self.result,
|
|
85
|
+
"artifact_dir": self.artifact_dir,
|
|
86
|
+
"run_dir": self.run_dir,
|
|
87
|
+
"session_dir": self.session_dir,
|
|
88
|
+
"business_verdict": self.business_verdict,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def invoke_caller(
|
|
93
|
+
spec: CallerInvocationSpec,
|
|
94
|
+
*,
|
|
95
|
+
runner: SupervisorRunner | None = None,
|
|
96
|
+
session_runtime: SessionRuntime | None = None,
|
|
97
|
+
) -> CallerResult:
|
|
98
|
+
"""Invoke the local supervisor through a generic caller-owned spec.
|
|
99
|
+
|
|
100
|
+
``business_verdict`` stays ``None`` both in the wrapped supervisor result
|
|
101
|
+
and in the caller wrapper. Callers remain responsible for business verdicts,
|
|
102
|
+
rendering, delivery, and any platform integration outside this library.
|
|
103
|
+
"""
|
|
104
|
+
_validate_spec(spec)
|
|
105
|
+
role = _resolve_role(spec)
|
|
106
|
+
cwd = _optional_str(spec.cwd)
|
|
107
|
+
|
|
108
|
+
if spec.mode in {EXEC_MODE, EXEC_DRY_RUN_MODE}:
|
|
109
|
+
active_runner = runner or SupervisorRunner(
|
|
110
|
+
runs_dir=Path(spec.runs_dir) if spec.runs_dir is not None else None
|
|
111
|
+
)
|
|
112
|
+
prompt = _combined_prompt(spec)
|
|
113
|
+
if spec.mode == EXEC_DRY_RUN_MODE:
|
|
114
|
+
dry = active_runner.dry_run(role=role, prompt=prompt, cwd=cwd)
|
|
115
|
+
return _from_run_like(mode=spec.mode, outcome=dry)
|
|
116
|
+
outcome = active_runner.run(role=role, prompt=prompt, cwd=cwd)
|
|
117
|
+
return _from_run_like(mode=spec.mode, outcome=outcome)
|
|
118
|
+
|
|
119
|
+
active_runtime = session_runtime or SessionRuntime(sessions_dir=_sessions_dir(spec))
|
|
120
|
+
session_id = _required_session_id(spec)
|
|
121
|
+
|
|
122
|
+
if spec.mode == SESSION_CREATE_MODE:
|
|
123
|
+
created = active_runtime.create_session(
|
|
124
|
+
role=role,
|
|
125
|
+
session_id=session_id,
|
|
126
|
+
session_name=spec.session_name,
|
|
127
|
+
cwd=cwd,
|
|
128
|
+
)
|
|
129
|
+
return _from_session_create(spec.mode, created)
|
|
130
|
+
if spec.mode == SESSION_SEND_MODE:
|
|
131
|
+
sent = active_runtime.send(
|
|
132
|
+
role=role,
|
|
133
|
+
session_id=session_id,
|
|
134
|
+
prompt=_combined_prompt(spec),
|
|
135
|
+
cwd=cwd,
|
|
136
|
+
)
|
|
137
|
+
return _from_session_turn(
|
|
138
|
+
spec.mode,
|
|
139
|
+
sent,
|
|
140
|
+
session_dir=_session_dir(active_runtime, spec, session_id),
|
|
141
|
+
)
|
|
142
|
+
if spec.mode == SESSION_STATUS_MODE:
|
|
143
|
+
status = active_runtime.status(role=role, session_id=session_id, cwd=cwd)
|
|
144
|
+
return _from_session_management(
|
|
145
|
+
spec.mode,
|
|
146
|
+
status.result,
|
|
147
|
+
session_dir=_session_dir(active_runtime, spec, session_id),
|
|
148
|
+
)
|
|
149
|
+
if spec.mode == SESSION_CLOSE_MODE:
|
|
150
|
+
closed = active_runtime.close(role=role, session_id=session_id, cwd=cwd)
|
|
151
|
+
return _from_session_close(spec.mode, closed, _session_dir(active_runtime, spec, session_id))
|
|
152
|
+
|
|
153
|
+
raise CallerInvocationError(f"unsupported mode {spec.mode!r}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _validate_spec(spec: CallerInvocationSpec) -> None:
|
|
157
|
+
if spec.mode not in SUPPORTED_MODES:
|
|
158
|
+
raise CallerInvocationError(f"unsupported mode {spec.mode!r}")
|
|
159
|
+
role_source_count = int(spec.role is not None) + int(spec.role_file is not None)
|
|
160
|
+
if role_source_count != 1:
|
|
161
|
+
raise CallerInvocationError("exactly one role source is required: role or role_file")
|
|
162
|
+
if spec.mode in PROMPT_REQUIRED_MODES and not _has_text(spec.prompt):
|
|
163
|
+
raise CallerInvocationError(f"mode {spec.mode!r} requires a non-empty prompt")
|
|
164
|
+
if spec.mode in SESSION_MODES and not _has_text(spec.session_id):
|
|
165
|
+
raise CallerInvocationError(f"mode {spec.mode!r} requires a non-empty session_id")
|
|
166
|
+
if spec.session_name is not None:
|
|
167
|
+
if spec.mode != SESSION_CREATE_MODE:
|
|
168
|
+
raise CallerInvocationError("session_name is only accepted for session_create")
|
|
169
|
+
if not _has_text(spec.session_name):
|
|
170
|
+
raise CallerInvocationError("session_name must be a non-empty string")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _resolve_role(spec: CallerInvocationSpec) -> AgentRoleSpec:
|
|
174
|
+
if spec.role is not None:
|
|
175
|
+
return spec.role
|
|
176
|
+
if spec.role_file is None:
|
|
177
|
+
# Unreachable after validation; keeps type checkers honest.
|
|
178
|
+
raise CallerInvocationError("missing role source")
|
|
179
|
+
raw = json.loads(Path(spec.role_file).read_text(encoding="utf-8"))
|
|
180
|
+
return load_role(raw)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _combined_prompt(spec: CallerInvocationSpec) -> str:
|
|
184
|
+
prompt = spec.prompt or ""
|
|
185
|
+
context = spec.context or ""
|
|
186
|
+
if context:
|
|
187
|
+
return f"{context}\n\n{prompt}"
|
|
188
|
+
return prompt
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _from_run_like(
|
|
192
|
+
*,
|
|
193
|
+
mode: str,
|
|
194
|
+
outcome: DryRunResult | RunOutcome,
|
|
195
|
+
) -> CallerResult:
|
|
196
|
+
result = dict(outcome.result)
|
|
197
|
+
run_dir = str(outcome.run_dir)
|
|
198
|
+
return CallerResult(
|
|
199
|
+
mode=mode,
|
|
200
|
+
supervisor_status=_status_from(result),
|
|
201
|
+
result=result,
|
|
202
|
+
artifact_dir=run_dir,
|
|
203
|
+
run_dir=run_dir,
|
|
204
|
+
session_dir=None,
|
|
205
|
+
business_verdict=None,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _from_session_create(mode: str, outcome: SessionCreateOutcome) -> CallerResult:
|
|
210
|
+
result = dict(outcome.result)
|
|
211
|
+
session_dir = str(outcome.session_dir)
|
|
212
|
+
return CallerResult(
|
|
213
|
+
mode=mode,
|
|
214
|
+
supervisor_status=_status_from(result),
|
|
215
|
+
result=result,
|
|
216
|
+
artifact_dir=session_dir,
|
|
217
|
+
run_dir=None,
|
|
218
|
+
session_dir=session_dir,
|
|
219
|
+
business_verdict=None,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _from_session_turn(
|
|
224
|
+
mode: str,
|
|
225
|
+
outcome: SessionTurnOutcome,
|
|
226
|
+
*,
|
|
227
|
+
session_dir: Path,
|
|
228
|
+
) -> CallerResult:
|
|
229
|
+
result = dict(outcome.result)
|
|
230
|
+
run_dir = result.get("run_dir")
|
|
231
|
+
run_dir_str = run_dir if isinstance(run_dir, str) else str(outcome.turn_dir)
|
|
232
|
+
return CallerResult(
|
|
233
|
+
mode=mode,
|
|
234
|
+
supervisor_status=_status_from(result),
|
|
235
|
+
result=result,
|
|
236
|
+
artifact_dir=run_dir_str,
|
|
237
|
+
run_dir=run_dir_str,
|
|
238
|
+
session_dir=str(session_dir),
|
|
239
|
+
business_verdict=None,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _from_session_management(
|
|
244
|
+
mode: str,
|
|
245
|
+
result: dict[str, Any],
|
|
246
|
+
*,
|
|
247
|
+
session_dir: Path,
|
|
248
|
+
) -> CallerResult:
|
|
249
|
+
result_copy = dict(result)
|
|
250
|
+
session_dir_str = str(session_dir)
|
|
251
|
+
return CallerResult(
|
|
252
|
+
mode=mode,
|
|
253
|
+
supervisor_status=_status_from(result_copy),
|
|
254
|
+
result=result_copy,
|
|
255
|
+
artifact_dir=session_dir_str,
|
|
256
|
+
run_dir=None,
|
|
257
|
+
session_dir=session_dir_str,
|
|
258
|
+
business_verdict=None,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _from_session_close(
|
|
263
|
+
mode: str,
|
|
264
|
+
outcome: SessionCloseOutcome,
|
|
265
|
+
session_dir: Path,
|
|
266
|
+
) -> CallerResult:
|
|
267
|
+
return _from_session_management(mode, outcome.result, session_dir=session_dir)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _status_from(result: dict[str, Any]) -> str | None:
|
|
271
|
+
status = result.get("status")
|
|
272
|
+
return status if isinstance(status, str) else None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _sessions_dir(spec: CallerInvocationSpec) -> Path:
|
|
276
|
+
if spec.sessions_dir is not None:
|
|
277
|
+
return Path(spec.sessions_dir)
|
|
278
|
+
return Path.cwd() / DEFAULT_SESSIONS_DIR_NAME
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _session_dir(
|
|
282
|
+
runtime: SessionRuntime,
|
|
283
|
+
spec: CallerInvocationSpec,
|
|
284
|
+
session_id: str,
|
|
285
|
+
) -> Path:
|
|
286
|
+
base = Path(spec.sessions_dir) if spec.sessions_dir is not None else runtime.sessions_dir
|
|
287
|
+
return base / session_id
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _required_session_id(spec: CallerInvocationSpec) -> str:
|
|
291
|
+
if spec.session_id is None:
|
|
292
|
+
raise CallerInvocationError("session_id is required")
|
|
293
|
+
return spec.session_id
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _optional_str(value: str | Path | None) -> str | None:
|
|
297
|
+
return str(value) if value is not None else None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _has_text(value: str | None) -> bool:
|
|
301
|
+
return isinstance(value, str) and bool(value)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
prog="agent-run-supervisor",
|
|
11
|
+
description="Supervise ACP/acpx external AGENT runs and sessions with redacted local evidence.",
|
|
12
|
+
)
|
|
13
|
+
subparsers = parser.add_subparsers(dest="command", metavar="command")
|
|
14
|
+
|
|
15
|
+
validate_role = subparsers.add_parser(
|
|
16
|
+
"validate-role",
|
|
17
|
+
help="Validate an AgentRoleSpec file.",
|
|
18
|
+
)
|
|
19
|
+
validate_role.add_argument("role_file", help="Path to a JSON AgentRoleSpec file.")
|
|
20
|
+
|
|
21
|
+
replay = subparsers.add_parser(
|
|
22
|
+
"replay",
|
|
23
|
+
help="Replay an observed acpx stdout NDJSON stream through the parser.",
|
|
24
|
+
)
|
|
25
|
+
replay.add_argument("events_file", help="Path to acpx stdout NDJSON file.")
|
|
26
|
+
|
|
27
|
+
doctor = subparsers.add_parser(
|
|
28
|
+
"doctor",
|
|
29
|
+
help="Run environment/fixture probes without launching a real AGENT.",
|
|
30
|
+
)
|
|
31
|
+
doctor.add_argument("--role", default=None, help="Optional role file to validate.")
|
|
32
|
+
doctor.add_argument(
|
|
33
|
+
"--fixtures",
|
|
34
|
+
default=None,
|
|
35
|
+
help="Optional fixture directory to replay.",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
run = subparsers.add_parser(
|
|
39
|
+
"run",
|
|
40
|
+
help="Supervise local acpx exec or persist safe dry-run artifacts.",
|
|
41
|
+
description="Supervise local acpx exec under AgentRoleSpec policy, or persist safe dry-run artifacts with --no-real-run.",
|
|
42
|
+
)
|
|
43
|
+
run.add_argument("--role", required=True, help="Path to AgentRoleSpec JSON file.")
|
|
44
|
+
run.add_argument("--prompt-file", required=True, help="Path to prompt text file.")
|
|
45
|
+
run.add_argument("--cwd", default=None, help="Override effective cwd for exec or dry-run compilation.")
|
|
46
|
+
run.add_argument(
|
|
47
|
+
"--no-real-run",
|
|
48
|
+
action="store_true",
|
|
49
|
+
help="Persist safe dry-run artifacts without launching local acpx exec.",
|
|
50
|
+
)
|
|
51
|
+
run.add_argument(
|
|
52
|
+
"--runs-dir",
|
|
53
|
+
default=None,
|
|
54
|
+
help="Directory that holds run artifacts (defaults to .agent-run-supervisor/runs).",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
_add_session_parser(subparsers)
|
|
58
|
+
_add_cleanup_parser(subparsers)
|
|
59
|
+
return parser
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _add_cleanup_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
63
|
+
"""Confined, dry-run-first run/session artifact retention/cleanup."""
|
|
64
|
+
cleanup = subparsers.add_parser(
|
|
65
|
+
"cleanup",
|
|
66
|
+
help="Plan (dry-run) or apply confined retention cleanup of local run/session artifacts.",
|
|
67
|
+
description=(
|
|
68
|
+
"List run/session artifacts under a .agent-run-supervisor root that a "
|
|
69
|
+
"retention policy would remove. Dry-run by default; --apply deletes only "
|
|
70
|
+
"the planned, confined, non-live entries."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
cleanup.add_argument(
|
|
74
|
+
"--runs-dir",
|
|
75
|
+
default=None,
|
|
76
|
+
help="Directory that holds run artifacts (defaults to .agent-run-supervisor/runs).",
|
|
77
|
+
)
|
|
78
|
+
cleanup.add_argument(
|
|
79
|
+
"--sessions-dir",
|
|
80
|
+
default=None,
|
|
81
|
+
help="Directory that holds session records (defaults to .agent-run-supervisor/sessions).",
|
|
82
|
+
)
|
|
83
|
+
cleanup.add_argument(
|
|
84
|
+
"--max-age-days",
|
|
85
|
+
type=int,
|
|
86
|
+
default=None,
|
|
87
|
+
help="Delete artifacts strictly older than this many days.",
|
|
88
|
+
)
|
|
89
|
+
cleanup.add_argument(
|
|
90
|
+
"--max-count",
|
|
91
|
+
type=int,
|
|
92
|
+
default=None,
|
|
93
|
+
help="Keep only the newest N eligible artifacts; delete the rest.",
|
|
94
|
+
)
|
|
95
|
+
cleanup.add_argument(
|
|
96
|
+
"--apply",
|
|
97
|
+
action="store_true",
|
|
98
|
+
help="Actually delete the planned entries (default is a read-only dry-run).",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _add_session_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
103
|
+
"""Persistent-session lifecycle: ``session create|send|status|close|abort|list``."""
|
|
104
|
+
session = subparsers.add_parser(
|
|
105
|
+
"session",
|
|
106
|
+
help="Persistent session lifecycle (create / send / status / close / abort / list).",
|
|
107
|
+
description="Create/open a role-bound persistent session, send a prompt turn, query status, close, abort/cancel, or list local session records.",
|
|
108
|
+
)
|
|
109
|
+
session_sub = session.add_subparsers(dest="session_command", metavar="session_command")
|
|
110
|
+
|
|
111
|
+
def _add_common(sub: argparse.ArgumentParser) -> None:
|
|
112
|
+
sub.add_argument("--role", required=True, help="Path to AgentRoleSpec JSON file.")
|
|
113
|
+
sub.add_argument("--session-id", required=True, help="Local session id (safe path component).")
|
|
114
|
+
sub.add_argument("--cwd", default=None, help="Override effective cwd for session work.")
|
|
115
|
+
sub.add_argument(
|
|
116
|
+
"--sessions-dir",
|
|
117
|
+
default=None,
|
|
118
|
+
help="Directory that holds session records (defaults to .agent-run-supervisor/sessions).",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
create = session_sub.add_parser("create", help="Create/open a local persistent session.")
|
|
122
|
+
_add_common(create)
|
|
123
|
+
create.add_argument(
|
|
124
|
+
"--session-name",
|
|
125
|
+
default=None,
|
|
126
|
+
help="acpx session name (defaults to --session-id).",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
send = session_sub.add_parser("send", help="Send one prompt turn to a session.")
|
|
130
|
+
_add_common(send)
|
|
131
|
+
send.add_argument("--prompt-file", required=True, help="Path to prompt text file.")
|
|
132
|
+
|
|
133
|
+
status = session_sub.add_parser("status", help="Query a session's status/show record.")
|
|
134
|
+
_add_common(status)
|
|
135
|
+
|
|
136
|
+
close = session_sub.add_parser("close", help="Close an open local persistent session.")
|
|
137
|
+
_add_common(close)
|
|
138
|
+
|
|
139
|
+
abort = session_sub.add_parser("abort", help="Cooperatively cancel/abort active session work.")
|
|
140
|
+
_add_common(abort)
|
|
141
|
+
|
|
142
|
+
list_sessions = session_sub.add_parser(
|
|
143
|
+
"list",
|
|
144
|
+
help="List local session records (read-only; does not launch acpx/AGENT work).",
|
|
145
|
+
)
|
|
146
|
+
list_sessions.add_argument(
|
|
147
|
+
"--sessions-dir",
|
|
148
|
+
default=None,
|
|
149
|
+
help="Directory that holds session records (defaults to .agent-run-supervisor/sessions).",
|
|
150
|
+
)
|
|
151
|
+
list_sessions.add_argument(
|
|
152
|
+
"--role",
|
|
153
|
+
default=None,
|
|
154
|
+
help="Optional role file; if given, list only records owned by that role.",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
159
|
+
parser = _build_parser()
|
|
160
|
+
args = parser.parse_args(argv)
|
|
161
|
+
if args.command is None:
|
|
162
|
+
parser.print_usage(sys.stderr)
|
|
163
|
+
print("error: a subcommand is required", file=sys.stderr)
|
|
164
|
+
return 2
|
|
165
|
+
|
|
166
|
+
if args.command == "validate-role":
|
|
167
|
+
from agent_run_supervisor.commands import cmd_validate_role
|
|
168
|
+
|
|
169
|
+
return cmd_validate_role(args)
|
|
170
|
+
if args.command == "replay":
|
|
171
|
+
from agent_run_supervisor.commands import cmd_replay
|
|
172
|
+
|
|
173
|
+
return cmd_replay(args)
|
|
174
|
+
if args.command == "doctor":
|
|
175
|
+
from agent_run_supervisor.commands import cmd_doctor
|
|
176
|
+
|
|
177
|
+
return cmd_doctor(args)
|
|
178
|
+
if args.command == "run":
|
|
179
|
+
from agent_run_supervisor.commands import cmd_run
|
|
180
|
+
|
|
181
|
+
return cmd_run(args)
|
|
182
|
+
if args.command == "session":
|
|
183
|
+
from agent_run_supervisor.commands import cmd_session
|
|
184
|
+
|
|
185
|
+
return cmd_session(args)
|
|
186
|
+
if args.command == "cleanup":
|
|
187
|
+
from agent_run_supervisor.commands import cmd_cleanup
|
|
188
|
+
|
|
189
|
+
return cmd_cleanup(args)
|
|
190
|
+
parser.print_usage(sys.stderr)
|
|
191
|
+
print(f"error: unknown command {args.command}", file=sys.stderr)
|
|
192
|
+
return 2
|