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.
Files changed (30) hide show
  1. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/PKG-INFO +1 -1
  2. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/agent_core.py +4 -2
  4. forgexa_cli-1.64.1/forgexa_cli/aws_evidence.py +279 -0
  5. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/daemon.py +28 -1
  6. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
  7. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/SOURCES.txt +1 -0
  8. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/pyproject.toml +1 -1
  9. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/README.md +0 -0
  10. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/_build_config.py +0 -0
  11. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/_local_bind.py +0 -0
  12. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/autoupgrade.py +0 -0
  13. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/main.py +0 -0
  14. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/py.typed +0 -0
  15. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/runtime_evidence.py +0 -0
  16. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli/zcode_protocol.py +0 -0
  17. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  18. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
  19. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/requires.txt +0 -0
  20. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/forgexa_cli.egg-info/top_level.txt +0 -0
  21. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/setup.cfg +0 -0
  22. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_auth_and_runtime_commands.py +0 -0
  23. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_autoupgrade.py +0 -0
  24. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_check_command.py +0 -0
  25. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
  26. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_local_bind_commands.py +0 -0
  27. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_runtime_credentials.py +0 -0
  28. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_session_credentials.py +0 -0
  29. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_silent_install.py +0 -0
  30. {forgexa_cli-1.63.1 → forgexa_cli-1.64.1}/tests/test_upgrade_observability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.63.1
3
+ Version: 1.64.1
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.63.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 when the argv
3667
- form would exceed the platform command-line limit.
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.63.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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.63.1
3
+ Version: 1.64.1
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -5,6 +5,7 @@ forgexa_cli/_build_config.py
5
5
  forgexa_cli/_local_bind.py
6
6
  forgexa_cli/agent_core.py
7
7
  forgexa_cli/autoupgrade.py
8
+ forgexa_cli/aws_evidence.py
8
9
  forgexa_cli/daemon.py
9
10
  forgexa_cli/main.py
10
11
  forgexa_cli/py.typed
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.63.1"
3
+ version = "1.64.1"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
File without changes
File without changes