@dotdotgod/hermes 0.6.0 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/hermes",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Hermes CLI and messaging-gateway adapter without Plan Mode.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -13,7 +13,7 @@
13
13
  "devDependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.25.2",
15
15
  "esbuild": "0.27.3",
16
- "@dotdotgod/context": "0.6.0"
16
+ "@dotdotgod/context": "0.6.1"
17
17
  },
18
18
  "files": [
19
19
  "plugin.yaml",
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: dotdotgod
2
- version: 0.6.0
2
+ version: 0.6.1
3
3
  description: Project memory, isolated context tools and impact checks for Hermes CLI and messaging gateway; no Plan Mode.
4
4
  author: dotdotgod
5
5
  license: Elastic-2.0
package/runtime.py CHANGED
@@ -52,10 +52,13 @@ class Runtime:
52
52
  labels = access.get(session.principal, []) if isinstance(access, dict) else []
53
53
  return {label: roots[label] for label in labels if label in roots} if isinstance(labels, list) else {}
54
54
 
55
- def session(self, session_id):
55
+ def trusted_session(self, session_id):
56
56
  if not session_id or session_id not in self.sessions:
57
57
  raise ValueError("No trusted host session; start a turn before using dotdotgod")
58
- session = self.sessions[session_id]
58
+ return self.sessions[session_id]
59
+
60
+ def session(self, session_id):
61
+ session = self.trusted_session(session_id)
59
62
  if session.root is None:
60
63
  raise ValueError("Select an authorized repository with dotdotgod_select_root first")
61
64
  if session.parent_id:
@@ -122,8 +125,23 @@ class Runtime:
122
125
 
123
126
  def call(self, name, args, session_id):
124
127
  with self.lock:
128
+ session = self.trusted_session(session_id)
125
129
  if name == "dotdotgod_select_root":
126
- return self.select(self.sessions[session_id], args.get("label"))
130
+ if not self.allowed(session):
131
+ raise PermissionError("No authorized repository labels; only dotdotgod_project_initialize is available")
132
+ return self.select(session, args.get("label"))
133
+ if session.root is None and not session.parent_id and name == "dotdotgod_project_initialize":
134
+ target = args.get("root")
135
+ if not isinstance(target, str) or not Path(target).is_absolute():
136
+ raise ValueError("Initialization without selection requires an explicit absolute root directory")
137
+ root = Path(target).resolve(strict=True)
138
+ if not root.is_dir():
139
+ raise ValueError("Initialization root must be a directory")
140
+ proxy = Proxy(root, digest(session.principal + "\0" + session.id + "\0initialize"))
141
+ try:
142
+ return proxy.call(name, {**args, "root": str(root)})
143
+ finally:
144
+ proxy.close()
127
145
  session = self.session(session_id)
128
146
  if name == "dotdotgod_impact_status":
129
147
  return envelope({"ok": True, "pending": list(self.pending(session))})
@@ -160,6 +178,13 @@ class Runtime:
160
178
 
161
179
  def before_tool(self, tool_name, args, session_id="", **kwargs):
162
180
  try:
181
+ session = self.trusted_session(session_id)
182
+ if session.root is None and not session.parent_id:
183
+ if not tool_name.startswith("dotdotgod_") or tool_name == "dotdotgod_project_initialize":
184
+ return None
185
+ if tool_name == "dotdotgod_select_root" and self.allowed(session):
186
+ return None
187
+ return {"action": "block", "message": "dotdotgod is inactive without a selected repository; only initialization is available (selection requires authorized labels)"}
163
188
  if tool_name == "dotdotgod_select_root":
164
189
  return None
165
190
  session = self.session(session_id)
@@ -194,7 +219,11 @@ class Runtime:
194
219
  elif platform in {"", "cli"}:
195
220
  session.root = Path(self.ctx.get_config("cli_root", "") or self.launch_root).resolve(strict=True)
196
221
  if session.root is None:
197
- return {"context": "Select a repository using dotdotgod_select_root. Authorized labels: " + json.dumps(list(self.allowed(session)))}
222
+ labels = list(self.allowed(session))
223
+ context = "dotdotgod is inactive; ordinary host tools remain available. Only dotdotgod_project_initialize may run without selection, with an explicit absolute root and existing write confirmation. Initialization does not select or authorize a repository."
224
+ if labels:
225
+ context += " Optionally select a repository using dotdotgod_select_root. Authorized labels: " + json.dumps(labels)
226
+ return {"context": context}
198
227
  try:
199
228
  self.session(session_id)
200
229
  parts = []