gemcode 0.3.89__py3-none-any.whl → 0.3.91__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.
- gemcode/invoke.py +17 -1
- gemcode/tools/veomem_tools.py +252 -0
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/METADATA +1 -1
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/RECORD +8 -7
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/WHEEL +0 -0
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/entry_points.txt +0 -0
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/licenses/LICENSE +0 -0
- {gemcode-0.3.89.dist-info → gemcode-0.3.91.dist-info}/top_level.txt +0 -0
gemcode/invoke.py
CHANGED
|
@@ -175,10 +175,26 @@ async def run_turn(
|
|
|
175
175
|
if attachment_paths:
|
|
176
176
|
from gemcode.multimodal_input import build_user_content
|
|
177
177
|
|
|
178
|
+
# Optional HITL gate for local attachment reads/upload materialization.
|
|
179
|
+
# This is separate from workspace trust: the OS may still require
|
|
180
|
+
# user-granted "Files and Folders" permissions on first access.
|
|
181
|
+
# If approved once, we don't re-prompt for the rest of this session.
|
|
182
|
+
attach_allow = True
|
|
183
|
+
if cfg is not None:
|
|
184
|
+
attach_allow = bool(getattr(cfg, "interactive_permission_ask", False))
|
|
185
|
+
attach_allow = attach_allow and hasattr(sys.stdin, "isatty") and sys.stdin.isatty()
|
|
186
|
+
if attach_allow and not bool(getattr(cfg, "_attachments_allowed", False)):
|
|
187
|
+
attach_allow = _prompt_yes_no(
|
|
188
|
+
"Allow GemCode to read and upload the attached file(s) from disk? (y/n) "
|
|
189
|
+
)
|
|
190
|
+
if attach_allow:
|
|
191
|
+
object.__setattr__(cfg, "_attachments_allowed", True)
|
|
192
|
+
effective_attachments = attachment_paths if attach_allow else None
|
|
193
|
+
|
|
178
194
|
root = cfg.project_root if cfg is not None else Path.cwd()
|
|
179
195
|
current_message, attach_warn = build_user_content(
|
|
180
196
|
prompt,
|
|
181
|
-
|
|
197
|
+
effective_attachments,
|
|
182
198
|
project_root=root,
|
|
183
199
|
)
|
|
184
200
|
for w in attach_warn:
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _enabled() -> bool:
|
|
9
|
+
return os.environ.get("GEMCODE_VEOMEM", "").strip().lower() in ("1", "true", "yes", "on")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_veomem_tools(cfg) -> list[Any]:
|
|
13
|
+
"""
|
|
14
|
+
VeoMem recall tools (token-efficient, 3-step retrieval workflow).
|
|
15
|
+
|
|
16
|
+
Exposes a compact flow to the agent:
|
|
17
|
+
1) `veomem_search` returns a small index of matching observation IDs
|
|
18
|
+
2) `veomem_timeline` fetches compact context around a specific anchor ID
|
|
19
|
+
3) `veomem_get_observations` fetches full text for a small set of IDs
|
|
20
|
+
"""
|
|
21
|
+
if not _enabled():
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
project_root = Path(getattr(cfg, "project_root"))
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from veomem.store import get_observations as _get_observations
|
|
28
|
+
from veomem.store import search as _search
|
|
29
|
+
from veomem.store import timeline as _timeline
|
|
30
|
+
except Exception:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
def veomem_search(
|
|
34
|
+
query: str,
|
|
35
|
+
limit: int = 10,
|
|
36
|
+
kind: str | None = None,
|
|
37
|
+
wing: str | None = None,
|
|
38
|
+
room: str | None = None,
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Search VeoMem for relevant prior tool observations.
|
|
42
|
+
|
|
43
|
+
Returns a small index of hits (IDs + snippets). Typical usage:
|
|
44
|
+
- call `veomem_timeline()` for one promising anchor
|
|
45
|
+
- then call `veomem_get_observations()` for a small set of IDs
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(query, str) or not query.strip():
|
|
48
|
+
return {"error": "query must be a non-empty string"}
|
|
49
|
+
return _search(
|
|
50
|
+
project_root,
|
|
51
|
+
query=str(query),
|
|
52
|
+
limit=int(limit),
|
|
53
|
+
kind=str(kind) if kind else None,
|
|
54
|
+
wing=str(wing) if wing else None,
|
|
55
|
+
room=str(room) if room else None,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def veomem_timeline(
|
|
59
|
+
id: int,
|
|
60
|
+
window_ms: int = 120000,
|
|
61
|
+
limit: int = 40,
|
|
62
|
+
) -> dict[str, Any]:
|
|
63
|
+
"""
|
|
64
|
+
Fetch compact context around a specific VeoMem observation id.
|
|
65
|
+
|
|
66
|
+
This is the "timeline" step: it returns neighbors around an anchor.
|
|
67
|
+
The agent then decides which IDs to fetch in full.
|
|
68
|
+
"""
|
|
69
|
+
return _timeline(
|
|
70
|
+
project_root,
|
|
71
|
+
observation_id=int(id),
|
|
72
|
+
window_ms=int(window_ms),
|
|
73
|
+
limit=int(limit),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def veomem_get_observations(
|
|
77
|
+
ids: str,
|
|
78
|
+
max_chars: int = 8000,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Fetch full observation text for selected VeoMem observation IDs.
|
|
82
|
+
|
|
83
|
+
ids:
|
|
84
|
+
Comma-separated list, e.g. "12,13,21"
|
|
85
|
+
|
|
86
|
+
The returned `text` is truncated to `max_chars` per observation.
|
|
87
|
+
"""
|
|
88
|
+
if not isinstance(ids, str) or not ids.strip():
|
|
89
|
+
return {"error": "ids must be a non-empty comma-separated string"}
|
|
90
|
+
|
|
91
|
+
parts = [x.strip() for x in ids.split(",") if x.strip()]
|
|
92
|
+
id_list: list[int] = []
|
|
93
|
+
for p in parts:
|
|
94
|
+
try:
|
|
95
|
+
id_list.append(int(p))
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
if not id_list:
|
|
100
|
+
return {"error": "no valid integer ids found"}
|
|
101
|
+
|
|
102
|
+
rows = _get_observations(project_root, ids=id_list)
|
|
103
|
+
out: list[dict[str, Any]] = []
|
|
104
|
+
for o in rows:
|
|
105
|
+
t = (o.text or "").strip().replace("\n", " ")
|
|
106
|
+
truncated = False
|
|
107
|
+
if len(t) > int(max_chars):
|
|
108
|
+
t = t[: int(max_chars)].rstrip() + "…"
|
|
109
|
+
truncated = True
|
|
110
|
+
out.append(
|
|
111
|
+
{
|
|
112
|
+
"id": int(o.id),
|
|
113
|
+
"kind": str(o.kind),
|
|
114
|
+
"title": str(o.title),
|
|
115
|
+
"wing": str(o.wing),
|
|
116
|
+
"room": str(o.room),
|
|
117
|
+
"text": t,
|
|
118
|
+
"truncated": truncated,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return {"ok": True, "results": out, "count": len(out)}
|
|
123
|
+
|
|
124
|
+
veomem_search.__name__ = "veomem_search"
|
|
125
|
+
veomem_timeline.__name__ = "veomem_timeline"
|
|
126
|
+
veomem_get_observations.__name__ = "veomem_get_observations"
|
|
127
|
+
|
|
128
|
+
return [veomem_search, veomem_timeline, veomem_get_observations]
|
|
129
|
+
import os
|
|
130
|
+
from pathlib import Path
|
|
131
|
+
from typing import Any
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _enabled() -> bool:
|
|
135
|
+
return os.environ.get("GEMCODE_VEOMEM", "").strip().lower() in ("1", "true", "yes", "on")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def make_veomem_tools(cfg) -> list[Any]:
|
|
139
|
+
"""
|
|
140
|
+
VeoMem recall tools (compact 3-step retrieval workflow).
|
|
141
|
+
|
|
142
|
+
Exposes a token-efficient 3-step flow to the agent:
|
|
143
|
+
1) `veomem_search` returns an index of matching observation IDs
|
|
144
|
+
2) `veomem_timeline` fetches compact context around a specific anchor ID
|
|
145
|
+
3) `veomem_get_observations` fetches full text for a small set of IDs
|
|
146
|
+
"""
|
|
147
|
+
if not _enabled():
|
|
148
|
+
return []
|
|
149
|
+
|
|
150
|
+
project_root = Path(getattr(cfg, "project_root"))
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
from veomem.store import get_observations as _get_observations
|
|
154
|
+
from veomem.store import search as _search
|
|
155
|
+
from veomem.store import timeline as _timeline
|
|
156
|
+
except Exception:
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
def veomem_search(
|
|
160
|
+
query: str,
|
|
161
|
+
limit: int = 10,
|
|
162
|
+
kind: str | None = None,
|
|
163
|
+
wing: str | None = None,
|
|
164
|
+
room: str | None = None,
|
|
165
|
+
) -> dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Search VeoMem for relevant prior tool observations (FTS5/BM25).
|
|
168
|
+
|
|
169
|
+
Returns a small "index" of hits (IDs + snippets). Prefer:
|
|
170
|
+
- call `veomem_timeline()` for one promising anchor
|
|
171
|
+
- then call `veomem_get_observations()` for a small set of IDs
|
|
172
|
+
"""
|
|
173
|
+
if not isinstance(query, str) or not query.strip():
|
|
174
|
+
return {"error": "query must be a non-empty string"}
|
|
175
|
+
return _search(
|
|
176
|
+
project_root,
|
|
177
|
+
query=str(query),
|
|
178
|
+
limit=int(limit),
|
|
179
|
+
kind=str(kind) if kind else None,
|
|
180
|
+
wing=str(wing) if wing else None,
|
|
181
|
+
room=str(room) if room else None,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def veomem_timeline(
|
|
185
|
+
id: int,
|
|
186
|
+
window_ms: int = 120000,
|
|
187
|
+
limit: int = 40,
|
|
188
|
+
) -> dict[str, Any]:
|
|
189
|
+
"""
|
|
190
|
+
Fetch compact context around a specific VeoMem observation id.
|
|
191
|
+
|
|
192
|
+
This is the "timeline" step: it returns neighbors around an anchor
|
|
193
|
+
(usually the agent should then decide which IDs to fetch in full).
|
|
194
|
+
"""
|
|
195
|
+
return _timeline(
|
|
196
|
+
project_root,
|
|
197
|
+
observation_id=int(id),
|
|
198
|
+
window_ms=int(window_ms),
|
|
199
|
+
limit=int(limit),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def veomem_get_observations(
|
|
203
|
+
ids: str,
|
|
204
|
+
max_chars: int = 8000,
|
|
205
|
+
) -> dict[str, Any]:
|
|
206
|
+
"""
|
|
207
|
+
Fetch full observation text for selected VeoMem observation IDs.
|
|
208
|
+
|
|
209
|
+
ids:
|
|
210
|
+
Comma-separated list, e.g. "12,13,21"
|
|
211
|
+
|
|
212
|
+
The returned `text` is truncated to `max_chars` per observation.
|
|
213
|
+
"""
|
|
214
|
+
if not isinstance(ids, str) or not ids.strip():
|
|
215
|
+
return {"error": "ids must be a non-empty comma-separated string"}
|
|
216
|
+
parts = [x.strip() for x in ids.split(",") if x.strip()]
|
|
217
|
+
id_list: list[int] = []
|
|
218
|
+
for p in parts:
|
|
219
|
+
try:
|
|
220
|
+
id_list.append(int(p))
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
if not id_list:
|
|
224
|
+
return {"error": "no valid integer ids found"}
|
|
225
|
+
|
|
226
|
+
rows = _get_observations(project_root, ids=id_list)
|
|
227
|
+
out: list[dict[str, Any]] = []
|
|
228
|
+
for o in rows:
|
|
229
|
+
t = (o.text or "").strip().replace("\n", " ")
|
|
230
|
+
truncated = False
|
|
231
|
+
if len(t) > int(max_chars):
|
|
232
|
+
t = t[: int(max_chars)].rstrip() + "…"
|
|
233
|
+
truncated = True
|
|
234
|
+
out.append(
|
|
235
|
+
{
|
|
236
|
+
"id": int(o.id),
|
|
237
|
+
"kind": str(o.kind),
|
|
238
|
+
"title": str(o.title),
|
|
239
|
+
"wing": str(o.wing),
|
|
240
|
+
"room": str(o.room),
|
|
241
|
+
"text": t,
|
|
242
|
+
"truncated": truncated,
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
return {"ok": True, "results": out, "count": len(out)}
|
|
246
|
+
|
|
247
|
+
veomem_search.__name__ = "veomem_search"
|
|
248
|
+
veomem_timeline.__name__ = "veomem_timeline"
|
|
249
|
+
veomem_get_observations.__name__ = "veomem_get_observations"
|
|
250
|
+
|
|
251
|
+
return [veomem_search, veomem_timeline, veomem_get_observations]
|
|
252
|
+
|
|
@@ -21,7 +21,7 @@ gemcode/ide_protocol.py,sha256=WJO4KdwyxjQcH1O_vTn7SPuy1ZZMm0eC8_xRLA9RYQo,2108
|
|
|
21
21
|
gemcode/ide_stdio.py,sha256=qDZ8qCR0kWipvyxLJ3tbZfAXChZtosi46dLeNuMejFk,11066
|
|
22
22
|
gemcode/intent_classifier.py,sha256=YfRVEe8gHeKlRkjuSWef1bZ0MPBwyYMp5jymP5Vig5U,8507
|
|
23
23
|
gemcode/interactions.py,sha256=B0b3QNE_I2i5_HtiebX4ehhjlc4Nbqjf_XbvcTLyJT0,641
|
|
24
|
-
gemcode/invoke.py,sha256=
|
|
24
|
+
gemcode/invoke.py,sha256=ik8biwJVymL0CQ_JVmPqrpnYcn9nFa0-ue4nD-99ssM,11798
|
|
25
25
|
gemcode/kaira_daemon.py,sha256=Bzkpc96HocfYAV9D5skid_Gi4bJDOLgO5YlD8vbTgyY,6960
|
|
26
26
|
gemcode/learning.py,sha256=o4Ivczm626NPRiNbSEb7-RvKJMefnv0ZpYt4UB2C3JA,3856
|
|
27
27
|
gemcode/limits.py,sha256=3j6N8V643X7-nP-cAIf37Xg9bkGpQlEJB3nPptApQWk,2504
|
|
@@ -94,6 +94,7 @@ gemcode/tools/subtask.py,sha256=iH2gWSKFXzhX_4yrfBbR6p-oIzqUmarSaA51I8luTUs,1007
|
|
|
94
94
|
gemcode/tools/tasks.py,sha256=4kDjMuoxgD3kJtM4fy2bOQl0ak6CljdA-nN1-tJG-dw,7668
|
|
95
95
|
gemcode/tools/think.py,sha256=bch9bsz1bs24uia5l3utnNSkT84mIyU_EzMgi82e60Y,1624
|
|
96
96
|
gemcode/tools/todo.py,sha256=dlGfcNce1WsJ5Y9txrDL3SoF6Hv2rms9r1cvGPs6qIs,5798
|
|
97
|
+
gemcode/tools/veomem_tools.py,sha256=8pxY0varxvwnox4tKPfX5axHUA7yhcHPlchYHaEFSVw,7241
|
|
97
98
|
gemcode/tools/web.py,sha256=I-6-GgCVKblc9zVFfilWLHoJZfri7_pC2MpT52ZZarE,5078
|
|
98
99
|
gemcode/tools/web_search.py,sha256=UsO3W2FDRSJYtXQTT0jildzEQLt6P3XeR7KyUi-Dxqs,9163
|
|
99
100
|
gemcode/tui/input_handler.py,sha256=Az8SbPaPHksIoibjph8gevMnfjagR1b-34_wpKbEhgQ,8259
|
|
@@ -105,9 +106,9 @@ gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
|
|
|
105
106
|
gemcode/web/sse_adapter.py,sha256=fXhKxn_bdJJUGqlmvkxLNSYL-ZiIZDaLHtQCF_BheRc,7108
|
|
106
107
|
gemcode/web/terminal_repl.py,sha256=fQt895g0qcr6VBhXfv_5b_bsC5zHT5-MO0ysBdgi2Fg,3886
|
|
107
108
|
gemcode/web/web_sse_compat.py,sha256=9A2s-GI7El7AotJqhO263FrLwppCXXkdydZ5EiOQbao,504
|
|
108
|
-
gemcode-0.3.
|
|
109
|
-
gemcode-0.3.
|
|
110
|
-
gemcode-0.3.
|
|
111
|
-
gemcode-0.3.
|
|
112
|
-
gemcode-0.3.
|
|
113
|
-
gemcode-0.3.
|
|
109
|
+
gemcode-0.3.91.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
|
|
110
|
+
gemcode-0.3.91.dist-info/METADATA,sha256=iwDj-SFo0LsgLe9w1FlsBrzcOFdyUSueiSwY9oPvvvg,17084
|
|
111
|
+
gemcode-0.3.91.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
112
|
+
gemcode-0.3.91.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
|
|
113
|
+
gemcode-0.3.91.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
|
|
114
|
+
gemcode-0.3.91.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|