forgexa-cli 1.63.1__tar.gz → 1.64.1__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.63.1 → forgexa_cli-1.64.1}/PKG-INFO +1 -1
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/agent_core.py +4 -2
- forgexa_cli-1.64.1/forgexa_cli/aws_evidence.py +279 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/daemon.py +28 -1
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/SOURCES.txt +1 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/pyproject.toml +1 -1
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/README.md +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/setup.cfg +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.64.1"
|
|
@@ -2871,6 +2871,7 @@ def mimo_base_env(env: dict[str, str]) -> dict[str, str]:
|
|
|
2871
2871
|
},
|
|
2872
2872
|
"external_directory": "deny",
|
|
2873
2873
|
"task": "allow",
|
|
2874
|
+
"actor": "allow",
|
|
2874
2875
|
"skill": {"*": "allow", "compose:*": "deny"},
|
|
2875
2876
|
"webfetch": "deny",
|
|
2876
2877
|
"websearch": "deny",
|
|
@@ -3663,8 +3664,9 @@ def build_copilot_command(
|
|
|
3663
3664
|
stdin instead - verified live on CLI 1.0.86, where piped stdin is a full
|
|
3664
3665
|
non-interactive prompt source. The daemon uses it for native launchers
|
|
3665
3666
|
to avoid single-argument and total command-line limits; Windows batch
|
|
3666
|
-
wrappers retain task-file redirection. The host uses it
|
|
3667
|
-
|
|
3667
|
+
wrappers retain task-file redirection. The host uses it for every prompt
|
|
3668
|
+
so task text never passes through Windows batch argument parsing or
|
|
3669
|
+
command-line limits, including when attachments are inlined.
|
|
3668
3670
|
"""
|
|
3669
3671
|
cmd = [
|
|
3670
3672
|
binary,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
PAYLOAD_MAX_BYTES = 10 * 1024 * 1024
|
|
12
|
+
SUMMARY_MAX_BYTES = 32 * 1024
|
|
13
|
+
REFERENCE_FIELDS = frozenset(
|
|
14
|
+
{
|
|
15
|
+
"issuer",
|
|
16
|
+
"workspace_id",
|
|
17
|
+
"project_id",
|
|
18
|
+
"user_id",
|
|
19
|
+
"snapshot_id",
|
|
20
|
+
"payload_sha256",
|
|
21
|
+
"summary_sha256",
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
SECRET_FIELDS = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"access_key_id",
|
|
27
|
+
"secret_access_key",
|
|
28
|
+
"session_token",
|
|
29
|
+
"private_key",
|
|
30
|
+
"encrypted_material",
|
|
31
|
+
"aws_access_key_id",
|
|
32
|
+
"aws_secret_access_key",
|
|
33
|
+
"aws_session_token",
|
|
34
|
+
"authorization",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EvidenceError(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def canonical_json(value: Any) -> str:
|
|
44
|
+
return json.dumps(
|
|
45
|
+
value, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def digest(value: Any) -> str:
|
|
50
|
+
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def model_destination(driver_id: str, environment: dict) -> dict:
|
|
54
|
+
if any(
|
|
55
|
+
environment.get(key) not in (None, "", "0", "false")
|
|
56
|
+
for key in (
|
|
57
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
58
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
59
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
60
|
+
)
|
|
61
|
+
):
|
|
62
|
+
raise EvidenceError("aws_evidence_unknown_provider")
|
|
63
|
+
defaults = {
|
|
64
|
+
"claude": ("anthropic", "https://api.anthropic.com"),
|
|
65
|
+
"copilot": ("github", "https://api.githubcopilot.com"),
|
|
66
|
+
"gemini": ("google", "https://generativelanguage.googleapis.com"),
|
|
67
|
+
}
|
|
68
|
+
explicit_keys = {
|
|
69
|
+
"claude": ("ANTHROPIC_BASE_URL",),
|
|
70
|
+
"mimo": ("ANTHROPIC_BASE_URL",),
|
|
71
|
+
"deepseek": ("DEEPSEEK_BASE_URL", "ANTHROPIC_BASE_URL"),
|
|
72
|
+
"copilot": ("COPILOT_API_URL", "GITHUB_COPILOT_BASE_URL", "GH_HOST"),
|
|
73
|
+
"gemini": ("GOOGLE_GEMINI_BASE_URL", "GEMINI_API_BASE_URL"),
|
|
74
|
+
"kimi": ("KIMI_BASE_URL", "OPENAI_BASE_URL"),
|
|
75
|
+
}
|
|
76
|
+
if driver_id not in explicit_keys:
|
|
77
|
+
raise EvidenceError("aws_evidence_unknown_provider")
|
|
78
|
+
endpoints = [environment[key] for key in explicit_keys[driver_id] if environment.get(key)]
|
|
79
|
+
if len(set(endpoints)) > 1 or (not endpoints and driver_id not in defaults):
|
|
80
|
+
raise EvidenceError("aws_evidence_unknown_provider")
|
|
81
|
+
endpoint = endpoints[0] if endpoints else defaults[driver_id][1]
|
|
82
|
+
parsed = urlsplit(endpoint)
|
|
83
|
+
known_hosts = {
|
|
84
|
+
"api.anthropic.com": "anthropic",
|
|
85
|
+
"api.githubcopilot.com": "github",
|
|
86
|
+
"api.individual.githubcopilot.com": "github",
|
|
87
|
+
"api.business.githubcopilot.com": "github",
|
|
88
|
+
"api.enterprise.githubcopilot.com": "github",
|
|
89
|
+
"generativelanguage.googleapis.com": "google",
|
|
90
|
+
"api.kimi.com": "moonshot",
|
|
91
|
+
"api.moonshot.cn": "moonshot",
|
|
92
|
+
"api.moonshot.ai": "moonshot",
|
|
93
|
+
"api.xiaomimimo.com": "xiaomi",
|
|
94
|
+
"api.deepseek.com": "deepseek",
|
|
95
|
+
}
|
|
96
|
+
provider = known_hosts.get(parsed.hostname or "")
|
|
97
|
+
if (
|
|
98
|
+
parsed.scheme != "https"
|
|
99
|
+
or parsed.username
|
|
100
|
+
or parsed.password
|
|
101
|
+
or not provider
|
|
102
|
+
or parsed.port not in (None, 443)
|
|
103
|
+
):
|
|
104
|
+
raise EvidenceError("aws_evidence_unknown_provider")
|
|
105
|
+
return {"model_provider": provider, "endpoint": endpoint.rstrip("/")}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def validate_reference(reference: Any) -> dict:
|
|
109
|
+
if not isinstance(reference, dict) or set(reference) != REFERENCE_FIELDS:
|
|
110
|
+
raise EvidenceError("aws_evidence_invalid_reference")
|
|
111
|
+
for field in ("workspace_id", "project_id", "user_id", "snapshot_id"):
|
|
112
|
+
value = reference[field]
|
|
113
|
+
if not isinstance(value, str):
|
|
114
|
+
raise EvidenceError("aws_evidence_invalid_reference")
|
|
115
|
+
try:
|
|
116
|
+
if str(UUID(value)) != value:
|
|
117
|
+
raise ValueError("Noncanonical identifier")
|
|
118
|
+
except ValueError:
|
|
119
|
+
raise EvidenceError("aws_evidence_invalid_reference") from None
|
|
120
|
+
for field in ("payload_sha256", "summary_sha256"):
|
|
121
|
+
if not isinstance(reference[field], str) or not re.fullmatch(
|
|
122
|
+
r"[0-9a-f]{64}", reference[field]
|
|
123
|
+
):
|
|
124
|
+
raise EvidenceError("aws_evidence_invalid_reference")
|
|
125
|
+
issuer = reference["issuer"]
|
|
126
|
+
if (
|
|
127
|
+
not isinstance(issuer, str)
|
|
128
|
+
or len(issuer) > 2048
|
|
129
|
+
or not issuer.startswith(("https://", "http://"))
|
|
130
|
+
):
|
|
131
|
+
raise EvidenceError("aws_evidence_invalid_reference")
|
|
132
|
+
return dict(reference)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _unique_object(pairs: list) -> dict:
|
|
136
|
+
result = {}
|
|
137
|
+
for key, value in pairs:
|
|
138
|
+
if key in result or key.lower() in SECRET_FIELDS:
|
|
139
|
+
raise EvidenceError("aws_evidence_invalid_payload")
|
|
140
|
+
result[key] = value
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _reject_constant(value: str) -> None:
|
|
145
|
+
raise EvidenceError("aws_evidence_invalid_payload")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_payload(reference: Any, metadata: Any, payload: Any, model_provider: str) -> dict:
|
|
149
|
+
reference = validate_reference(reference)
|
|
150
|
+
if not isinstance(payload, str) or len(payload.encode("utf-8")) > PAYLOAD_MAX_BYTES:
|
|
151
|
+
raise EvidenceError("aws_evidence_size_limit")
|
|
152
|
+
if hashlib.sha256(payload.encode("utf-8")).hexdigest() != reference["payload_sha256"]:
|
|
153
|
+
raise EvidenceError("aws_evidence_integrity_failed")
|
|
154
|
+
if not isinstance(metadata, dict) or metadata.get("status") not in ("ready", "partial"):
|
|
155
|
+
raise EvidenceError("aws_evidence_unavailable")
|
|
156
|
+
for field in REFERENCE_FIELDS - {"issuer"}:
|
|
157
|
+
if metadata.get(field) != reference[field]:
|
|
158
|
+
raise EvidenceError("aws_evidence_identity_mismatch")
|
|
159
|
+
policy = metadata.get("data_policy")
|
|
160
|
+
if (
|
|
161
|
+
not isinstance(policy, dict)
|
|
162
|
+
or policy.get("allow_agent_transfer") is not True
|
|
163
|
+
or not isinstance(policy.get("allowed_model_providers"), list)
|
|
164
|
+
or not model_provider
|
|
165
|
+
or model_provider not in policy.get("allowed_model_providers", [])
|
|
166
|
+
or type(policy.get("policy_version")) is not int
|
|
167
|
+
or policy["policy_version"] < 1
|
|
168
|
+
):
|
|
169
|
+
raise EvidenceError("aws_evidence_policy_denied")
|
|
170
|
+
try:
|
|
171
|
+
expires = datetime.fromisoformat(str(metadata["expires_at"]).replace("Z", "+00:00"))
|
|
172
|
+
if expires.tzinfo is None or expires <= datetime.now(expires.tzinfo):
|
|
173
|
+
raise ValueError("Expired")
|
|
174
|
+
evidence = json.loads(
|
|
175
|
+
payload, object_pairs_hook=_unique_object, parse_constant=_reject_constant
|
|
176
|
+
)
|
|
177
|
+
except (ValueError, TypeError, KeyError, RecursionError):
|
|
178
|
+
raise EvidenceError("aws_evidence_invalid_payload") from None
|
|
179
|
+
if (
|
|
180
|
+
not isinstance(evidence, dict)
|
|
181
|
+
or evidence.get("schema_version") != 1
|
|
182
|
+
or evidence.get("provider") != "aws_metrics"
|
|
183
|
+
):
|
|
184
|
+
raise EvidenceError("aws_evidence_invalid_payload")
|
|
185
|
+
if evidence.get("snapshot_id") != reference["snapshot_id"]:
|
|
186
|
+
raise EvidenceError("aws_evidence_identity_mismatch")
|
|
187
|
+
origin = evidence.get("origin")
|
|
188
|
+
if not isinstance(origin, dict) or any(
|
|
189
|
+
origin.get(key) != reference[field]
|
|
190
|
+
for key, field in (
|
|
191
|
+
("issuer", "issuer"),
|
|
192
|
+
("workspace_id", "workspace_id"),
|
|
193
|
+
("project_id", "project_id"),
|
|
194
|
+
("created_by_user_id", "user_id"),
|
|
195
|
+
)
|
|
196
|
+
):
|
|
197
|
+
raise EvidenceError("aws_evidence_identity_mismatch")
|
|
198
|
+
sources, resources, metrics = (
|
|
199
|
+
evidence.get("sources"),
|
|
200
|
+
evidence.get("resources"),
|
|
201
|
+
evidence.get("metrics"),
|
|
202
|
+
)
|
|
203
|
+
if not isinstance(sources, list) or not 1 <= len(sources) <= 8:
|
|
204
|
+
raise EvidenceError("aws_evidence_invalid_sources")
|
|
205
|
+
source_ids = set()
|
|
206
|
+
for source in sources:
|
|
207
|
+
if (
|
|
208
|
+
not isinstance(source, dict)
|
|
209
|
+
or not isinstance(source.get("source_id"), str)
|
|
210
|
+
or source["source_id"] in source_ids
|
|
211
|
+
or not re.fullmatch(r"\d{12}", str(source.get("account_id", "")))
|
|
212
|
+
or source.get("partition") not in ("aws", "aws-cn", "aws-us-gov")
|
|
213
|
+
or not isinstance(source.get("region"), str)
|
|
214
|
+
or not isinstance(source.get("aws_profile_id"), str)
|
|
215
|
+
or type(source.get("credential_version")) is not int
|
|
216
|
+
or source["credential_version"] < 1
|
|
217
|
+
):
|
|
218
|
+
raise EvidenceError("aws_evidence_invalid_sources")
|
|
219
|
+
source_ids.add(source["source_id"])
|
|
220
|
+
if (
|
|
221
|
+
not isinstance(resources, list)
|
|
222
|
+
or len(resources) > 20
|
|
223
|
+
or not isinstance(metrics, list)
|
|
224
|
+
or len(metrics) > 100
|
|
225
|
+
):
|
|
226
|
+
raise EvidenceError("aws_evidence_size_limit")
|
|
227
|
+
resource_sources = {}
|
|
228
|
+
for resource in resources:
|
|
229
|
+
if (
|
|
230
|
+
not isinstance(resource, dict)
|
|
231
|
+
or not isinstance(resource.get("resource_ref"), str)
|
|
232
|
+
or not isinstance(resource.get("source_id"), str)
|
|
233
|
+
or resource["resource_ref"] in resource_sources
|
|
234
|
+
or resource.get("source_id") not in source_ids
|
|
235
|
+
):
|
|
236
|
+
raise EvidenceError("aws_evidence_invalid_resources")
|
|
237
|
+
resource_sources[resource["resource_ref"]] = resource["source_id"]
|
|
238
|
+
metric_ids = set()
|
|
239
|
+
for metric in metrics:
|
|
240
|
+
if (
|
|
241
|
+
not isinstance(metric, dict)
|
|
242
|
+
or not isinstance(metric.get("evidence_ref"), str)
|
|
243
|
+
or not isinstance(metric.get("source_id"), str)
|
|
244
|
+
or not isinstance(metric.get("resource_ref"), str)
|
|
245
|
+
or metric["evidence_ref"] in metric_ids
|
|
246
|
+
or metric.get("source_id") not in source_ids
|
|
247
|
+
or resource_sources.get(metric.get("resource_ref")) != metric["source_id"]
|
|
248
|
+
or not isinstance(metric.get("data_status"), str)
|
|
249
|
+
):
|
|
250
|
+
raise EvidenceError("aws_evidence_invalid_metrics")
|
|
251
|
+
metric_ids.add(metric["evidence_ref"])
|
|
252
|
+
selection = evidence.get("selection")
|
|
253
|
+
if not isinstance(selection, dict) or selection.get("scope_mode") not in ("common", "market"):
|
|
254
|
+
raise EvidenceError("aws_evidence_invalid_selection")
|
|
255
|
+
if selection["scope_mode"] == "common" and "country_codes" in selection:
|
|
256
|
+
raise EvidenceError("aws_evidence_invalid_selection")
|
|
257
|
+
summary = {
|
|
258
|
+
**evidence,
|
|
259
|
+
"metrics": [
|
|
260
|
+
{key: value for key, value in metric.items() if key != "datapoints"}
|
|
261
|
+
for metric in metrics
|
|
262
|
+
],
|
|
263
|
+
}
|
|
264
|
+
try:
|
|
265
|
+
summary_json = canonical_json(summary)
|
|
266
|
+
except (ValueError, TypeError):
|
|
267
|
+
raise EvidenceError("aws_evidence_invalid_payload") from None
|
|
268
|
+
if len(summary_json.encode("utf-8")) > SUMMARY_MAX_BYTES:
|
|
269
|
+
raise EvidenceError("aws_evidence_size_limit")
|
|
270
|
+
if digest(summary) != reference["summary_sha256"]:
|
|
271
|
+
raise EvidenceError("aws_evidence_integrity_failed")
|
|
272
|
+
return {
|
|
273
|
+
"reference": reference,
|
|
274
|
+
"summary": summary,
|
|
275
|
+
"summary_json": summary_json,
|
|
276
|
+
"data_policy": dict(policy),
|
|
277
|
+
"expires_at": metadata["expires_at"],
|
|
278
|
+
"model_provider": model_provider,
|
|
279
|
+
}
|
|
@@ -1231,7 +1231,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
1231
1231
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
1232
1232
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
1233
1233
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
1234
|
-
DAEMON_VERSION = "1.
|
|
1234
|
+
DAEMON_VERSION = "1.64.1"
|
|
1235
1235
|
|
|
1236
1236
|
|
|
1237
1237
|
def _detect_client_type() -> str:
|
|
@@ -18681,10 +18681,37 @@ class RuntimeDaemon:
|
|
|
18681
18681
|
self._shutdown_event.set()
|
|
18682
18682
|
|
|
18683
18683
|
|
|
18684
|
+
def _start_desktop_parent_monitor(daemon, loop):
|
|
18685
|
+
"""Stop only an explicitly Desktop-owned daemon when its parent pipe closes."""
|
|
18686
|
+
if sys.platform != "win32" or os.environ.pop("FORGEXA_DESKTOP_PARENT_PIPE", None) != "1":
|
|
18687
|
+
return None
|
|
18688
|
+
stream = sys.stdin
|
|
18689
|
+
if stream is None:
|
|
18690
|
+
raise RuntimeError("Desktop parent pipe is unavailable")
|
|
18691
|
+
import threading
|
|
18692
|
+
|
|
18693
|
+
def monitor():
|
|
18694
|
+
try:
|
|
18695
|
+
while stream.read(1):
|
|
18696
|
+
pass
|
|
18697
|
+
except (OSError, ValueError):
|
|
18698
|
+
pass
|
|
18699
|
+
try:
|
|
18700
|
+
# Capture the running loop here, not in this background thread.
|
|
18701
|
+
loop.call_soon_threadsafe(daemon.handle_signal, signal.SIGTERM)
|
|
18702
|
+
except RuntimeError:
|
|
18703
|
+
pass # The daemon has already closed its loop.
|
|
18704
|
+
|
|
18705
|
+
thread = threading.Thread(target=monitor, name="desktop-parent-pipe", daemon=True)
|
|
18706
|
+
thread.start()
|
|
18707
|
+
return thread
|
|
18708
|
+
|
|
18709
|
+
|
|
18684
18710
|
async def main():
|
|
18685
18711
|
daemon = RuntimeDaemon()
|
|
18686
18712
|
|
|
18687
18713
|
loop = asyncio.get_event_loop()
|
|
18714
|
+
_start_desktop_parent_monitor(daemon, loop)
|
|
18688
18715
|
if sys.platform != "win32":
|
|
18689
18716
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
18690
18717
|
loop.add_signal_handler(sig, daemon.handle_signal, sig)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|