@konbakuyomu/smart-search-linux-x64 0.0.0 → 0.1.26

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +18 -3
  3. package/runtime/smart-search/_internal/base_library.zip +0 -0
  4. package/runtime/smart-search/_internal/certifi/cacert.pem +3959 -0
  5. package/runtime/smart-search/_internal/certifi/py.typed +0 -0
  6. package/runtime/smart-search/_internal/libpython3.13.so.1.0 +0 -0
  7. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/INSTALLER +1 -0
  8. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/METADATA +171 -0
  9. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/RECORD +298 -0
  10. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/WHEEL +5 -0
  11. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/licenses/AUTHORS.rst +11 -0
  12. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/licenses/LICENSE +27 -0
  13. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/top_level.txt +1 -0
  14. package/runtime/smart-search/_internal/smart_search/__init__.py +1 -0
  15. package/runtime/smart-search/_internal/smart_search/activity.py +279 -0
  16. package/runtime/smart-search/_internal/smart_search/assets/i18n/messages.json +17858 -0
  17. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/SKILL.md +69 -0
  18. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +3 -0
  19. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +21 -0
  20. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/cli-core.md +99 -0
  21. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/command-patterns.md +166 -0
  22. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/deep-research-mode.md +204 -0
  23. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/provider-routing.md +178 -0
  24. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/regression-release.md +46 -0
  25. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/setup-config.md +133 -0
  26. package/runtime/smart-search/_internal/smart_search/assets/ui/index.html +955 -0
  27. package/runtime/smart-search/_internal/smart_search/cli.py +3651 -0
  28. package/runtime/smart-search/_internal/smart_search/config.py +1302 -0
  29. package/runtime/smart-search/_internal/smart_search/desktop_backend.py +709 -0
  30. package/runtime/smart-search/_internal/smart_search/desktop_catalog.py +166 -0
  31. package/runtime/smart-search/_internal/smart_search/desktop_cli.py +209 -0
  32. package/runtime/smart-search/_internal/smart_search/desktop_entry.py +19 -0
  33. package/runtime/smart-search/_internal/smart_search/desktop_environment.py +629 -0
  34. package/runtime/smart-search/_internal/smart_search/desktop_skills.py +129 -0
  35. package/runtime/smart-search/_internal/smart_search/desktop_updates.py +160 -0
  36. package/runtime/smart-search/_internal/smart_search/desktop_worker.py +117 -0
  37. package/runtime/smart-search/_internal/smart_search/i18n.py +135 -0
  38. package/runtime/smart-search/_internal/smart_search/intent_router.py +253 -0
  39. package/runtime/smart-search/_internal/smart_search/jev.py +473 -0
  40. package/runtime/smart-search/_internal/smart_search/jev_search.py +719 -0
  41. package/runtime/smart-search/_internal/smart_search/logger.py +43 -0
  42. package/runtime/smart-search/_internal/smart_search/provider_descriptions.py +140 -0
  43. package/runtime/smart-search/_internal/smart_search/provider_errors.py +137 -0
  44. package/runtime/smart-search/_internal/smart_search/provider_health.py +277 -0
  45. package/runtime/smart-search/_internal/smart_search/providers/__init__.py +27 -0
  46. package/runtime/smart-search/_internal/smart_search/providers/anysearch.py +336 -0
  47. package/runtime/smart-search/_internal/smart_search/providers/base.py +41 -0
  48. package/runtime/smart-search/_internal/smart_search/providers/context7.py +153 -0
  49. package/runtime/smart-search/_internal/smart_search/providers/exa.py +191 -0
  50. package/runtime/smart-search/_internal/smart_search/providers/jina.py +124 -0
  51. package/runtime/smart-search/_internal/smart_search/providers/openai_compatible.py +977 -0
  52. package/runtime/smart-search/_internal/smart_search/providers/sciverse.py +351 -0
  53. package/runtime/smart-search/_internal/smart_search/providers/tinyfish.py +213 -0
  54. package/runtime/smart-search/_internal/smart_search/providers/xai_responses.py +242 -0
  55. package/runtime/smart-search/_internal/smart_search/providers/zhipu.py +172 -0
  56. package/runtime/smart-search/_internal/smart_search/providers/zhipu_mcp.py +287 -0
  57. package/runtime/smart-search/_internal/smart_search/sciverse_schema.py +437 -0
  58. package/runtime/smart-search/_internal/smart_search/service.py +4302 -0
  59. package/runtime/smart-search/_internal/smart_search/skill_installer.py +549 -0
  60. package/runtime/smart-search/_internal/smart_search/skill_maintenance.py +181 -0
  61. package/runtime/smart-search/_internal/smart_search/sources.py +429 -0
  62. package/runtime/smart-search/_internal/smart_search/state_files.py +53 -0
  63. package/runtime/smart-search/_internal/smart_search/ui_api.py +246 -0
  64. package/runtime/smart-search/_internal/smart_search/ui_metadata.py +698 -0
  65. package/runtime/smart-search/_internal/smart_search/ui_server.py +506 -0
  66. package/runtime/smart-search/_internal/smart_search/utils.py +220 -0
  67. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/INSTALLER +1 -0
  68. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/METADATA +97 -0
  69. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/RECORD +105 -0
  70. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/REQUESTED +0 -0
  71. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/WHEEL +5 -0
  72. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/direct_url.json +1 -0
  73. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/entry_points.txt +2 -0
  74. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/licenses/LICENSE +21 -0
  75. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/top_level.txt +1 -0
  76. package/runtime/smart-search/_internal/wcwidth/_wcwidth_c.abi3.so +0 -0
  77. package/runtime/smart-search/_internal/wcwidth/_wcwidth_c.pyi +77 -0
  78. package/runtime/smart-search/_internal/wcwidth/py.typed +0 -0
  79. package/runtime/smart-search/package.json +49 -0
  80. package/runtime/smart-search/smart-search +0 -0
  81. package/README.md +0 -3
@@ -0,0 +1,279 @@
1
+ """Local, bounded, metadata-only activity shared by the CLI and desktop clients."""
2
+ from __future__ import annotations
3
+ from .i18n import source_message
4
+
5
+ from contextlib import contextmanager
6
+ from contextvars import ContextVar
7
+ import os
8
+ from pathlib import Path
9
+ import sqlite3
10
+ import sys
11
+ import threading
12
+ import time
13
+ import uuid
14
+
15
+ from .config import config
16
+
17
+ SCHEMA_VERSION = 2
18
+ MAX_COMPLETED_RUNS = 1000
19
+ RETENTION_SECONDS = 7 * 86400
20
+ _current: ContextVar[Invocation | None] = ContextVar("smart_search_activity", default=None)
21
+ _SAFE_ERROR_TYPES = {"", "parameter_error", "config_error", "auth_error", "timeout", "rate_limited",
22
+ "network_error", "parse_error", "provider_error", "runtime_error", "cancelled",
23
+ "evidence_error", "interrupted"}
24
+
25
+
26
+ class ActivityStore:
27
+ def __init__(self, directory: str | Path | None = None):
28
+ self.directory = Path(directory) if directory is not None else config.config_file.parent
29
+ self.path = self.directory / "activity.sqlite3"
30
+
31
+ @contextmanager
32
+ def connection(self, timeout: float = 0.2):
33
+ self.directory.mkdir(parents=True, exist_ok=True)
34
+ db = sqlite3.connect(self.path, timeout=timeout)
35
+ try:
36
+ if os.name != "nt":
37
+ self.path.chmod(0o600)
38
+ db.row_factory = sqlite3.Row
39
+ db.execute("PRAGMA foreign_keys=ON")
40
+ version = db.execute("PRAGMA user_version").fetchone()[0]
41
+ if version > SCHEMA_VERSION:
42
+ raise ValueError(source_message('Activity database was created by a newer version.'))
43
+ if not version:
44
+ db.executescript("""
45
+ CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);
46
+ CREATE TABLE IF NOT EXISTS runs (
47
+ run_id TEXT PRIMARY KEY, command TEXT NOT NULL, origin TEXT NOT NULL,
48
+ config_dir TEXT NOT NULL, version TEXT NOT NULL, pid INTEGER NOT NULL,
49
+ status TEXT NOT NULL DEFAULT 'running', phase TEXT NOT NULL DEFAULT 'starting',
50
+ provider TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '',
51
+ started_at REAL NOT NULL, updated_at REAL NOT NULL, finished_at REAL,
52
+ elapsed_ms REAL NOT NULL DEFAULT 0, error_type TEXT NOT NULL DEFAULT '',
53
+ exit_code INTEGER, sources_count INTEGER NOT NULL DEFAULT 0,
54
+ sequence INTEGER NOT NULL DEFAULT 0, config_revision TEXT NOT NULL DEFAULT '');
55
+ CREATE TABLE IF NOT EXISTS events (
56
+ id INTEGER PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
57
+ sequence INTEGER NOT NULL, timestamp REAL NOT NULL, phase TEXT NOT NULL,
58
+ provider TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '',
59
+ status TEXT NOT NULL, error_type TEXT NOT NULL DEFAULT '',
60
+ UNIQUE(run_id, sequence));
61
+ CREATE INDEX IF NOT EXISTS events_run ON events(run_id, sequence);
62
+ PRAGMA user_version=2;
63
+ """)
64
+ elif version == 1:
65
+ db.execute("BEGIN IMMEDIATE")
66
+ if db.execute("PRAGMA user_version").fetchone()[0] == 1:
67
+ db.execute("ALTER TABLE runs ADD COLUMN config_revision TEXT NOT NULL DEFAULT ''")
68
+ db.execute("PRAGMA user_version=2")
69
+ with db:
70
+ yield db
71
+ finally:
72
+ db.close()
73
+
74
+ def enabled(self) -> bool:
75
+ if os.getenv("SMART_SEARCH_ACTIVITY_ENABLED", "true").lower() in {"false", "0", "no"}:
76
+ return False
77
+ with self.connection() as db:
78
+ row = db.execute("SELECT value FROM settings WHERE key='enabled'").fetchone()
79
+ return row is None or row[0] == "true"
80
+
81
+ def set_enabled(self, enabled: bool):
82
+ with self.connection() as db:
83
+ db.execute("INSERT OR REPLACE INTO settings VALUES ('enabled', ?)", ("true" if enabled else "false",))
84
+
85
+ def start(self, run_id: str, command: str, origin: str, version: str, config_revision: str = ""):
86
+ now = time.time()
87
+ with self.connection() as db:
88
+ db.execute("""INSERT INTO runs(run_id,command,origin,config_dir,version,pid,started_at,updated_at,config_revision)
89
+ VALUES (?,?,?,?,?,?,?,?,?)""",
90
+ (run_id, command, origin, str(self.directory), version, os.getpid(), now, now, config_revision))
91
+ db.execute("INSERT INTO events(run_id,sequence,timestamp,phase,status) VALUES (?,0,?,'starting','running')",
92
+ (run_id, now))
93
+ self._prune(db, now)
94
+
95
+ @staticmethod
96
+ def _prune(db, now):
97
+ db.execute("""DELETE FROM runs WHERE finished_at IS NOT NULL AND
98
+ (finished_at < ? OR run_id NOT IN (SELECT run_id FROM runs WHERE finished_at IS NOT NULL
99
+ ORDER BY finished_at DESC LIMIT ?))""", (now - RETENTION_SECONDS, MAX_COMPLETED_RUNS))
100
+
101
+ def heartbeat(self, run_id: str):
102
+ with self.connection() as db:
103
+ db.execute("UPDATE runs SET updated_at=? WHERE run_id=? AND finished_at IS NULL", (time.time(), run_id))
104
+
105
+ def progress(self, run_id: str, *, phase: str, provider: str = "", model: str = "", error_type: str = ""):
106
+ now = time.time()
107
+ with self.connection() as db:
108
+ changed = db.execute("""UPDATE runs SET phase=?,provider=COALESCE(NULLIF(?,''),provider),
109
+ model=CASE WHEN ?='' AND (?='' OR ?=provider COLLATE NOCASE) THEN model ELSE ? END,
110
+ error_type=?,updated_at=?,
111
+ sequence=sequence+1 WHERE run_id=? AND finished_at IS NULL""",
112
+ (phase, provider, model, provider, provider, model, error_type, now, run_id)).rowcount
113
+ if changed:
114
+ db.execute("""INSERT INTO events(run_id,sequence,timestamp,phase,provider,model,status,error_type)
115
+ SELECT run_id,sequence,?,phase,provider,model,'running',error_type FROM runs WHERE run_id=?""", (now, run_id))
116
+ # ponytail: retain 500 phase events per run; paginate a larger journal if a real workflow needs more.
117
+ db.execute("DELETE FROM events WHERE run_id=? AND id NOT IN (SELECT id FROM events WHERE run_id=? ORDER BY id DESC LIMIT 500)",
118
+ (run_id, run_id))
119
+
120
+ def finish(self, run_id: str, exit_code: int, status: str, *, error_type: str = "", sources_count: int = 0):
121
+ now = time.time()
122
+ # SQLite's busy timeout retries the final write, still bounded to one second.
123
+ with self.connection(timeout=1.0) as db:
124
+ changed = db.execute("""UPDATE runs SET status=?,finished_at=?,updated_at=?,exit_code=?,error_type=?,
125
+ sources_count=?,elapsed_ms=(?-started_at)*1000,sequence=sequence+1
126
+ WHERE run_id=? AND finished_at IS NULL""",
127
+ (status, now, now, exit_code, error_type, sources_count, now, run_id)).rowcount
128
+ if changed:
129
+ db.execute("""INSERT INTO events(run_id,sequence,timestamp,phase,provider,model,status,error_type)
130
+ SELECT run_id,sequence,?,phase,provider,model,status,error_type FROM runs WHERE run_id=?""", (now, run_id))
131
+ self._prune(db, now)
132
+
133
+ @staticmethod
134
+ def _public_run(row):
135
+ value = dict(row)
136
+ if value["finished_at"] is None:
137
+ value["elapsed_ms"] = round((time.time() - value["started_at"]) * 1000, 1)
138
+ if time.time() - value["updated_at"] > 10:
139
+ value["status"] = "stale"
140
+ return value
141
+
142
+ def list_runs(self, limit: int = 100) -> list[dict]:
143
+ with self.connection() as db:
144
+ self._prune(db, time.time())
145
+ return [self._public_run(row) for row in db.execute("SELECT * FROM runs ORDER BY started_at DESC LIMIT ?",
146
+ (max(1, min(limit, 1000)),))]
147
+
148
+ def details(self, run_id: str) -> dict:
149
+ with self.connection() as db:
150
+ row = db.execute("SELECT * FROM runs WHERE run_id=?", (run_id,)).fetchone()
151
+ if row is None:
152
+ return {"ok": False, "error": source_message('活动记录不存在。')}
153
+ events = [dict(item) for item in db.execute("SELECT sequence,timestamp,phase,provider,model,status,error_type FROM events WHERE run_id=? ORDER BY sequence", (run_id,))]
154
+ return {"ok": True, "run": self._public_run(row), "events": events,
155
+ "events_truncated": bool(events and events[0]["sequence"] > 0)}
156
+
157
+ def clear(self):
158
+ with self.connection() as db:
159
+ db.execute("DELETE FROM runs WHERE finished_at IS NOT NULL")
160
+
161
+
162
+ class Invocation:
163
+ def __init__(self, command: str, *, origin: str = "cli", version: str = "", run_id: str = ""):
164
+ self.run_id = uuid.UUID(run_id).hex if run_id else uuid.uuid4().hex
165
+ self.command, self.origin, self.version = command[:100], origin, version
166
+ self._stop = threading.Event()
167
+ self._warned = False
168
+ self._enabled = False
169
+ self._finished = False
170
+ self._error_type = ""
171
+ self._sources_count = 0
172
+ self._heartbeat = None
173
+ self.store = self._try(ActivityStore)
174
+ self._secrets = self._try(config.secret_values) or ()
175
+ # An opaque snapshot handle identifies this invocation without retaining a
176
+ # credential-derived hash that could be used as an offline guessing oracle.
177
+ self._revision = uuid.uuid4().hex
178
+
179
+ def _try(self, fn, *args, **kwargs):
180
+ try:
181
+ return fn(*args, **kwargs)
182
+ except (OSError, sqlite3.Error, ValueError):
183
+ if not self._warned:
184
+ self._warned = True
185
+ try:
186
+ print("smart-search: activity recording unavailable; the command will continue.", file=sys.stderr)
187
+ except OSError:
188
+ pass
189
+ return None
190
+
191
+ def start(self):
192
+ if self.store is None:
193
+ return
194
+ self._enabled = bool(self._try(self.store.enabled))
195
+ if not self._enabled:
196
+ return
197
+ self._try(self.store.start, self.run_id, self.command, self.origin, self.version, self._revision)
198
+ self._heartbeat = threading.Thread(target=self._keep_alive, daemon=True)
199
+ self._heartbeat.start()
200
+
201
+ def _keep_alive(self):
202
+ while not self._stop.wait(2):
203
+ self._try(self.store.heartbeat, self.run_id)
204
+
205
+ def _label(self, value: object) -> str:
206
+ text = str(value or "")
207
+ for secret in self._secrets:
208
+ if secret:
209
+ text = text.replace(secret, "[REDACTED]")
210
+ return text[:100]
211
+
212
+ def progress(self, phase: str, provider: str = "", model: str = "", error_type: str = ""):
213
+ if self._enabled and not self._finished:
214
+ self._try(self.store.progress, self.run_id, phase=self._label(phase), provider=self._label(provider),
215
+ model=self._label(model), error_type=error_type if error_type in _SAFE_ERROR_TYPES else "runtime_error")
216
+
217
+ def result(self, data: dict):
218
+ if data.get("provider"):
219
+ # The final result names the primary provider; keep supplemental
220
+ # providers in phase events without attaching its model to them.
221
+ self.progress(self.command, str(data["provider"]), str(data.get("model") or ""))
222
+ self._error_type = str(data.get("error_type") or "")
223
+ if self._error_type not in _SAFE_ERROR_TYPES:
224
+ self._error_type = "runtime_error"
225
+ sources = data.get("sources")
226
+ self._sources_count = len(sources) if isinstance(sources, list) else 0
227
+
228
+ def finish(self, exit_code: int = 0, status: str = ""):
229
+ if self._finished:
230
+ return
231
+ self._finished = True
232
+ self._stop.set()
233
+ status = status or ("cancelled" if self._error_type == "cancelled" else "finished" if exit_code == 0 else "failed")
234
+ if self._enabled:
235
+ self._try(self.store.finish, self.run_id, exit_code, status, error_type=self._error_type,
236
+ sources_count=self._sources_count)
237
+
238
+
239
+ @contextmanager
240
+ def observe(command: str, *, origin: str = "cli", version: str = "", run_id: str = ""):
241
+ previous = _current.get()
242
+ if previous is not None:
243
+ yield previous
244
+ return
245
+ run = Invocation(command, origin=origin, version=version, run_id=run_id)
246
+ token = _current.set(run)
247
+ run.start()
248
+ try:
249
+ yield run
250
+ except KeyboardInterrupt:
251
+ run.finish(5, "cancelled")
252
+ raise
253
+ except SystemExit as error:
254
+ run.finish(error.code if isinstance(error.code, int) else 5)
255
+ raise
256
+ except BaseException:
257
+ run.finish(5)
258
+ raise
259
+ finally:
260
+ run.finish()
261
+ _current.reset(token)
262
+
263
+
264
+ def progress(phase: str, provider: str = "", model: str = "", error_type: str = ""):
265
+ run = _current.get()
266
+ if run is not None:
267
+ run.progress(phase, provider, model, error_type)
268
+
269
+
270
+ def result(data: dict):
271
+ run = _current.get()
272
+ if run is not None:
273
+ run.result(data)
274
+
275
+
276
+ def cancelled():
277
+ run = _current.get()
278
+ if run is not None:
279
+ run.finish(5, "cancelled")