claude-smart 0.2.47 → 0.2.48
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/pyproject.toml +1 -1
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +8 -5
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +15 -11
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +24 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +25 -8
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +133 -3273
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +56 -4
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +362 -81
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -490
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +29 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +56 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +0 -1513
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +6 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1281
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +6 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +13 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +33 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +44 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +0 -148
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"""SQLite PendingToolCallStore methods (the PendingToolCallStore bucket).
|
|
2
|
+
|
|
3
|
+
Extracted verbatim from ``_agent_run.py`` (the PendingToolCallStore bucket): the
|
|
4
|
+
twelve pending-tool-call methods plus the PTC-owned
|
|
5
|
+
``_insert_pending_tool_call_unlocked`` private.
|
|
6
|
+
|
|
7
|
+
Four of these methods are multi-table transactions that cascade agent-run state
|
|
8
|
+
by calling AgentRunStore-owned ``_..._unlocked`` privates
|
|
9
|
+
(``_finalize_runs_without_pending_dependencies_unlocked`` from ``cancel`` /
|
|
10
|
+
``expire``; ``_mark_runs_ready_with_actionable_dependencies_unlocked`` from
|
|
11
|
+
``update_resolved`` / ``mark_not_applicable``;
|
|
12
|
+
``_finalize_runs_without_actionable_dependencies_unlocked`` from
|
|
13
|
+
``mark_not_applicable``). Those privates live in the co-composed
|
|
14
|
+
``SQLiteAgentRunStoreMixin`` and are reached via MRO — kept SINGLE there, never
|
|
15
|
+
duplicated. Each transaction is moved WHOLE (its ``BEGIN IMMEDIATE`` /
|
|
16
|
+
``with self._lock`` block plus all writes and its commit/rollback).
|
|
17
|
+
|
|
18
|
+
The residual ``_agent_run.py`` module (helpers-only, no mixin class)
|
|
19
|
+
permanently holds the shared row/datetime helpers (``_dt_str``,
|
|
20
|
+
``_row_to_pending_tool_call``, ``_record_to_prior_answer_match`` …), which are
|
|
21
|
+
imported here rather than duplicated.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import sqlite3
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from datetime import UTC, datetime, timedelta
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from reflexio.server.services.storage.storage_base import (
|
|
32
|
+
AgentRunStatus,
|
|
33
|
+
PendingToolCallRecord,
|
|
34
|
+
PendingToolCallStatus,
|
|
35
|
+
PendingToolCallUpsertResult,
|
|
36
|
+
PriorAnswerMatch,
|
|
37
|
+
RunToolDependencyRecord,
|
|
38
|
+
not_applicable_tool_result,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from .._agent_run import (
|
|
42
|
+
_dt_str,
|
|
43
|
+
_record_to_prior_answer_match,
|
|
44
|
+
_row_to_pending_tool_call,
|
|
45
|
+
)
|
|
46
|
+
from .._base import SQLiteStorageBase, _json_dumps
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SQLitePendingToolCallStoreMixin:
|
|
50
|
+
"""SQLite-backed pending-tool-call store primitives."""
|
|
51
|
+
|
|
52
|
+
_lock: Any
|
|
53
|
+
conn: sqlite3.Connection
|
|
54
|
+
_fetchone: Any
|
|
55
|
+
_fetchall: Any
|
|
56
|
+
_current_timestamp: Any
|
|
57
|
+
org_id: str
|
|
58
|
+
|
|
59
|
+
# Provided via MRO by the co-composed SQLiteAgentRunStoreMixin; reached from
|
|
60
|
+
# the cross-bucket pending-tool-call methods below (cancel / expire /
|
|
61
|
+
# update_resolved / mark_not_applicable).
|
|
62
|
+
_finalize_runs_without_pending_dependencies_unlocked: Callable[..., None]
|
|
63
|
+
_mark_runs_ready_with_actionable_dependencies_unlocked: Callable[..., None]
|
|
64
|
+
_finalize_runs_without_actionable_dependencies_unlocked: Callable[..., None]
|
|
65
|
+
|
|
66
|
+
@SQLiteStorageBase.handle_exceptions
|
|
67
|
+
def create_pending_tool_call(
|
|
68
|
+
self, record: PendingToolCallRecord
|
|
69
|
+
) -> PendingToolCallRecord:
|
|
70
|
+
with self._lock:
|
|
71
|
+
self.conn.execute(
|
|
72
|
+
"""
|
|
73
|
+
INSERT INTO _pending_tool_calls (
|
|
74
|
+
id, org_id, user_id, scope, scope_hash, tool_name, dedup_key,
|
|
75
|
+
status, question_text, answer_format, args, tags, result,
|
|
76
|
+
embedding, superseded_by, resolved_at, expires_at, cache_until,
|
|
77
|
+
valid_until
|
|
78
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
79
|
+
""",
|
|
80
|
+
(
|
|
81
|
+
record.id,
|
|
82
|
+
record.org_id,
|
|
83
|
+
record.user_id,
|
|
84
|
+
_json_dumps(record.scope),
|
|
85
|
+
record.scope_hash,
|
|
86
|
+
record.tool_name,
|
|
87
|
+
record.dedup_key,
|
|
88
|
+
record.status.value,
|
|
89
|
+
record.question_text,
|
|
90
|
+
record.answer_format,
|
|
91
|
+
_json_dumps(record.args),
|
|
92
|
+
_json_dumps(record.tags),
|
|
93
|
+
_json_dumps(record.result),
|
|
94
|
+
_json_dumps(record.embedding),
|
|
95
|
+
record.superseded_by,
|
|
96
|
+
_dt_str(record.resolved_at),
|
|
97
|
+
_dt_str(record.expires_at),
|
|
98
|
+
_dt_str(record.cache_until),
|
|
99
|
+
_dt_str(record.valid_until),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
self.conn.commit()
|
|
103
|
+
stored = self.get_pending_tool_call(record.id)
|
|
104
|
+
if stored is None: # pragma: no cover
|
|
105
|
+
raise RuntimeError(f"Failed to create pending tool call {record.id}")
|
|
106
|
+
return stored
|
|
107
|
+
|
|
108
|
+
def _insert_pending_tool_call_unlocked(self, record: PendingToolCallRecord) -> None:
|
|
109
|
+
self.conn.execute(
|
|
110
|
+
"""
|
|
111
|
+
INSERT INTO _pending_tool_calls (
|
|
112
|
+
id, org_id, user_id, scope, scope_hash, tool_name, dedup_key,
|
|
113
|
+
status, question_text, answer_format, args, tags, result,
|
|
114
|
+
embedding, superseded_by, resolved_at, expires_at, cache_until,
|
|
115
|
+
valid_until
|
|
116
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
117
|
+
""",
|
|
118
|
+
(
|
|
119
|
+
record.id,
|
|
120
|
+
record.org_id,
|
|
121
|
+
record.user_id,
|
|
122
|
+
_json_dumps(record.scope),
|
|
123
|
+
record.scope_hash,
|
|
124
|
+
record.tool_name,
|
|
125
|
+
record.dedup_key,
|
|
126
|
+
record.status.value,
|
|
127
|
+
record.question_text,
|
|
128
|
+
record.answer_format,
|
|
129
|
+
_json_dumps(record.args),
|
|
130
|
+
_json_dumps(record.tags),
|
|
131
|
+
_json_dumps(record.result),
|
|
132
|
+
_json_dumps(record.embedding),
|
|
133
|
+
record.superseded_by,
|
|
134
|
+
_dt_str(record.resolved_at),
|
|
135
|
+
_dt_str(record.expires_at),
|
|
136
|
+
_dt_str(record.cache_until),
|
|
137
|
+
_dt_str(record.valid_until),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@SQLiteStorageBase.handle_exceptions
|
|
142
|
+
def create_or_attach_pending_tool_call(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
record: PendingToolCallRecord,
|
|
146
|
+
dependency: RunToolDependencyRecord,
|
|
147
|
+
now: datetime | None = None,
|
|
148
|
+
) -> PendingToolCallUpsertResult:
|
|
149
|
+
current = now or datetime.now(UTC)
|
|
150
|
+
created = False
|
|
151
|
+
pending_tool_call_id = record.id
|
|
152
|
+
with self._lock:
|
|
153
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
154
|
+
try:
|
|
155
|
+
row = self.conn.execute(
|
|
156
|
+
"""
|
|
157
|
+
SELECT * FROM _pending_tool_calls
|
|
158
|
+
WHERE org_id = ?
|
|
159
|
+
AND scope_hash = ?
|
|
160
|
+
AND tool_name = ?
|
|
161
|
+
AND dedup_key = ?
|
|
162
|
+
AND status = ?
|
|
163
|
+
AND cache_until > ?
|
|
164
|
+
ORDER BY created_at ASC
|
|
165
|
+
LIMIT 1
|
|
166
|
+
""",
|
|
167
|
+
(
|
|
168
|
+
record.org_id,
|
|
169
|
+
record.scope_hash,
|
|
170
|
+
record.tool_name,
|
|
171
|
+
record.dedup_key,
|
|
172
|
+
PendingToolCallStatus.PENDING.value,
|
|
173
|
+
_dt_str(current),
|
|
174
|
+
),
|
|
175
|
+
).fetchone()
|
|
176
|
+
pending_tool_call_id = row["id"] if row is not None else record.id
|
|
177
|
+
if row is None:
|
|
178
|
+
self._insert_pending_tool_call_unlocked(record)
|
|
179
|
+
created = True
|
|
180
|
+
self.conn.execute(
|
|
181
|
+
"""
|
|
182
|
+
INSERT OR IGNORE INTO _run_tool_dependencies (
|
|
183
|
+
run_id, pending_tool_call_id, dependency_kind,
|
|
184
|
+
resolved_at, consumed_at
|
|
185
|
+
) VALUES (?,?,?,?,?)
|
|
186
|
+
""",
|
|
187
|
+
(
|
|
188
|
+
dependency.run_id,
|
|
189
|
+
pending_tool_call_id,
|
|
190
|
+
dependency.dependency_kind.value,
|
|
191
|
+
_dt_str(dependency.resolved_at),
|
|
192
|
+
_dt_str(dependency.consumed_at),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
except Exception:
|
|
196
|
+
self.conn.rollback()
|
|
197
|
+
raise
|
|
198
|
+
else:
|
|
199
|
+
self.conn.commit()
|
|
200
|
+
|
|
201
|
+
stored = self.get_pending_tool_call(pending_tool_call_id)
|
|
202
|
+
if stored is None: # pragma: no cover
|
|
203
|
+
raise RuntimeError("Failed to create or attach pending tool call")
|
|
204
|
+
return PendingToolCallUpsertResult(pending_tool_call=stored, created=created)
|
|
205
|
+
|
|
206
|
+
@SQLiteStorageBase.handle_exceptions
|
|
207
|
+
def get_pending_tool_call(self, call_id: str) -> PendingToolCallRecord | None:
|
|
208
|
+
row = self._fetchone(
|
|
209
|
+
"SELECT * FROM _pending_tool_calls WHERE id = ?", (call_id,)
|
|
210
|
+
)
|
|
211
|
+
return _row_to_pending_tool_call(row) if row else None
|
|
212
|
+
|
|
213
|
+
@SQLiteStorageBase.handle_exceptions
|
|
214
|
+
def list_pending_tool_calls(
|
|
215
|
+
self,
|
|
216
|
+
*,
|
|
217
|
+
status: PendingToolCallStatus | None = None,
|
|
218
|
+
limit: int = 100,
|
|
219
|
+
) -> list[PendingToolCallRecord]:
|
|
220
|
+
bounded_limit = max(1, min(limit, 500))
|
|
221
|
+
params: list[Any] = [self.org_id]
|
|
222
|
+
status_clause = ""
|
|
223
|
+
if status is not None:
|
|
224
|
+
status_clause = "AND status = ?"
|
|
225
|
+
params.append(status.value)
|
|
226
|
+
params.append(bounded_limit)
|
|
227
|
+
rows = self._fetchall(
|
|
228
|
+
f"""
|
|
229
|
+
SELECT * FROM _pending_tool_calls
|
|
230
|
+
WHERE org_id = ?
|
|
231
|
+
{status_clause}
|
|
232
|
+
ORDER BY created_at DESC, id ASC
|
|
233
|
+
LIMIT ?
|
|
234
|
+
""",
|
|
235
|
+
tuple(params),
|
|
236
|
+
)
|
|
237
|
+
return [_row_to_pending_tool_call(row) for row in rows]
|
|
238
|
+
|
|
239
|
+
@SQLiteStorageBase.handle_exceptions
|
|
240
|
+
def cancel_pending_tool_call(
|
|
241
|
+
self,
|
|
242
|
+
call_id: str,
|
|
243
|
+
*,
|
|
244
|
+
cancelled_at: datetime | None = None,
|
|
245
|
+
) -> PendingToolCallRecord | None:
|
|
246
|
+
now = cancelled_at or datetime.now(UTC)
|
|
247
|
+
now_s = _dt_str(now)
|
|
248
|
+
with self._lock:
|
|
249
|
+
self.conn.execute(
|
|
250
|
+
"""
|
|
251
|
+
UPDATE _pending_tool_calls
|
|
252
|
+
SET status = ?
|
|
253
|
+
WHERE id = ?
|
|
254
|
+
AND status = ?
|
|
255
|
+
""",
|
|
256
|
+
(
|
|
257
|
+
PendingToolCallStatus.CANCELLED.value,
|
|
258
|
+
call_id,
|
|
259
|
+
PendingToolCallStatus.PENDING.value,
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
self.conn.execute(
|
|
263
|
+
"""
|
|
264
|
+
UPDATE _run_tool_dependencies
|
|
265
|
+
SET resolved_at = ?
|
|
266
|
+
WHERE pending_tool_call_id = ? AND resolved_at IS NULL
|
|
267
|
+
""",
|
|
268
|
+
(now_s, call_id),
|
|
269
|
+
)
|
|
270
|
+
self._finalize_runs_without_pending_dependencies_unlocked(now_s or "")
|
|
271
|
+
self.conn.commit()
|
|
272
|
+
return self.get_pending_tool_call(call_id)
|
|
273
|
+
|
|
274
|
+
@SQLiteStorageBase.handle_exceptions
|
|
275
|
+
def expire_pending_tool_calls(
|
|
276
|
+
self,
|
|
277
|
+
*,
|
|
278
|
+
now: datetime | None = None,
|
|
279
|
+
limit: int = 100,
|
|
280
|
+
) -> int:
|
|
281
|
+
current = now or datetime.now(UTC)
|
|
282
|
+
now_s = _dt_str(current)
|
|
283
|
+
bounded_limit = max(1, min(limit, 500))
|
|
284
|
+
with self._lock:
|
|
285
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
286
|
+
try:
|
|
287
|
+
rows = self.conn.execute(
|
|
288
|
+
"""
|
|
289
|
+
SELECT id
|
|
290
|
+
FROM _pending_tool_calls
|
|
291
|
+
WHERE status = ?
|
|
292
|
+
AND expires_at IS NOT NULL
|
|
293
|
+
AND expires_at <= ?
|
|
294
|
+
ORDER BY expires_at ASC, created_at ASC, id ASC
|
|
295
|
+
LIMIT ?
|
|
296
|
+
""",
|
|
297
|
+
(
|
|
298
|
+
PendingToolCallStatus.PENDING.value,
|
|
299
|
+
now_s,
|
|
300
|
+
bounded_limit,
|
|
301
|
+
),
|
|
302
|
+
).fetchall()
|
|
303
|
+
call_ids = [row["id"] for row in rows]
|
|
304
|
+
if not call_ids:
|
|
305
|
+
self.conn.commit()
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
placeholders = ",".join("?" for _ in call_ids)
|
|
309
|
+
self.conn.execute(
|
|
310
|
+
f"""
|
|
311
|
+
UPDATE _pending_tool_calls
|
|
312
|
+
SET status = ?
|
|
313
|
+
WHERE id IN ({placeholders})
|
|
314
|
+
AND status = ?
|
|
315
|
+
""",
|
|
316
|
+
(
|
|
317
|
+
PendingToolCallStatus.EXPIRED.value,
|
|
318
|
+
*call_ids,
|
|
319
|
+
PendingToolCallStatus.PENDING.value,
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
self.conn.execute(
|
|
323
|
+
f"""
|
|
324
|
+
UPDATE _run_tool_dependencies
|
|
325
|
+
SET resolved_at = ?
|
|
326
|
+
WHERE pending_tool_call_id IN ({placeholders})
|
|
327
|
+
AND resolved_at IS NULL
|
|
328
|
+
""",
|
|
329
|
+
(now_s, *call_ids),
|
|
330
|
+
)
|
|
331
|
+
self._finalize_runs_without_pending_dependencies_unlocked(now_s or "")
|
|
332
|
+
except Exception:
|
|
333
|
+
self.conn.rollback()
|
|
334
|
+
raise
|
|
335
|
+
else:
|
|
336
|
+
self.conn.commit()
|
|
337
|
+
return len(call_ids)
|
|
338
|
+
|
|
339
|
+
@SQLiteStorageBase.handle_exceptions
|
|
340
|
+
def delete_expired_pending_tool_calls(
|
|
341
|
+
self, *, now: int, grace_seconds: int, limit: int = 1000
|
|
342
|
+
) -> int:
|
|
343
|
+
"""Delete terminal 'expired'-status rows whose expires_at is past the grace window.
|
|
344
|
+
|
|
345
|
+
Only rows with status='expired' are deleted. RESOLVED rows are never
|
|
346
|
+
touched even if their expires_at is in the past, preserving live cached
|
|
347
|
+
results for resumable extraction.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
now: Current Unix epoch seconds.
|
|
351
|
+
grace_seconds: Grace buffer; cutoff = now - grace_seconds.
|
|
352
|
+
limit: Max rows to delete per call.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Number of rows deleted.
|
|
356
|
+
"""
|
|
357
|
+
cutoff_iso = datetime.fromtimestamp(now - grace_seconds, UTC).isoformat()
|
|
358
|
+
bounded_limit = max(1, min(limit, 10_000))
|
|
359
|
+
with self._lock:
|
|
360
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
361
|
+
try:
|
|
362
|
+
rows = self.conn.execute(
|
|
363
|
+
"SELECT id FROM _pending_tool_calls "
|
|
364
|
+
"WHERE org_id = ? AND status = 'expired' AND expires_at IS NOT NULL AND expires_at < ? "
|
|
365
|
+
"ORDER BY expires_at ASC LIMIT ?",
|
|
366
|
+
(self.org_id, cutoff_iso, bounded_limit),
|
|
367
|
+
).fetchall()
|
|
368
|
+
if not rows:
|
|
369
|
+
self.conn.commit()
|
|
370
|
+
return 0
|
|
371
|
+
ids = [r["id"] for r in rows]
|
|
372
|
+
ph = ",".join("?" * len(ids))
|
|
373
|
+
cur = self.conn.execute(
|
|
374
|
+
f"DELETE FROM _pending_tool_calls WHERE id IN ({ph})", ids
|
|
375
|
+
)
|
|
376
|
+
self.conn.commit()
|
|
377
|
+
except Exception:
|
|
378
|
+
self.conn.rollback()
|
|
379
|
+
raise
|
|
380
|
+
return cur.rowcount
|
|
381
|
+
|
|
382
|
+
@SQLiteStorageBase.handle_exceptions
|
|
383
|
+
def find_active_pending_tool_call(
|
|
384
|
+
self,
|
|
385
|
+
*,
|
|
386
|
+
org_id: str,
|
|
387
|
+
scope_hash: str,
|
|
388
|
+
tool_name: str,
|
|
389
|
+
dedup_key: str,
|
|
390
|
+
now: datetime | None = None,
|
|
391
|
+
) -> PendingToolCallRecord | None:
|
|
392
|
+
now_s = _dt_str(now or datetime.now(UTC))
|
|
393
|
+
row = self._fetchone(
|
|
394
|
+
"""
|
|
395
|
+
SELECT * FROM _pending_tool_calls
|
|
396
|
+
WHERE org_id = ?
|
|
397
|
+
AND scope_hash = ?
|
|
398
|
+
AND tool_name = ?
|
|
399
|
+
AND dedup_key = ?
|
|
400
|
+
AND status = ?
|
|
401
|
+
AND cache_until > ?
|
|
402
|
+
ORDER BY created_at ASC
|
|
403
|
+
LIMIT 1
|
|
404
|
+
""",
|
|
405
|
+
(
|
|
406
|
+
org_id,
|
|
407
|
+
scope_hash,
|
|
408
|
+
tool_name,
|
|
409
|
+
dedup_key,
|
|
410
|
+
PendingToolCallStatus.PENDING.value,
|
|
411
|
+
now_s,
|
|
412
|
+
),
|
|
413
|
+
)
|
|
414
|
+
return _row_to_pending_tool_call(row) if row else None
|
|
415
|
+
|
|
416
|
+
@SQLiteStorageBase.handle_exceptions
|
|
417
|
+
def search_prior_tool_calls(
|
|
418
|
+
self,
|
|
419
|
+
*,
|
|
420
|
+
org_id: str,
|
|
421
|
+
scope_hash: str,
|
|
422
|
+
tool_name: str,
|
|
423
|
+
query_embedding: list[float] | None = None,
|
|
424
|
+
now: datetime | None = None,
|
|
425
|
+
limit: int = 8,
|
|
426
|
+
) -> list[PriorAnswerMatch]:
|
|
427
|
+
current = now or datetime.now(UTC)
|
|
428
|
+
bounded_limit = max(1, min(limit, 50))
|
|
429
|
+
rows = self._fetchall(
|
|
430
|
+
"""
|
|
431
|
+
SELECT * FROM _pending_tool_calls
|
|
432
|
+
WHERE org_id = ?
|
|
433
|
+
AND scope_hash = ?
|
|
434
|
+
AND tool_name = ?
|
|
435
|
+
AND (
|
|
436
|
+
(
|
|
437
|
+
status = ?
|
|
438
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
439
|
+
)
|
|
440
|
+
OR (
|
|
441
|
+
status = ?
|
|
442
|
+
AND (valid_until IS NULL OR valid_until > ?)
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
ORDER BY
|
|
446
|
+
CASE status WHEN ? THEN 0 ELSE 1 END,
|
|
447
|
+
COALESCE(resolved_at, created_at) DESC,
|
|
448
|
+
id ASC
|
|
449
|
+
""",
|
|
450
|
+
(
|
|
451
|
+
org_id,
|
|
452
|
+
scope_hash,
|
|
453
|
+
tool_name,
|
|
454
|
+
PendingToolCallStatus.PENDING.value,
|
|
455
|
+
_dt_str(current),
|
|
456
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
457
|
+
_dt_str(current),
|
|
458
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
seen_resolved_dedup_keys: set[str] = set()
|
|
462
|
+
records: list[PendingToolCallRecord] = []
|
|
463
|
+
for row in rows:
|
|
464
|
+
record = _row_to_pending_tool_call(row)
|
|
465
|
+
if record.status == PendingToolCallStatus.RESOLVED:
|
|
466
|
+
if record.dedup_key in seen_resolved_dedup_keys:
|
|
467
|
+
continue
|
|
468
|
+
seen_resolved_dedup_keys.add(record.dedup_key)
|
|
469
|
+
records.append(record)
|
|
470
|
+
matches = [
|
|
471
|
+
_record_to_prior_answer_match(record, query_embedding=query_embedding)
|
|
472
|
+
for record in records
|
|
473
|
+
]
|
|
474
|
+
if query_embedding:
|
|
475
|
+
matches.sort(
|
|
476
|
+
key=lambda match: (
|
|
477
|
+
match.similarity is not None,
|
|
478
|
+
match.similarity or -1.0,
|
|
479
|
+
match.resolved_at
|
|
480
|
+
or match.created_at
|
|
481
|
+
or datetime.min.replace(tzinfo=UTC),
|
|
482
|
+
),
|
|
483
|
+
reverse=True,
|
|
484
|
+
)
|
|
485
|
+
return matches[:bounded_limit]
|
|
486
|
+
|
|
487
|
+
@SQLiteStorageBase.handle_exceptions
|
|
488
|
+
def resolve_pending_tool_call(
|
|
489
|
+
self,
|
|
490
|
+
call_id: str,
|
|
491
|
+
*,
|
|
492
|
+
result: dict[str, Any],
|
|
493
|
+
resolved_at: datetime | None = None,
|
|
494
|
+
valid_for_seconds: int,
|
|
495
|
+
) -> PendingToolCallRecord | None:
|
|
496
|
+
resolved = resolved_at or datetime.now(UTC)
|
|
497
|
+
valid_until = resolved + timedelta(seconds=valid_for_seconds)
|
|
498
|
+
with self._lock:
|
|
499
|
+
cur = self.conn.execute(
|
|
500
|
+
"""
|
|
501
|
+
UPDATE _pending_tool_calls
|
|
502
|
+
SET status = ?, result = ?, resolved_at = ?, valid_until = ?
|
|
503
|
+
WHERE id = ?
|
|
504
|
+
AND status = ?
|
|
505
|
+
""",
|
|
506
|
+
(
|
|
507
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
508
|
+
_json_dumps(result),
|
|
509
|
+
_dt_str(resolved),
|
|
510
|
+
_dt_str(valid_until),
|
|
511
|
+
call_id,
|
|
512
|
+
PendingToolCallStatus.PENDING.value,
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
if cur.rowcount == 0:
|
|
516
|
+
self.conn.commit()
|
|
517
|
+
return self.get_pending_tool_call(call_id)
|
|
518
|
+
self.conn.execute(
|
|
519
|
+
"""
|
|
520
|
+
UPDATE _pending_tool_calls
|
|
521
|
+
SET status = ?,
|
|
522
|
+
superseded_by = ?
|
|
523
|
+
WHERE id != ?
|
|
524
|
+
AND status = ?
|
|
525
|
+
AND (valid_until IS NULL OR valid_until > ?)
|
|
526
|
+
AND (org_id, scope_hash, tool_name, dedup_key) = (
|
|
527
|
+
SELECT org_id, scope_hash, tool_name, dedup_key
|
|
528
|
+
FROM _pending_tool_calls
|
|
529
|
+
WHERE id = ?
|
|
530
|
+
)
|
|
531
|
+
""",
|
|
532
|
+
(
|
|
533
|
+
PendingToolCallStatus.SUPERSEDED.value,
|
|
534
|
+
call_id,
|
|
535
|
+
call_id,
|
|
536
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
537
|
+
_dt_str(resolved),
|
|
538
|
+
call_id,
|
|
539
|
+
),
|
|
540
|
+
)
|
|
541
|
+
self.conn.execute(
|
|
542
|
+
"""
|
|
543
|
+
UPDATE _run_tool_dependencies
|
|
544
|
+
SET resolved_at = ?
|
|
545
|
+
WHERE pending_tool_call_id = ? AND resolved_at IS NULL
|
|
546
|
+
""",
|
|
547
|
+
(_dt_str(resolved), call_id),
|
|
548
|
+
)
|
|
549
|
+
self.conn.execute(
|
|
550
|
+
"""
|
|
551
|
+
UPDATE _agent_runs
|
|
552
|
+
SET status = ?, updated_at = ?
|
|
553
|
+
WHERE status IN (?, ?)
|
|
554
|
+
AND EXISTS (
|
|
555
|
+
SELECT 1
|
|
556
|
+
FROM _run_tool_dependencies d
|
|
557
|
+
WHERE d.run_id = _agent_runs.id
|
|
558
|
+
AND d.pending_tool_call_id = ?
|
|
559
|
+
AND d.resolved_at IS NOT NULL
|
|
560
|
+
AND d.consumed_at IS NULL
|
|
561
|
+
)
|
|
562
|
+
""",
|
|
563
|
+
(
|
|
564
|
+
AgentRunStatus.RESUME_READY.value,
|
|
565
|
+
self._current_timestamp(),
|
|
566
|
+
AgentRunStatus.FINALIZED.value,
|
|
567
|
+
AgentRunStatus.FINALIZED_PENDING_TOOL.value,
|
|
568
|
+
call_id,
|
|
569
|
+
),
|
|
570
|
+
)
|
|
571
|
+
self.conn.commit()
|
|
572
|
+
return self.get_pending_tool_call(call_id)
|
|
573
|
+
|
|
574
|
+
@SQLiteStorageBase.handle_exceptions
|
|
575
|
+
def update_resolved_pending_tool_call_result(
|
|
576
|
+
self,
|
|
577
|
+
call_id: str,
|
|
578
|
+
*,
|
|
579
|
+
result: dict[str, Any],
|
|
580
|
+
resolved_at: datetime | None = None,
|
|
581
|
+
valid_for_seconds: int,
|
|
582
|
+
) -> PendingToolCallRecord | None:
|
|
583
|
+
resolved = resolved_at or datetime.now(UTC)
|
|
584
|
+
valid_until = resolved + timedelta(seconds=valid_for_seconds)
|
|
585
|
+
now_s = _dt_str(resolved) or self._current_timestamp()
|
|
586
|
+
with self._lock:
|
|
587
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
588
|
+
try:
|
|
589
|
+
cur = self.conn.execute(
|
|
590
|
+
"""
|
|
591
|
+
UPDATE _pending_tool_calls
|
|
592
|
+
SET result = ?, resolved_at = ?, valid_until = ?
|
|
593
|
+
WHERE id = ?
|
|
594
|
+
AND status = ?
|
|
595
|
+
""",
|
|
596
|
+
(
|
|
597
|
+
_json_dumps(result),
|
|
598
|
+
_dt_str(resolved),
|
|
599
|
+
_dt_str(valid_until),
|
|
600
|
+
call_id,
|
|
601
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
602
|
+
),
|
|
603
|
+
)
|
|
604
|
+
if cur.rowcount == 0:
|
|
605
|
+
self.conn.commit()
|
|
606
|
+
return self.get_pending_tool_call(call_id)
|
|
607
|
+
self.conn.execute(
|
|
608
|
+
"""
|
|
609
|
+
UPDATE _pending_tool_calls
|
|
610
|
+
SET status = ?,
|
|
611
|
+
superseded_by = ?
|
|
612
|
+
WHERE id != ?
|
|
613
|
+
AND status = ?
|
|
614
|
+
AND (valid_until IS NULL OR valid_until > ?)
|
|
615
|
+
AND (org_id, scope_hash, tool_name, dedup_key) = (
|
|
616
|
+
SELECT org_id, scope_hash, tool_name, dedup_key
|
|
617
|
+
FROM _pending_tool_calls
|
|
618
|
+
WHERE id = ?
|
|
619
|
+
)
|
|
620
|
+
""",
|
|
621
|
+
(
|
|
622
|
+
PendingToolCallStatus.SUPERSEDED.value,
|
|
623
|
+
call_id,
|
|
624
|
+
call_id,
|
|
625
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
626
|
+
_dt_str(resolved),
|
|
627
|
+
call_id,
|
|
628
|
+
),
|
|
629
|
+
)
|
|
630
|
+
self.conn.execute(
|
|
631
|
+
"""
|
|
632
|
+
UPDATE _run_tool_dependencies
|
|
633
|
+
SET resolved_at = ?,
|
|
634
|
+
consumed_at = NULL
|
|
635
|
+
WHERE pending_tool_call_id = ?
|
|
636
|
+
""",
|
|
637
|
+
(_dt_str(resolved), call_id),
|
|
638
|
+
)
|
|
639
|
+
self._mark_runs_ready_with_actionable_dependencies_unlocked(
|
|
640
|
+
now_s, pending_tool_call_id=call_id
|
|
641
|
+
)
|
|
642
|
+
except Exception:
|
|
643
|
+
self.conn.rollback()
|
|
644
|
+
raise
|
|
645
|
+
else:
|
|
646
|
+
self.conn.commit()
|
|
647
|
+
return self.get_pending_tool_call(call_id)
|
|
648
|
+
|
|
649
|
+
@SQLiteStorageBase.handle_exceptions
|
|
650
|
+
def mark_pending_tool_call_not_applicable(
|
|
651
|
+
self,
|
|
652
|
+
call_id: str,
|
|
653
|
+
*,
|
|
654
|
+
resolved_at: datetime | None = None,
|
|
655
|
+
valid_for_seconds: int,
|
|
656
|
+
) -> PendingToolCallRecord | None:
|
|
657
|
+
resolved = resolved_at or datetime.now(UTC)
|
|
658
|
+
valid_until = resolved + timedelta(seconds=valid_for_seconds)
|
|
659
|
+
now_s = _dt_str(resolved) or self._current_timestamp()
|
|
660
|
+
with self._lock:
|
|
661
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
662
|
+
try:
|
|
663
|
+
cur = self.conn.execute(
|
|
664
|
+
"""
|
|
665
|
+
UPDATE _pending_tool_calls
|
|
666
|
+
SET status = ?, result = ?, resolved_at = ?, valid_until = ?
|
|
667
|
+
WHERE id = ?
|
|
668
|
+
AND status IN (?, ?)
|
|
669
|
+
""",
|
|
670
|
+
(
|
|
671
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
672
|
+
_json_dumps(not_applicable_tool_result()),
|
|
673
|
+
_dt_str(resolved),
|
|
674
|
+
_dt_str(valid_until),
|
|
675
|
+
call_id,
|
|
676
|
+
PendingToolCallStatus.PENDING.value,
|
|
677
|
+
PendingToolCallStatus.RESOLVED.value,
|
|
678
|
+
),
|
|
679
|
+
)
|
|
680
|
+
if cur.rowcount == 0:
|
|
681
|
+
self.conn.commit()
|
|
682
|
+
return self.get_pending_tool_call(call_id)
|
|
683
|
+
self.conn.execute(
|
|
684
|
+
"""
|
|
685
|
+
UPDATE _run_tool_dependencies
|
|
686
|
+
SET resolved_at = COALESCE(resolved_at, ?),
|
|
687
|
+
consumed_at = ?
|
|
688
|
+
WHERE pending_tool_call_id = ?
|
|
689
|
+
AND consumed_at IS NULL
|
|
690
|
+
""",
|
|
691
|
+
(_dt_str(resolved), _dt_str(resolved), call_id),
|
|
692
|
+
)
|
|
693
|
+
self._mark_runs_ready_with_actionable_dependencies_unlocked(
|
|
694
|
+
now_s, pending_tool_call_id=call_id
|
|
695
|
+
)
|
|
696
|
+
self._finalize_runs_without_actionable_dependencies_unlocked(
|
|
697
|
+
now_s, pending_tool_call_id=call_id
|
|
698
|
+
)
|
|
699
|
+
except Exception:
|
|
700
|
+
self.conn.rollback()
|
|
701
|
+
raise
|
|
702
|
+
else:
|
|
703
|
+
self.conn.commit()
|
|
704
|
+
return self.get_pending_tool_call(call_id)
|