ainativelang 1.2.8__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.
Files changed (194) hide show
  1. adapters/__init__.py +29 -0
  2. adapters/agent.py +85 -0
  3. adapters/base.py +183 -0
  4. adapters/core.py +146 -0
  5. adapters/core_patch.py +10 -0
  6. adapters/crm.py +183 -0
  7. adapters/demo_mock.py +65 -0
  8. adapters/embedding_memory.py +248 -0
  9. adapters/extras.py +105 -0
  10. adapters/github.py +190 -0
  11. adapters/langchain_tool.py +152 -0
  12. adapters/llm_query.py +171 -0
  13. adapters/mock.py +193 -0
  14. adapters/openclaw_defaults.py +18 -0
  15. adapters/openclaw_integration.py +672 -0
  16. adapters/openclaw_memory.py +179 -0
  17. adapters/ptc_runner.py +298 -0
  18. adapters/tiktok.py +102 -0
  19. adapters/tool_registry.py +90 -0
  20. adapters/vector_memory.py +137 -0
  21. ainativelang-1.2.8.dist-info/METADATA +1155 -0
  22. ainativelang-1.2.8.dist-info/RECORD +194 -0
  23. ainativelang-1.2.8.dist-info/WHEEL +5 -0
  24. ainativelang-1.2.8.dist-info/entry_points.txt +20 -0
  25. ainativelang-1.2.8.dist-info/licenses/LICENSE +201 -0
  26. ainativelang-1.2.8.dist-info/licenses/LICENSE.docs +15 -0
  27. ainativelang-1.2.8.dist-info/licenses/NOTICE +21 -0
  28. ainativelang-1.2.8.dist-info/top_level.txt +8 -0
  29. cli/__init__.py +1 -0
  30. cli/main.py +1390 -0
  31. compiler_diagnostics.py +315 -0
  32. compiler_v2.py +5072 -0
  33. grammar_constraint.py +71 -0
  34. runtime/__init__.py +6 -0
  35. runtime/adapters/__init__.py +20 -0
  36. runtime/adapters/base.py +88 -0
  37. runtime/adapters/builtins.py +136 -0
  38. runtime/adapters/executor_bridge.py +49 -0
  39. runtime/adapters/fs.py +91 -0
  40. runtime/adapters/http.py +129 -0
  41. runtime/adapters/memory.py +656 -0
  42. runtime/adapters/replay.py +51 -0
  43. runtime/adapters/sqlite.py +74 -0
  44. runtime/adapters/tools.py +30 -0
  45. runtime/adapters/wasm.py +105 -0
  46. runtime/compat.py +114 -0
  47. runtime/engine.py +1777 -0
  48. runtime/sandbox_shim.py +82 -0
  49. runtime/values.py +102 -0
  50. runtime/wrappers/__init__.py +6 -0
  51. runtime/wrappers/langgraph_wrapper.py +75 -0
  52. runtime/wrappers/temporal_wrapper.py +75 -0
  53. runtime.py +9 -0
  54. scripts/__init__.py +1 -0
  55. scripts/ainl_mcp_server.py +808 -0
  56. scripts/ainl_memory_append_cli.py +56 -0
  57. scripts/ainl_trace_viewer.py +102 -0
  58. scripts/analyze_eval_trends.py +209 -0
  59. scripts/apollo_migrate_state.py +73 -0
  60. scripts/auto_tune_ainl_caps.py +310 -0
  61. scripts/benchmark_ollama.py +428 -0
  62. scripts/benchmark_runtime.py +1371 -0
  63. scripts/benchmark_size.py +1687 -0
  64. scripts/bridge_sizing_probe.py +176 -0
  65. scripts/build_canonical_curriculum.py +224 -0
  66. scripts/build_canonical_training_pack.py +146 -0
  67. scripts/build_failure_boost_dataset.py +208 -0
  68. scripts/build_regression_supervision.py +304 -0
  69. scripts/capabilities_filter.py +185 -0
  70. scripts/capabilities_report.py +123 -0
  71. scripts/check_all_nonstrict.py +59 -0
  72. scripts/check_all_strict.py +52 -0
  73. scripts/check_docs_contracts.py +202 -0
  74. scripts/check_python_baseline.py +31 -0
  75. scripts/compare_benchmark_json.py +441 -0
  76. scripts/compat_gate.py +57 -0
  77. scripts/compat_report.py +80 -0
  78. scripts/compile_daily_lead_summary.py +29 -0
  79. scripts/contracts_gate.py +33 -0
  80. scripts/convert_to_training.py +149 -0
  81. scripts/cron_drift_check.py +56 -0
  82. scripts/curate_corpus.py +184 -0
  83. scripts/dump_first_step.py +19 -0
  84. scripts/emit_langgraph.py +123 -0
  85. scripts/emit_temporal.py +174 -0
  86. scripts/eval_finetuned_model.py +1220 -0
  87. scripts/eval_ollama.py +261 -0
  88. scripts/evaluate_corpus.py +153 -0
  89. scripts/export_memory_daily_log_markdown.py +55 -0
  90. scripts/export_memory_records.py +87 -0
  91. scripts/feedback_loop.py +40 -0
  92. scripts/finetune_ainl.py +323 -0
  93. scripts/gen_tool_api_v2_tools.py +77 -0
  94. scripts/generate_corpus.py +324 -0
  95. scripts/generate_negative_examples.py +109 -0
  96. scripts/generate_synthetic_dataset.py +174 -0
  97. scripts/import_memory_markdown.py +58 -0
  98. scripts/import_memory_records.py +104 -0
  99. scripts/infer_ainl_lora.py +352 -0
  100. scripts/inspect_ainl.py +154 -0
  101. scripts/memory_retention_report.py +340 -0
  102. scripts/migrate_memory_legacy.py +78 -0
  103. scripts/mock_executor_bridge.py +62 -0
  104. scripts/operator_only_adapter_audit.py +262 -0
  105. scripts/patch_mode.py +81 -0
  106. scripts/plan_delta.py +65 -0
  107. scripts/policy_gate.py +61 -0
  108. scripts/refresh_snapshot_fixtures.py +141 -0
  109. scripts/regenerate_ir_snapshots.py +64 -0
  110. scripts/render_graph.py +105 -0
  111. scripts/retrieval_playbook.py +95 -0
  112. scripts/run_ainl_tests.py +45 -0
  113. scripts/run_daily_lead_summary.py +79 -0
  114. scripts/run_intelligence.py +163 -0
  115. scripts/run_runtime_tests.py +36 -0
  116. scripts/run_test_profiles.py +120 -0
  117. scripts/run_tests_and_emit.py +495 -0
  118. scripts/run_wrapper_ainl.py +57 -0
  119. scripts/runtime_runner_service.py +679 -0
  120. scripts/serve_dashboard.py +33 -0
  121. scripts/summarize_runs.py +129 -0
  122. scripts/sweep_checkpoints.py +252 -0
  123. scripts/teacher_distill_dataset.py +237 -0
  124. scripts/test_lsp.py +123 -0
  125. scripts/test_strict_compile.py +30 -0
  126. scripts/tool_api.py +156 -0
  127. scripts/validate_ainl.py +468 -0
  128. scripts/validate_coordination_mailbox.py +17 -0
  129. scripts/validate_corpus.py +109 -0
  130. scripts/validate_examples.py +34 -0
  131. scripts/validate_memory_records.py +15 -0
  132. scripts/validate_s_cron_schedules.py +123 -0
  133. scripts/validator_app.py +93 -0
  134. scripts/viability.py +197 -0
  135. scripts/visualize_ainl.py +643 -0
  136. tooling/__init__.py +1 -0
  137. tooling/adapter_manifest.json +472 -0
  138. tooling/adapter_manifest.py +19 -0
  139. tooling/ainl_profile_catalog.py +73 -0
  140. tooling/ainl_profiles.json +64 -0
  141. tooling/ainl_tool_api.schema.json +119 -0
  142. tooling/artifact_policy.json +58 -0
  143. tooling/artifact_profiles.json +147 -0
  144. tooling/bench_metrics.py +119 -0
  145. tooling/benchmark_manifest.json +64 -0
  146. tooling/benchmark_runtime_ci.json +2409 -0
  147. tooling/benchmark_runtime_results.json +2069 -0
  148. tooling/benchmark_size.json +51862 -0
  149. tooling/benchmark_size_ci.json +1999 -0
  150. tooling/bot_bootstrap.json +50 -0
  151. tooling/canonical_curriculum.json +126 -0
  152. tooling/canonical_training_pack.json +329 -0
  153. tooling/capabilities.json +795 -0
  154. tooling/capabilities.schema.json +266 -0
  155. tooling/capability_grant.py +197 -0
  156. tooling/coordination_validator.py +294 -0
  157. tooling/cron_registry.json +64 -0
  158. tooling/doctor.py +151 -0
  159. tooling/effect_analysis.py +433 -0
  160. tooling/emission_planner.py +192 -0
  161. tooling/emit_targets.py +21 -0
  162. tooling/graph_api.py +294 -0
  163. tooling/graph_diff.py +128 -0
  164. tooling/graph_export.py +108 -0
  165. tooling/graph_normalize.py +244 -0
  166. tooling/graph_rewrite.py +286 -0
  167. tooling/graph_safe_edit.py +153 -0
  168. tooling/graph_slice.py +100 -0
  169. tooling/intelligence_budget_hydrate.py +109 -0
  170. tooling/ir_canonical.py +148 -0
  171. tooling/ir_compact.py +261 -0
  172. tooling/ir_compact_patch.py +271 -0
  173. tooling/markdown_importer.py +661 -0
  174. tooling/mcp_ecosystem_import.py +210 -0
  175. tooling/mcp_exposure_profiles.json +26 -0
  176. tooling/mcp_host_install.py +285 -0
  177. tooling/memory_bridge.py +205 -0
  178. tooling/memory_markdown_bridge.py +130 -0
  179. tooling/memory_markdown_import.py +164 -0
  180. tooling/memory_migrate.py +201 -0
  181. tooling/memory_validator.py +297 -0
  182. tooling/openclaw_install.py +49 -0
  183. tooling/oversight.py +308 -0
  184. tooling/policy_validator.py +120 -0
  185. tooling/project_provenance.json +34 -0
  186. tooling/result_summary.py +67 -0
  187. tooling/security_profiles.json +127 -0
  188. tooling/security_report.py +212 -0
  189. tooling/step_focus.py +67 -0
  190. tooling/support_matrix.json +234 -0
  191. tooling/tool_api_v2.tools.json +737 -0
  192. tooling/trace_focus.py +31 -0
  193. tooling/zeroclaw_bridge.py +94 -0
  194. tooling/zeroclaw_install.py +51 -0
adapters/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ Adapters: pluggable backends for R (db, api), P (pay), Sc (scrape).
3
+ Replace mock adapters with real implementations (Prisma, Stripe, etc.).
4
+ """
5
+ from .base import (
6
+ AdapterRegistry,
7
+ APIAdapter,
8
+ AuthAdapter,
9
+ CacheAdapter,
10
+ DBAdapter,
11
+ PayAdapter,
12
+ QueueAdapter,
13
+ ScrapeAdapter,
14
+ TxnAdapter,
15
+ )
16
+ from .mock import mock_registry
17
+
18
+ __all__ = [
19
+ "AdapterRegistry",
20
+ "DBAdapter",
21
+ "APIAdapter",
22
+ "AuthAdapter",
23
+ "CacheAdapter",
24
+ "PayAdapter",
25
+ "QueueAdapter",
26
+ "ScrapeAdapter",
27
+ "TxnAdapter",
28
+ "mock_registry",
29
+ ]
adapters/agent.py ADDED
@@ -0,0 +1,85 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List
5
+
6
+ from runtime.adapters.base import RuntimeAdapter, AdapterError
7
+
8
+
9
+ class AgentAdapter(RuntimeAdapter):
10
+ """Extension/OpenClaw agent coordination adapter (local, file-backed).
11
+
12
+ This adapter is:
13
+ - extension-level and noncanonical,
14
+ - local-only and file-backed,
15
+ - sandboxed under AINL_AGENT_ROOT (default: /tmp/ainl_agents).
16
+ """
17
+
18
+ def _root(self) -> Path:
19
+ raw = os.getenv("AINL_AGENT_ROOT", "/tmp/ainl_agents")
20
+ # Disallow using filesystem root as sandbox; that defeats the boundary.
21
+ if os.path.abspath(raw) == os.path.sep:
22
+ raise AdapterError("AINL_AGENT_ROOT must not be filesystem root")
23
+ root = Path(raw).resolve()
24
+ root.mkdir(parents=True, exist_ok=True)
25
+ return root
26
+
27
+ def _safe_path(self, rel: str) -> Path:
28
+ root = self._root()
29
+ target = (root / str(rel)).resolve()
30
+ if target != root and root not in target.parents:
31
+ raise AdapterError("agent path escapes AINL_AGENT_ROOT sandbox")
32
+ return target
33
+
34
+ def call(self, target: str, args: List[Any], context: Dict[str, Any]) -> Any:
35
+ verb = (target or "").strip()
36
+
37
+ if verb == "send_task":
38
+ if not args:
39
+ raise AdapterError("agent.send_task requires at least one argument (task envelope)")
40
+
41
+ envelope = args[0]
42
+ if not isinstance(envelope, dict):
43
+ raise AdapterError("agent.send_task expects first argument to be a JSON object envelope")
44
+
45
+ # Optional second argument must not be treated as an arbitrary path; reject
46
+ # attempts to override the tasks file location to preserve the AINL_AGENT_ROOT
47
+ # sandbox boundary.
48
+ if len(args) > 1:
49
+ raise AdapterError("agent.send_task does not accept explicit path arguments; tasks file is fixed under AINL_AGENT_ROOT")
50
+
51
+ # Tasks file path is fixed by convention; callers should not control it.
52
+ path = self._safe_path("tasks/openclaw_agent_tasks.jsonl")
53
+
54
+ try:
55
+ line = json.dumps(envelope, sort_keys=True)
56
+ except Exception as e:
57
+ raise AdapterError(f"agent.send_task could not serialize envelope: {e}") from e
58
+
59
+ path.parent.mkdir(parents=True, exist_ok=True)
60
+ with path.open("a", encoding="utf-8") as f:
61
+ f.write(line + "\n")
62
+
63
+ task_id = str(envelope.get("task_id") or "")
64
+ return task_id
65
+
66
+ if verb == "read_result":
67
+ if not args:
68
+ raise AdapterError("agent.read_result requires task_id")
69
+ task_id = str(args[0])
70
+ # Treat task_id as an identifier, not a path fragment.
71
+ if "/" in task_id or ".." in task_id:
72
+ raise AdapterError("agent.read_result task_id must not contain path separators or escape AINL_AGENT_ROOT sandbox")
73
+ rel_path = f"results/{task_id}.json"
74
+ path = self._safe_path(rel_path)
75
+ if not path.exists() or not path.is_file():
76
+ raise AdapterError("agent.read_result target does not exist")
77
+ try:
78
+ data = json.loads(path.read_text(encoding="utf-8"))
79
+ except Exception as e:
80
+ raise AdapterError(f"agent.read_result failed to parse JSON: {e}") from e
81
+ if not isinstance(data, dict):
82
+ raise AdapterError("agent.read_result expects JSON object result")
83
+ return data
84
+
85
+ raise AdapterError(f"agent supports only send_task/read_result (got {target!r})")
adapters/base.py ADDED
@@ -0,0 +1,183 @@
1
+ """
2
+ Adapter interfaces for AI-Native Lang runtime.
3
+ Implement these to plug in real backends (Prisma, Stripe, HTTP, cache, queue, txn, auth).
4
+ """
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, List, Optional
7
+
8
+
9
+ class DBAdapter(ABC):
10
+ """Backend for db.F (find), db.G (get one), db.P (create), db.D (delete)."""
11
+
12
+ @abstractmethod
13
+ def find(self, entity: str, fields: str = "*") -> List[Dict[str, Any]]:
14
+ """Return list of entities (e.g. db.F Product *)."""
15
+ pass
16
+
17
+ def get(self, entity: str, id_value: Any) -> Optional[Dict[str, Any]]:
18
+ """Return one entity by id (e.g. db.G Product 1)."""
19
+ rows = self.find(entity, "*")
20
+ for r in rows:
21
+ if r.get("id") == id_value:
22
+ return r
23
+ return None
24
+
25
+ def create(self, entity: str, data: Dict[str, Any]) -> Dict[str, Any]:
26
+ """Create one entity (e.g. db.P Order {...})."""
27
+ raise NotImplementedError("db.P")
28
+
29
+ def delete(self, entity: str, id_value: Any) -> bool:
30
+ """Delete one entity (e.g. db.D Order 1)."""
31
+ raise NotImplementedError("db.D")
32
+
33
+
34
+ class APIAdapter(ABC):
35
+ """Backend for api.G (GET), api.P (POST), etc."""
36
+
37
+ @abstractmethod
38
+ def get(self, path: str) -> Any:
39
+ """HTTP GET path -> response body."""
40
+ pass
41
+
42
+ def post(self, path: str, body: Optional[Dict] = None) -> Any:
43
+ """HTTP POST path with body."""
44
+ raise NotImplementedError("api.P")
45
+
46
+
47
+ class PayAdapter(ABC):
48
+ """Backend for P (payment intent)."""
49
+
50
+ @abstractmethod
51
+ def create_intent(
52
+ self,
53
+ name: str,
54
+ amount: str,
55
+ currency: str,
56
+ desc: str = "",
57
+ ) -> Dict[str, Any]:
58
+ """Create payment intent; return { client_secret, ... }."""
59
+ pass
60
+
61
+
62
+ class ScrapeAdapter(ABC):
63
+ """Backend for Sc (scrape)."""
64
+
65
+ @abstractmethod
66
+ def scrape(self, name: str, url: str, selectors: Dict[str, str]) -> Dict[str, Any]:
67
+ """Run scraper; return dict of field -> value."""
68
+ pass
69
+
70
+
71
+ class CacheAdapter(ABC):
72
+ """Backend for cache capability steps."""
73
+
74
+ @abstractmethod
75
+ def get(self, namespace: str, key: str) -> Any:
76
+ """Lookup cache value by namespace+key; return None on miss."""
77
+ pass
78
+
79
+ @abstractmethod
80
+ def set(self, namespace: str, key: str, value: Any, ttl_s: int = 0) -> None:
81
+ """Store cache value with optional ttl in seconds."""
82
+ pass
83
+
84
+
85
+ class QueueAdapter(ABC):
86
+ """Backend for queue capability steps."""
87
+
88
+ @abstractmethod
89
+ def push(self, queue: str, value: Any) -> str:
90
+ """Push value to queue and return message id."""
91
+ pass
92
+
93
+
94
+ class TxnAdapter(ABC):
95
+ """Backend for transaction capability steps."""
96
+
97
+ @abstractmethod
98
+ def begin(self, name: str) -> str:
99
+ """Start transaction scope and return transaction id."""
100
+ pass
101
+
102
+ @abstractmethod
103
+ def commit(self, name: str) -> None:
104
+ """Commit transaction scope."""
105
+ pass
106
+
107
+ @abstractmethod
108
+ def rollback(self, name: str) -> None:
109
+ """Rollback transaction scope."""
110
+ pass
111
+
112
+
113
+ class AuthAdapter(ABC):
114
+ """Backend for auth capability checks."""
115
+
116
+ @abstractmethod
117
+ def validate(self, token_or_value: str) -> bool:
118
+ """Return True when auth value/token is valid."""
119
+ pass
120
+
121
+
122
+ class AdapterRegistry:
123
+ """Holds runtime adapters. Used by ExecutionEngine."""
124
+
125
+ def __init__(
126
+ self,
127
+ db: Optional[DBAdapter] = None,
128
+ api: Optional[APIAdapter] = None,
129
+ pay: Optional[PayAdapter] = None,
130
+ scrape: Optional[ScrapeAdapter] = None,
131
+ cache: Optional[CacheAdapter] = None,
132
+ queue: Optional[QueueAdapter] = None,
133
+ txn: Optional[TxnAdapter] = None,
134
+ auth: Optional[AuthAdapter] = None,
135
+ ):
136
+ self.db = db
137
+ self.api = api
138
+ self.pay = pay
139
+ self.scrape = scrape
140
+ self.cache = cache
141
+ self.queue = queue
142
+ self.txn = txn
143
+ self.auth = auth
144
+
145
+ def get_db(self) -> DBAdapter:
146
+ if self.db is None:
147
+ raise RuntimeError("No db adapter registered")
148
+ return self.db
149
+
150
+ def get_api(self) -> APIAdapter:
151
+ if self.api is None:
152
+ raise RuntimeError("No api adapter registered")
153
+ return self.api
154
+
155
+ def get_pay(self) -> PayAdapter:
156
+ if self.pay is None:
157
+ raise RuntimeError("No pay adapter registered")
158
+ return self.pay
159
+
160
+ def get_scrape(self) -> ScrapeAdapter:
161
+ if self.scrape is None:
162
+ raise RuntimeError("No scrape adapter registered")
163
+ return self.scrape
164
+
165
+ def get_cache(self) -> CacheAdapter:
166
+ if self.cache is None:
167
+ raise RuntimeError("No cache adapter registered")
168
+ return self.cache
169
+
170
+ def get_queue(self) -> QueueAdapter:
171
+ if self.queue is None:
172
+ raise RuntimeError("No queue adapter registered")
173
+ return self.queue
174
+
175
+ def get_txn(self) -> TxnAdapter:
176
+ if self.txn is None:
177
+ raise RuntimeError("No txn adapter registered")
178
+ return self.txn
179
+
180
+ def get_auth(self) -> AuthAdapter:
181
+ if self.auth is None:
182
+ raise RuntimeError("No auth adapter registered")
183
+ return self.auth
adapters/core.py ADDED
@@ -0,0 +1,146 @@
1
+ # Core builtin adapter for AINL
2
+ # Provides fundamental operations: now, math, string, collection utilities
3
+
4
+ import time
5
+
6
+ def core_now(frame, *args):
7
+ """Return current timestamp (seconds)."""
8
+ return time.time()
9
+
10
+ def add(frame, a, b):
11
+ return a + b
12
+
13
+ def sub(frame, a, b):
14
+ return a - b
15
+
16
+ def mul(frame, a, b):
17
+ return a * b
18
+
19
+ def div(frame, a, b):
20
+ if b == 0:
21
+ raise ZeroDivisionError("division by zero")
22
+ return a / b
23
+
24
+ def int_div(frame, a, b):
25
+ return a // b
26
+
27
+ def mod(frame, a, b):
28
+ return a % b
29
+
30
+ def pow_(frame, a, b):
31
+ return a ** b
32
+
33
+ def len_(frame, value):
34
+ if isinstance(value, (list, tuple, str, dict)):
35
+ return len(value)
36
+ raise TypeError("len() expects a collection")
37
+
38
+ def concat(frame, a, b):
39
+ return str(a) + str(b)
40
+
41
+ def join(frame, sep, items):
42
+ return sep.join(str(x) for x in items)
43
+
44
+ def stringify(frame, value):
45
+ return str(value)
46
+
47
+ def lt(frame, a, b):
48
+ return a < b
49
+
50
+ def gt(frame, a, b):
51
+ return a > b
52
+
53
+ def lte(frame, a, b):
54
+ return a <= b
55
+
56
+ def gte(frame, a, b):
57
+ return a >= b
58
+
59
+ def eq(frame, a, b):
60
+ return a == b
61
+
62
+ def ne(frame, a, b):
63
+ return a != b
64
+
65
+ # --- String operations ---
66
+ def split(frame, s, sep):
67
+ """Split string by separator (string). Returns list."""
68
+ if not isinstance(s, str):
69
+ s = str(s)
70
+ if not isinstance(sep, str):
71
+ sep = str(sep)
72
+ return s.split(sep)
73
+
74
+ def contains(frame, s, substr):
75
+ """Return True if substr in s."""
76
+ return substr in s
77
+
78
+ def starts_with(frame, s, prefix):
79
+ return s.startswith(prefix)
80
+
81
+ def ends_with(frame, s, suffix):
82
+ return s.endswith(suffix)
83
+
84
+ def substr(frame, s, start, length=None):
85
+ """Return substring. If length omitted, to end."""
86
+ if not isinstance(s, str):
87
+ s = str(s)
88
+ if length is None:
89
+ return s[start:]
90
+ return s[start:start+length]
91
+
92
+ def replace(frame, s, old, new):
93
+ return s.replace(old, new)
94
+
95
+ def to_lower(frame, s):
96
+ return s.lower()
97
+
98
+ def to_upper(frame, s):
99
+ return s.upper()
100
+
101
+ def trim(frame, s):
102
+ return s.strip()
103
+
104
+ # Map of verbs to functions
105
+ VERBS = {
106
+ 'now': core_now,
107
+ 'add': add,
108
+ 'sub': sub,
109
+ 'mul': mul,
110
+ 'div': div,
111
+ 'idiv': int_div,
112
+ 'mod': mod,
113
+ 'pow': pow_,
114
+ 'len': len_,
115
+ 'concat': concat,
116
+ 'join': join,
117
+ 'stringify': stringify,
118
+ 'lt': lt,
119
+ 'gt': gt,
120
+ 'lte': lte,
121
+ 'gte': gte,
122
+ 'eq': eq,
123
+ 'ne': ne,
124
+ # string
125
+ 'split': split,
126
+ 'contains': contains,
127
+ 'starts_with': starts_with,
128
+ 'ends_with': ends_with,
129
+ 'substr': substr,
130
+ 'replace': replace,
131
+ 'to_lower': to_lower,
132
+ 'to_upper': to_upper,
133
+ 'trim': trim,
134
+ }
135
+
136
+ def core_registry():
137
+ return {
138
+ "desc": "Core builtins",
139
+ "module": "adapters.core",
140
+ "verbs": {verb: {} for verb in VERBS.keys()}
141
+ }
142
+
143
+ def get(verb):
144
+ if verb in VERBS:
145
+ return VERBS[verb]
146
+ raise KeyError(f"Unknown core verb: {verb}")
adapters/core_patch.py ADDED
@@ -0,0 +1,10 @@
1
+ # Patch to ensure core adapter is present in openclaw_monitor_registry
2
+ # This module should be imported before the engine is created.
3
+
4
+ from . import core
5
+ from . import openclaw_integration as oi
6
+
7
+ # Call the function to get the registry dict and inject core if missing
8
+ _reg = oi.openclaw_monitor_registry()
9
+ if 'core' not in _reg:
10
+ _reg['core'] = core.core_registry()
adapters/crm.py ADDED
@@ -0,0 +1,183 @@
1
+ """Local CRM HTTP adapter: GitHub intelligence + lead upsert (paths configurable via env)."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import logging
7
+ import os
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List
11
+
12
+ import requests
13
+
14
+ from runtime.adapters.base import RuntimeAdapter, AdapterError
15
+
16
+ from adapters.openclaw_defaults import DEFAULT_CRM_API_BASE
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ DEFAULT_BASE = os.getenv("CRM_API_BASE", DEFAULT_CRM_API_BASE).rstrip("/")
21
+ PATH_DIGEST = os.getenv("CRM_PATH_GITHUB_INTEL_DIGEST", "/api/github-intelligence/digests")
22
+ PATH_FIND = os.getenv("CRM_PATH_GITHUB_INTEL_FIND", "/api/github-intelligence")
23
+ PATH_LEADS = os.getenv("CRM_PATH_LEADS_UPSERT", "/api/leads")
24
+
25
+
26
+ def _dry_run(context: Dict[str, Any]) -> bool:
27
+ v = context.get("dry_run")
28
+ if v in (True, 1, "1", "true", "True", "yes"):
29
+ return True
30
+ return os.environ.get("AINL_DRY_RUN", "").strip().lower() in ("1", "true", "yes")
31
+
32
+
33
+ class _JsonFileCache:
34
+ def __init__(self) -> None:
35
+ self.path = Path(os.getenv("MONITOR_CACHE_JSON", "/tmp/monitor_state.json")).expanduser()
36
+
37
+ def _load(self) -> Dict[str, Any]:
38
+ try:
39
+ with open(self.path, "r", encoding="utf-8") as f:
40
+ data = json.load(f)
41
+ return data if isinstance(data, dict) else {}
42
+ except (OSError, json.JSONDecodeError):
43
+ return {}
44
+
45
+ def _save(self, data: Dict[str, Any]) -> None:
46
+ try:
47
+ self.path.parent.mkdir(parents=True, exist_ok=True)
48
+ with open(self.path, "w", encoding="utf-8") as f:
49
+ json.dump(data, f)
50
+ except OSError as e:
51
+ logger.warning("crm cache save failed: %s", e)
52
+
53
+ def get(self, namespace: str, key: str) -> Any:
54
+ return self._load().get(namespace, {}).get(key)
55
+
56
+ def set(self, namespace: str, key: str, value: Any) -> None:
57
+ data = self._load()
58
+ data.setdefault(namespace, {})[key] = value
59
+ self._save(data)
60
+
61
+
62
+ def _as_dict(data: Any) -> Dict[str, Any]:
63
+ if isinstance(data, dict):
64
+ return data
65
+ if isinstance(data, str):
66
+ try:
67
+ o = json.loads(data)
68
+ return o if isinstance(o, dict) else {}
69
+ except json.JSONDecodeError:
70
+ return {}
71
+ return {}
72
+
73
+
74
+ class CrmAdapter(RuntimeAdapter):
75
+ """
76
+ Adapter group: crm
77
+ Verbs: create_github_intelligence_digest, find_github_intelligence, upsert_lead
78
+ """
79
+
80
+ def __init__(self) -> None:
81
+ self.base = DEFAULT_BASE
82
+ self._cache = _JsonFileCache()
83
+ self._session = requests.Session()
84
+
85
+ def call(self, target: str, args: List[Any], context: Dict[str, Any]) -> Any:
86
+ verb = str(target or "").strip().lower()
87
+ dry = _dry_run(context)
88
+
89
+ if verb == "create_github_intelligence_digest":
90
+ if len(args) < 3:
91
+ raise AdapterError(
92
+ "create_github_intelligence_digest requires generatedAt, newCount, repos"
93
+ )
94
+ generated_at = str(args[0])
95
+ try:
96
+ new_count = int(args[1]) if args[1] is not None else 0
97
+ except (TypeError, ValueError):
98
+ new_count = 0
99
+ repos = args[2]
100
+ if not isinstance(repos, list):
101
+ repos = []
102
+ body = {"generatedAt": generated_at, "newCount": new_count, "repos": repos}
103
+ if dry:
104
+ logger.info("[dry_run] crm.create_github_intelligence_digest — no POST")
105
+ return 0
106
+ url = f"{self.base}{PATH_DIGEST}"
107
+ r = self._session.post(url, json=body, timeout=30)
108
+ if r.status_code not in (200, 201):
109
+ logger.warning("CRM digest POST %s %s", r.status_code, (r.text or "")[:300])
110
+ return 0
111
+ try:
112
+ data = r.json()
113
+ except json.JSONDecodeError:
114
+ return 0
115
+ if isinstance(data, dict):
116
+ for k in ("id", "digestId", "reportId"):
117
+ if k in data and data[k] is not None:
118
+ try:
119
+ return int(data[k])
120
+ except (TypeError, ValueError):
121
+ continue
122
+ return 1
123
+
124
+ if verb == "find_github_intelligence":
125
+ since = args[0] if len(args) > 0 else None
126
+ try:
127
+ limit = int(args[1]) if len(args) > 1 and args[1] is not None else 20
128
+ except (TypeError, ValueError):
129
+ limit = 20
130
+ params: Dict[str, Any] = {"limit": limit}
131
+ if since not in (None, ""):
132
+ params["since"] = str(since)
133
+ sig = json.dumps(params, sort_keys=True)
134
+ ck = "find:" + hashlib.md5(sig.encode("utf-8")).hexdigest()
135
+ hit = self._cache.get("crm", ck)
136
+ if isinstance(hit, dict) and time.time() - float(hit.get("ts", 0)) < 60:
137
+ return hit.get("data", [])
138
+ if dry:
139
+ return []
140
+ url = f"{self.base}{PATH_FIND}"
141
+ r = self._session.get(url, params=params, timeout=30)
142
+ if r.status_code != 200:
143
+ logger.warning("CRM find_github_intelligence GET %s", r.status_code)
144
+ return []
145
+ try:
146
+ data = r.json()
147
+ except json.JSONDecodeError:
148
+ return []
149
+ if isinstance(data, list):
150
+ out = data
151
+ elif isinstance(data, dict) and isinstance(data.get("items"), list):
152
+ out = data["items"]
153
+ elif isinstance(data, dict) and isinstance(data.get("results"), list):
154
+ out = data["results"]
155
+ else:
156
+ out = []
157
+ self._cache.set("crm", ck, {"ts": time.time(), "data": out})
158
+ return out
159
+
160
+ if verb == "upsert_lead":
161
+ if not args:
162
+ raise AdapterError("upsert_lead requires lead_data: dict")
163
+ lead = _as_dict(args[0])
164
+ if dry:
165
+ logger.info("[dry_run] crm.upsert_lead — no POST")
166
+ return 0
167
+ url = f"{self.base}{PATH_LEADS}"
168
+ r = self._session.post(url, json=lead, timeout=30)
169
+ if r.status_code not in (200, 201):
170
+ logger.warning("CRM upsert_lead POST %s", r.status_code)
171
+ return 0
172
+ try:
173
+ data = r.json()
174
+ except json.JSONDecodeError:
175
+ return 1
176
+ if isinstance(data, dict) and data.get("id") is not None:
177
+ try:
178
+ return int(data["id"])
179
+ except (TypeError, ValueError):
180
+ pass
181
+ return 1
182
+
183
+ raise AdapterError(f"crm unknown target: {target}")
adapters/demo_mock.py ADDED
@@ -0,0 +1,65 @@
1
+ """Mock adapters plus email/calendar/social for demo runtime usage."""
2
+ from typing import Any, Dict, List, Optional
3
+ import time
4
+
5
+ from runtime.adapters.base import AdapterRegistry, RuntimeAdapter
6
+
7
+ from .mock import (
8
+ MockAPIAdapter,
9
+ MockAuthAdapter,
10
+ MockCacheAdapter,
11
+ MockDBAdapter,
12
+ MockPayAdapter,
13
+ MockQueueAdapter,
14
+ MockTxnAdapter,
15
+ )
16
+
17
+
18
+ class _MockEmailAdapter(RuntimeAdapter):
19
+ def call(self, target: str, args: List[Any], context: Dict[str, Any]) -> Any:
20
+ if str(target or "").upper() != "G":
21
+ return []
22
+ now = int(time.time())
23
+ return [
24
+ {"id": "e1", "subject": "Update", "date": now - 300},
25
+ {"id": "e2", "subject": "News", "date": now - 60},
26
+ ]
27
+
28
+
29
+ class _MockCalendarAdapter(RuntimeAdapter):
30
+ def call(self, target: str, args: List[Any], context: Dict[str, Any]) -> Any:
31
+ if str(target or "").upper() != "G":
32
+ return []
33
+ now = int(time.time())
34
+ return [
35
+ {"id": "ev1", "title": "Meeting", "start": now + 3600},
36
+ {"id": "ev2", "title": "Lunch", "start": now + 7200},
37
+ ]
38
+
39
+
40
+ class _MockSocialAdapter(RuntimeAdapter):
41
+ def call(self, target: str, args: List[Any], context: Dict[str, Any]) -> Any:
42
+ if str(target or "").upper() != "G":
43
+ return []
44
+ now = int(time.time())
45
+ return [
46
+ {"id": "m1", "text": "Hello", "ts": now - 90},
47
+ {"id": "m2", "text": "World", "ts": now - 30},
48
+ ]
49
+
50
+
51
+ def mock_registry(types: Optional[Dict[str, Dict[str, Any]]] = None) -> AdapterRegistry:
52
+ reg = AdapterRegistry(
53
+ allowed=["core", "db", "api", "pay", "scrape", "cache", "queue", "txn", "auth", "email", "calendar", "social"]
54
+ )
55
+ reg.register("db", MockDBAdapter(types))
56
+ reg.register("api", MockAPIAdapter())
57
+ reg.register("cache", MockCacheAdapter())
58
+ reg.register("queue", MockQueueAdapter())
59
+ reg.register("auth", MockAuthAdapter())
60
+ reg.register("pay", MockPayAdapter())
61
+ reg.register("txn", MockTxnAdapter())
62
+ reg.register("email", _MockEmailAdapter())
63
+ reg.register("calendar", _MockCalendarAdapter())
64
+ reg.register("social", _MockSocialAdapter())
65
+ return reg