forgexa-cli 1.13.4__tar.gz → 1.13.6__tar.gz
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.
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/PKG-INFO +1 -1
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli/daemon.py +178 -13
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli/main.py +70 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/pyproject.toml +1 -1
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/README.md +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/setup.cfg +0 -0
- {forgexa_cli-1.13.4 → forgexa_cli-1.13.6}/tests/test_auth_and_runtime_commands.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.13.
|
|
2
|
+
__version__ = "1.13.6"
|
|
@@ -523,7 +523,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
523
523
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
524
524
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
525
525
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
526
|
-
DAEMON_VERSION = "1.13.
|
|
526
|
+
DAEMON_VERSION = "1.13.6"
|
|
527
527
|
|
|
528
528
|
|
|
529
529
|
def _detect_client_type() -> str:
|
|
@@ -1585,6 +1585,14 @@ class WorkspaceManager:
|
|
|
1585
1585
|
"connection closed",
|
|
1586
1586
|
"connection refused",
|
|
1587
1587
|
"network is unreachable",
|
|
1588
|
+
# DNS resolution failures — transient (network reconnect, DHCP renewal,
|
|
1589
|
+
# macOS resolver flap). macOS/BSD SSH prints "nodename nor servname
|
|
1590
|
+
# provided"; Linux SSH/git prints "name or service not known" or
|
|
1591
|
+
# "temporary failure in name resolution".
|
|
1592
|
+
"nodename nor servname",
|
|
1593
|
+
"name or service not known",
|
|
1594
|
+
"could not resolve host",
|
|
1595
|
+
"temporary failure in name resolution",
|
|
1588
1596
|
)
|
|
1589
1597
|
return any(marker in err for marker in retry_markers)
|
|
1590
1598
|
|
|
@@ -2899,9 +2907,12 @@ class ProcessManager:
|
|
|
2899
2907
|
"connection reset",
|
|
2900
2908
|
"connection timed out",
|
|
2901
2909
|
"connection error",
|
|
2910
|
+
"failed to get response from the ai model",
|
|
2902
2911
|
"name or service not known",
|
|
2903
2912
|
"no such host",
|
|
2904
2913
|
"network is unreachable",
|
|
2914
|
+
"websockettransporterror",
|
|
2915
|
+
"websocket receive failed",
|
|
2905
2916
|
# "api error" removed: too broad — matches agent-generated code/output
|
|
2906
2917
|
# discussing API errors. Real API transport errors are covered by the
|
|
2907
2918
|
# connection patterns above (refused, reset, timed out, etc.).
|
|
@@ -3032,6 +3043,21 @@ class ProcessManager:
|
|
|
3032
3043
|
else:
|
|
3033
3044
|
has_result = True
|
|
3034
3045
|
has_meaningful_content = True
|
|
3046
|
+
elif ev_type == "session.error":
|
|
3047
|
+
err = data.get("data") or {}
|
|
3048
|
+
if isinstance(err, dict):
|
|
3049
|
+
msg = (
|
|
3050
|
+
err.get("message")
|
|
3051
|
+
or err.get("error")
|
|
3052
|
+
or err.get("errorType")
|
|
3053
|
+
or ""
|
|
3054
|
+
)
|
|
3055
|
+
if msg:
|
|
3056
|
+
error_messages.append(str(msg))
|
|
3057
|
+
elif isinstance(err, str) and err.strip():
|
|
3058
|
+
error_messages.append(err)
|
|
3059
|
+
elif isinstance(data.get("message"), str) and data.get("message", "").strip():
|
|
3060
|
+
error_messages.append(data["message"])
|
|
3035
3061
|
elif ev_type == "error":
|
|
3036
3062
|
msg = data.get("message", "")
|
|
3037
3063
|
if msg:
|
|
@@ -3137,6 +3163,31 @@ class ProcessManager:
|
|
|
3137
3163
|
and not signals["error_messages"]
|
|
3138
3164
|
)
|
|
3139
3165
|
|
|
3166
|
+
@staticmethod
|
|
3167
|
+
def _copilot_called_any_tools(stdout: str) -> bool:
|
|
3168
|
+
"""Return True if the Copilot output contains at least one tool call.
|
|
3169
|
+
|
|
3170
|
+
Used to distinguish an early API-rejection failure (exitCode=1 with
|
|
3171
|
+
no work done) from a mid-run failure where the CLI did attempt tool
|
|
3172
|
+
calls before encountering an error.
|
|
3173
|
+
"""
|
|
3174
|
+
for raw in (stdout or "").split("\n"):
|
|
3175
|
+
raw = raw.strip()
|
|
3176
|
+
if not raw:
|
|
3177
|
+
continue
|
|
3178
|
+
try:
|
|
3179
|
+
data = json.loads(raw)
|
|
3180
|
+
except json.JSONDecodeError:
|
|
3181
|
+
continue
|
|
3182
|
+
if isinstance(data, dict):
|
|
3183
|
+
ev = data.get("type", "")
|
|
3184
|
+
# tool.execution_start / tool_use / assistant.tool_calls are
|
|
3185
|
+
# all signals that at least one tool was invoked.
|
|
3186
|
+
if ev in ("tool.execution_start", "tool_use", "tool_result",
|
|
3187
|
+
"tool.execution_complete", "assistant.tool_calls"):
|
|
3188
|
+
return True
|
|
3189
|
+
return False
|
|
3190
|
+
|
|
3140
3191
|
@staticmethod
|
|
3141
3192
|
def is_rate_limited(result: "TaskResult") -> bool:
|
|
3142
3193
|
"""Check if an agent failure warrants trying a different agent.
|
|
@@ -4097,6 +4148,8 @@ class ProcessManager:
|
|
|
4097
4148
|
async def _run_copilot(
|
|
4098
4149
|
self, agent: DiscoveredAgent, prompt: str, cwd: Path, timeout: int, task_id: str,
|
|
4099
4150
|
on_chunk: Any = None,
|
|
4151
|
+
*,
|
|
4152
|
+
_retry_without_effort: bool = False,
|
|
4100
4153
|
) -> TaskResult:
|
|
4101
4154
|
"""Run GitHub Copilot CLI in non-interactive JSON-streaming mode.
|
|
4102
4155
|
|
|
@@ -4105,30 +4158,50 @@ class ProcessManager:
|
|
|
4105
4158
|
``gh auth login`` or GITHUB_TOKEN env var must be set.
|
|
4106
4159
|
|
|
4107
4160
|
Flags:
|
|
4108
|
-
-p / --prompt
|
|
4161
|
+
-p / --prompt Non-interactive prompt (exits after completion).
|
|
4109
4162
|
--output-format json JSONL stream of session events.
|
|
4110
|
-
--allow-all
|
|
4111
|
-
|
|
4112
|
-
-C <dir>
|
|
4163
|
+
--allow-all Grant all tool + path + URL permissions required
|
|
4164
|
+
for autonomous file-editing tasks.
|
|
4165
|
+
-C <dir> Change working directory before execution.
|
|
4166
|
+
|
|
4167
|
+
Compatibility note:
|
|
4168
|
+
``--effort`` is only included when ``FACTORY_COPILOT_REASONING`` is
|
|
4169
|
+
explicitly set to a non-empty value. Older Copilot CLI releases (and
|
|
4170
|
+
some VS Code–bundled variants) don't accept ``--effort``; passing it
|
|
4171
|
+
makes the CLI return exitCode=1 in the JSONL result event immediately
|
|
4172
|
+
without calling any tools. Making it opt-in avoids this incompatibility
|
|
4173
|
+
while still allowing operators to tune effort on supported versions.
|
|
4113
4174
|
"""
|
|
4114
4175
|
env = os.environ.copy()
|
|
4115
4176
|
env["TERM"] = "dumb" # suppress TTY-detection that suspends the process
|
|
4116
4177
|
env["COPILOT_HOME"] = self._prepare_copilot_home()
|
|
4117
4178
|
|
|
4118
4179
|
model_override = os.environ.get("FACTORY_COPILOT_MODEL")
|
|
4119
|
-
|
|
4180
|
+
# FACTORY_COPILOT_REASONING is opt-in: only pass --effort when the env
|
|
4181
|
+
# var is explicitly set to a non-empty string. The default (unset / "")
|
|
4182
|
+
# intentionally omits --effort so that Copilot CLI versions that don't
|
|
4183
|
+
# support the flag don't immediately fail with exitCode=1.
|
|
4184
|
+
reasoning = os.environ.get("FACTORY_COPILOT_REASONING", "").strip()
|
|
4120
4185
|
|
|
4121
4186
|
cmd = [
|
|
4122
4187
|
agent.command,
|
|
4123
4188
|
"--output-format", "json",
|
|
4124
4189
|
"--allow-all",
|
|
4125
|
-
"--effort", reasoning,
|
|
4126
|
-
"-C", str(cwd),
|
|
4127
|
-
"-p", prompt,
|
|
4128
4190
|
]
|
|
4191
|
+
if reasoning and not _retry_without_effort:
|
|
4192
|
+
cmd += ["--effort", reasoning]
|
|
4193
|
+
cmd += ["-C", str(cwd), "-p", prompt]
|
|
4129
4194
|
if model_override:
|
|
4130
4195
|
cmd = [agent.command, "--model", model_override] + cmd[1:]
|
|
4131
4196
|
|
|
4197
|
+
# Log the exact invocation (redact prompt body) so operators can
|
|
4198
|
+
# reproduce failures manually and diagnose CLI version incompatibilities.
|
|
4199
|
+
_cmd_display = " ".join(
|
|
4200
|
+
f'"{a}"' if " " in a else a
|
|
4201
|
+
for a in cmd[:-1] # omit the -p value (potentially huge/sensitive)
|
|
4202
|
+
) + ' -p "<prompt>"'
|
|
4203
|
+
logger.debug("Copilot invocation for task %s: %s", task_id, _cmd_display)
|
|
4204
|
+
|
|
4132
4205
|
try:
|
|
4133
4206
|
proc = await asyncio.create_subprocess_exec(
|
|
4134
4207
|
*cmd,
|
|
@@ -4151,7 +4224,27 @@ class ProcessManager:
|
|
|
4151
4224
|
# Copilot always exits 0 on normal completion; check result.exitCode
|
|
4152
4225
|
# from the JSONL "result" event for a true success signal.
|
|
4153
4226
|
copilot_exit = self._extract_copilot_exit_code(stdout)
|
|
4154
|
-
|
|
4227
|
+
signals = self._extract_output_signals(stdout)
|
|
4228
|
+
completed_without_result = bool(
|
|
4229
|
+
signals["has_turn_completed"]
|
|
4230
|
+
and signals["has_assistant_events"]
|
|
4231
|
+
and signals["has_meaningful_content"]
|
|
4232
|
+
and not signals["has_result"]
|
|
4233
|
+
and not signals["has_turn_failed"]
|
|
4234
|
+
and not signals["error_messages"]
|
|
4235
|
+
)
|
|
4236
|
+
structured_errors = [
|
|
4237
|
+
str(msg).strip()
|
|
4238
|
+
for msg in (signals.get("error_messages") or [])
|
|
4239
|
+
if str(msg).strip()
|
|
4240
|
+
]
|
|
4241
|
+
copilot_specific_error = ""
|
|
4242
|
+
for msg in reversed(structured_errors):
|
|
4243
|
+
prefix = "Copilot exited with code "
|
|
4244
|
+
suffix = msg[len(prefix):].strip() if msg.startswith(prefix) else ""
|
|
4245
|
+
if not (msg.startswith(prefix) and suffix.isdigit()):
|
|
4246
|
+
copilot_specific_error = msg
|
|
4247
|
+
break
|
|
4155
4248
|
effective_rc = copilot_exit if copilot_exit is not None else returncode
|
|
4156
4249
|
|
|
4157
4250
|
if effective_rc == 0 and returncode == 0:
|
|
@@ -4178,12 +4271,56 @@ class ProcessManager:
|
|
|
4178
4271
|
metrics=metrics,
|
|
4179
4272
|
)
|
|
4180
4273
|
else:
|
|
4274
|
+
# --- Immediate-failure retry ladder ---
|
|
4275
|
+
# If Copilot exited with code 1 and no tools were called, the CLI
|
|
4276
|
+
# rejected the initial API request before doing any work. This is a
|
|
4277
|
+
# flag-incompatibility or auth/quota symptom; retry with progressively
|
|
4278
|
+
# stripped flags to auto-heal across CLI versions.
|
|
4279
|
+
#
|
|
4280
|
+
# Retry 1: drop --effort (added in newer ghcs; unsupported in older
|
|
4281
|
+
# VS Code-bundled versions).
|
|
4282
|
+
# Retry 2: if still failing AND we haven't already tried both drops,
|
|
4283
|
+
# the error is likely auth/quota — log actionable hint.
|
|
4284
|
+
_no_tools = not self._copilot_called_any_tools(stdout)
|
|
4285
|
+
if (
|
|
4286
|
+
effective_rc == 1
|
|
4287
|
+
and _no_tools
|
|
4288
|
+
and reasoning
|
|
4289
|
+
and not _retry_without_effort
|
|
4290
|
+
and not copilot_specific_error
|
|
4291
|
+
):
|
|
4292
|
+
logger.warning(
|
|
4293
|
+
"Copilot exitCode=1 with no tool calls for task %s — "
|
|
4294
|
+
"retrying without --effort (flag may be unsupported by this CLI version). "
|
|
4295
|
+
"Invocation was: %s",
|
|
4296
|
+
task_id, _cmd_display,
|
|
4297
|
+
)
|
|
4298
|
+
return await self._run_copilot(
|
|
4299
|
+
agent, prompt, cwd, timeout, task_id, on_chunk,
|
|
4300
|
+
_retry_without_effort=True,
|
|
4301
|
+
)
|
|
4302
|
+
# Exhausted retries — emit an actionable diagnostic hint
|
|
4303
|
+
if effective_rc == 1 and _no_tools and not copilot_specific_error:
|
|
4304
|
+
logger.error(
|
|
4305
|
+
"Copilot exitCode=1 with no tool calls for task %s after retries. "
|
|
4306
|
+
"Likely causes: (1) GitHub auth expired — run `gh auth status` and "
|
|
4307
|
+
"`gh auth login --scopes copilot`; (2) Copilot subscription inactive; "
|
|
4308
|
+
"(3) --allow-all flag not supported by this CLI version (%s). "
|
|
4309
|
+
"To reproduce manually: %s",
|
|
4310
|
+
task_id, agent.version, _cmd_display,
|
|
4311
|
+
)
|
|
4312
|
+
if copilot_specific_error:
|
|
4313
|
+
failure_error = copilot_specific_error[:1500]
|
|
4314
|
+
elif stderr.strip():
|
|
4315
|
+
failure_error = f"Copilot exited with code {effective_rc}: {stderr[-500:].strip()}"
|
|
4316
|
+
else:
|
|
4317
|
+
failure_error = f"Copilot exited with code {effective_rc}"
|
|
4181
4318
|
return TaskResult(
|
|
4182
4319
|
status="failed",
|
|
4183
4320
|
exit_code=effective_rc,
|
|
4184
4321
|
stdout=stdout[-settings.AGENT_MAX_OUTPUT_SIZE:],
|
|
4185
4322
|
stderr=stderr[-10000:],
|
|
4186
|
-
error=
|
|
4323
|
+
error=failure_error,
|
|
4187
4324
|
metrics=metrics,
|
|
4188
4325
|
)
|
|
4189
4326
|
except asyncio.TimeoutError as exc:
|
|
@@ -6668,8 +6805,36 @@ class RuntimeDaemon:
|
|
|
6668
6805
|
if not intents:
|
|
6669
6806
|
issues.append("test-intent.json contains no test intents")
|
|
6670
6807
|
for ti in intents[:20]:
|
|
6671
|
-
|
|
6672
|
-
|
|
6808
|
+
# Accept common alternative field names that agents generate:
|
|
6809
|
+
# id → also accept "test_id", "intent_id"
|
|
6810
|
+
# title → also accept "description", "summary", "name"
|
|
6811
|
+
_ti_id = (
|
|
6812
|
+
ti.get("id")
|
|
6813
|
+
or ti.get("test_id")
|
|
6814
|
+
or ti.get("intent_id")
|
|
6815
|
+
)
|
|
6816
|
+
_ti_label = (
|
|
6817
|
+
ti.get("title")
|
|
6818
|
+
or ti.get("description")
|
|
6819
|
+
or ti.get("summary")
|
|
6820
|
+
or ti.get("name")
|
|
6821
|
+
)
|
|
6822
|
+
if not _ti_id or not _ti_label:
|
|
6823
|
+
# Include actual keys present so the retry prompt gives
|
|
6824
|
+
# the agent precise feedback on what to fix.
|
|
6825
|
+
_present_keys = list(ti.keys())[:8]
|
|
6826
|
+
if not _ti_id:
|
|
6827
|
+
issues.append(
|
|
6828
|
+
f"Test intent missing required 'id' field "
|
|
6829
|
+
f"(found keys: {_present_keys}). "
|
|
6830
|
+
f"Each intent MUST have an 'id' field (e.g. \"id\": \"TI-001\")."
|
|
6831
|
+
)
|
|
6832
|
+
else:
|
|
6833
|
+
issues.append(
|
|
6834
|
+
f"Test intent '{_ti_id}' missing required 'title' or 'description' field "
|
|
6835
|
+
f"(found keys: {_present_keys}). "
|
|
6836
|
+
f"Each intent MUST have a 'title' field (e.g. \"title\": \"...\")."
|
|
6837
|
+
)
|
|
6673
6838
|
break
|
|
6674
6839
|
except _json.JSONDecodeError as e:
|
|
6675
6840
|
issues.append(f"test-intent.json is not valid JSON: {e}")
|
|
@@ -27,6 +27,16 @@ Usage:
|
|
|
27
27
|
forgexa gates pending
|
|
28
28
|
forgexa config show
|
|
29
29
|
forgexa --help
|
|
30
|
+
|
|
31
|
+
Daemon shortcut commands (aliases — no need to type `daemon`):
|
|
32
|
+
forgexa start -d
|
|
33
|
+
forgexa stop
|
|
34
|
+
forgexa restart
|
|
35
|
+
forgexa status
|
|
36
|
+
forgexa status --verbose
|
|
37
|
+
forgexa logs -f
|
|
38
|
+
forgexa logs -n 100 -f
|
|
39
|
+
forgexa agents
|
|
30
40
|
"""
|
|
31
41
|
from __future__ import annotations
|
|
32
42
|
|
|
@@ -1387,6 +1397,59 @@ def main() -> None:
|
|
|
1387
1397
|
)
|
|
1388
1398
|
daemon_agents_p.add_argument("--all", action="store_true", help="Include all users' runtimes (admin only)")
|
|
1389
1399
|
|
|
1400
|
+
# ── Daemon shortcut commands ───────────────────────────────────────────────
|
|
1401
|
+
# Top-level aliases so users can type `forgexa start` instead of
|
|
1402
|
+
# `forgexa daemon start`. Both forms are fully equivalent.
|
|
1403
|
+
_start_sc = sub.add_parser(
|
|
1404
|
+
"start",
|
|
1405
|
+
help="Start local daemon (alias: forgexa daemon start)",
|
|
1406
|
+
)
|
|
1407
|
+
_start_sc.add_argument(
|
|
1408
|
+
"-d", "--detach",
|
|
1409
|
+
action="store_true",
|
|
1410
|
+
help="Run in background (recommended for production use)",
|
|
1411
|
+
)
|
|
1412
|
+
_start_sc.add_argument("--server-url", default=None, help="Server URL to connect to")
|
|
1413
|
+
|
|
1414
|
+
sub.add_parser(
|
|
1415
|
+
"stop",
|
|
1416
|
+
help="Stop local daemon (alias: forgexa daemon stop)",
|
|
1417
|
+
)
|
|
1418
|
+
|
|
1419
|
+
_restart_sc = sub.add_parser(
|
|
1420
|
+
"restart",
|
|
1421
|
+
help="Restart local daemon (alias: forgexa daemon restart)",
|
|
1422
|
+
)
|
|
1423
|
+
_restart_sc.add_argument("--server-url", default=None, help="Server URL to connect to")
|
|
1424
|
+
|
|
1425
|
+
_status_sc = sub.add_parser(
|
|
1426
|
+
"status",
|
|
1427
|
+
help="Daemon status and agents (alias: forgexa daemon status)",
|
|
1428
|
+
)
|
|
1429
|
+
_status_sc.add_argument("--all", action="store_true", help="List all runtimes (platform admin only)")
|
|
1430
|
+
_status_sc.add_argument("-v", "--verbose", action="store_true", help="Expanded per-agent detail")
|
|
1431
|
+
|
|
1432
|
+
_logs_sc = sub.add_parser(
|
|
1433
|
+
"logs",
|
|
1434
|
+
help="Stream daemon logs (alias: forgexa daemon logs)",
|
|
1435
|
+
)
|
|
1436
|
+
_logs_sc.add_argument(
|
|
1437
|
+
"-n", "--lines",
|
|
1438
|
+
type=int, default=50, metavar="N",
|
|
1439
|
+
help="Show last N lines before following (default: 50)",
|
|
1440
|
+
)
|
|
1441
|
+
_logs_sc.add_argument(
|
|
1442
|
+
"-f", "--follow",
|
|
1443
|
+
action="store_true",
|
|
1444
|
+
help="Stream new log lines as they arrive (like tail -f)",
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
_agents_sc = sub.add_parser(
|
|
1448
|
+
"agents",
|
|
1449
|
+
help="List AI agents (alias: forgexa daemon agents)",
|
|
1450
|
+
)
|
|
1451
|
+
_agents_sc.add_argument("--all", action="store_true", help="Include all users' runtimes (admin only)")
|
|
1452
|
+
|
|
1390
1453
|
# runtimes
|
|
1391
1454
|
rt_p = sub.add_parser("runtimes", help="Runtime management")
|
|
1392
1455
|
rt_sub = rt_p.add_subparsers(dest="rt_cmd")
|
|
@@ -1489,6 +1552,13 @@ def main() -> None:
|
|
|
1489
1552
|
"logs": cmd_daemon_logs,
|
|
1490
1553
|
"agents": cmd_daemon_agents,
|
|
1491
1554
|
}.get(a.daemon_cmd, lambda _: daemon_p.print_help())(a),
|
|
1555
|
+
# Daemon shortcut aliases (identical handlers, no `daemon` prefix needed)
|
|
1556
|
+
"start": cmd_daemon_start,
|
|
1557
|
+
"stop": cmd_daemon_stop,
|
|
1558
|
+
"restart": cmd_daemon_restart,
|
|
1559
|
+
"status": cmd_daemon_status,
|
|
1560
|
+
"logs": cmd_daemon_logs,
|
|
1561
|
+
"agents": cmd_daemon_agents,
|
|
1492
1562
|
"runtimes": lambda a: {
|
|
1493
1563
|
"list": cmd_runtimes_list,
|
|
1494
1564
|
}.get(a.rt_cmd, lambda _: rt_p.print_help())(a),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|