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 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
- attachment_paths,
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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.89
3
+ Version: 0.3.91
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -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=wyX39MHj_R_ttGVQGG7ORlcsTehUlpAJ6VMsDZ0qSD8,10856
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.89.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
109
- gemcode-0.3.89.dist-info/METADATA,sha256=fkUkGItD7CVXkFJYC1ddPFrSDjabmrkUzQqEF6v8LKU,17084
110
- gemcode-0.3.89.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
111
- gemcode-0.3.89.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
112
- gemcode-0.3.89.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
113
- gemcode-0.3.89.dist-info/RECORD,,
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,,