@borgee/agents-host 0.2.31 → 0.2.32
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/README.md +64 -0
- package/dist/cli-args.d.ts +3 -1
- package/dist/cli-args.js +16 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +12 -1
- package/dist/context/prompt.js +1 -1
- package/dist/gateway/localhost-gateway.js +29 -1
- package/dist/index.js +4 -1
- package/dist/policy/authorization-audit.d.ts +1 -0
- package/dist/policy/gateway-authorization.d.ts +1 -1
- package/dist/policy/gateway-authorization.js +27 -4
- package/dist/state-paths.d.ts +5 -0
- package/dist/state-paths.js +12 -0
- package/dist/update/package-installation.d.ts +52 -0
- package/dist/update/package-installation.js +90 -0
- package/dist/update/package-manager.d.ts +33 -0
- package/dist/update/package-manager.js +137 -0
- package/dist/update/semantic-version.d.ts +7 -0
- package/dist/update/semantic-version.js +76 -0
- package/dist/update/update-command.d.ts +9 -0
- package/dist/update/update-command.js +55 -0
- package/dist/update/update-notice.d.ts +39 -0
- package/dist/update/update-notice.js +161 -0
- package/package.json +3 -1
- package/skills/borgee-agent/SKILL.md +5 -3
- package/skills/borgee-agent/borgee-agent.mjs +54 -21
- package/skills/borgee-agent/borgee-agent.py +53 -26
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from urllib.error import HTTPError
|
|
@@ -17,15 +18,23 @@ TASK_THREAD_MISSING_TASK_ID_ERROR = (
|
|
|
17
18
|
"Task assignment thread could not resolve the current task from persisted context "
|
|
18
19
|
"or local fallback. Pass --task-id explicitly."
|
|
19
20
|
)
|
|
21
|
+
# Python ints are arbitrary precision, so nothing can be rounded on the way into the query
|
|
22
|
+
# string; the bound is here only so both packaged CLIs accept and reject exactly the same
|
|
23
|
+
# inputs. It is the Node CLI that needs it: there an out-of-range magnitude re-stringifies as
|
|
24
|
+
# `1e+21`, which the gateway's `Number.parseInt(value, 10)` reads back as `1`.
|
|
25
|
+
INTEGER_ARGUMENT_PATTERN = re.compile(r"[+-]?[0-9]+")
|
|
26
|
+
MAX_SAFE_INTEGER = 2**53 - 1
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
def parse_int(flag: str, value: str | None) -> int:
|
|
23
30
|
if value is None:
|
|
24
31
|
raise ValueError(f"Missing value after {flag}")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
if not INTEGER_ARGUMENT_PATTERN.fullmatch(value):
|
|
33
|
+
raise ValueError(f"Invalid integer for {flag}: {value}")
|
|
34
|
+
parsed = int(value)
|
|
35
|
+
if abs(parsed) > MAX_SAFE_INTEGER:
|
|
36
|
+
raise ValueError(f"Integer out of range for {flag}: {value}")
|
|
37
|
+
return parsed
|
|
29
38
|
|
|
30
39
|
|
|
31
40
|
def parse_args(argv: list[str]) -> dict[str, object]:
|
|
@@ -80,6 +89,8 @@ def parse_args(argv: list[str]) -> dict[str, object]:
|
|
|
80
89
|
set_action("get-me")
|
|
81
90
|
elif arg == "--read-history":
|
|
82
91
|
set_action("read-history")
|
|
92
|
+
elif arg == "--read-task-history":
|
|
93
|
+
set_action("read-task-history")
|
|
83
94
|
elif arg == "--read-draft":
|
|
84
95
|
set_action("read-draft")
|
|
85
96
|
elif arg == "--list-users":
|
|
@@ -185,22 +196,42 @@ def ensure_visible_mentions(body: str, participant_ids: list[str]) -> str:
|
|
|
185
196
|
return next_body
|
|
186
197
|
|
|
187
198
|
|
|
188
|
-
def
|
|
199
|
+
def explicit_task_id(options: dict[str, object]) -> str | None:
|
|
189
200
|
task_id = options.get("task_id")
|
|
190
|
-
|
|
201
|
+
trimmed = task_id.strip() if isinstance(task_id, str) else ""
|
|
202
|
+
return trimmed if trimmed else None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def missing_task_id_error(action: str) -> ValueError:
|
|
206
|
+
return ValueError(f"Missing required --task-id <value> for --{action}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def require_explicit_task_id(action: str, options: dict[str, object]) -> str:
|
|
210
|
+
task_id = explicit_task_id(options)
|
|
211
|
+
if task_id is None:
|
|
212
|
+
raise missing_task_id_error(action)
|
|
213
|
+
return task_id
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def history_query_suffix(options: dict[str, object]) -> str:
|
|
217
|
+
query: dict[str, str] = {}
|
|
218
|
+
for key in ("limit", "before", "after"):
|
|
219
|
+
if options.get(key) is not None:
|
|
220
|
+
query[key] = str(options[key])
|
|
221
|
+
return f"?{urlencode(query)}" if query else ""
|
|
191
222
|
|
|
192
223
|
|
|
193
224
|
def resolve_task_id(payload: dict[str, object], action: str, options: dict[str, object]) -> str | None:
|
|
194
|
-
task_id = options
|
|
195
|
-
if
|
|
196
|
-
return task_id
|
|
225
|
+
task_id = explicit_task_id(options)
|
|
226
|
+
if task_id is not None:
|
|
227
|
+
return task_id
|
|
197
228
|
task_assignment_context = payload.get("taskAssignmentContext")
|
|
198
229
|
if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True:
|
|
199
230
|
current_task_id = task_assignment_context.get("currentTaskId")
|
|
200
231
|
if isinstance(current_task_id, str) and current_task_id.strip():
|
|
201
232
|
return current_task_id.strip()
|
|
202
233
|
return None
|
|
203
|
-
raise
|
|
234
|
+
raise missing_task_id_error(action)
|
|
204
235
|
|
|
205
236
|
|
|
206
237
|
def resolve_gateway_request(
|
|
@@ -225,15 +256,10 @@ def resolve_gateway_request(
|
|
|
225
256
|
if action == "get-me":
|
|
226
257
|
return f"{base_url}/v1/channels/{channel_id}/me", "GET", None, False
|
|
227
258
|
if action == "read-history":
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
query["before"] = str(options["before"])
|
|
233
|
-
if options.get("after") is not None:
|
|
234
|
-
query["after"] = str(options["after"])
|
|
235
|
-
suffix = f"?{urlencode(query)}" if query else ""
|
|
236
|
-
return f"{base_url}/v1/channels/{channel_id}/history{suffix}", "GET", None, False
|
|
259
|
+
return f"{base_url}/v1/channels/{channel_id}/history{history_query_suffix(options)}", "GET", None, False
|
|
260
|
+
if action == "read-task-history":
|
|
261
|
+
task_id = quote(require_explicit_task_id(action, options), safe="")
|
|
262
|
+
return f"{base_url}/v1/tasks/{task_id}/history{history_query_suffix(options)}", "GET", None, False
|
|
237
263
|
if action == "read-draft":
|
|
238
264
|
turn_execution_id = options.get("turn_execution_id")
|
|
239
265
|
if not isinstance(turn_execution_id, str) or not turn_execution_id.strip():
|
|
@@ -258,7 +284,7 @@ def resolve_gateway_request(
|
|
|
258
284
|
return f"{base_url}/v1/channels/{channel_id}/tasks", "GET", None, False
|
|
259
285
|
if action == "get-task":
|
|
260
286
|
task_id = resolve_task_id(payload, action, options)
|
|
261
|
-
if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and
|
|
287
|
+
if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and explicit_task_id(options) is None:
|
|
262
288
|
return f"{base_url}/v1/channels/{channel_id}/current-task", "GET", None, True
|
|
263
289
|
return f"{base_url}/v1/tasks/{quote(str(task_id), safe='')}", "GET", None, False
|
|
264
290
|
if action == "update-task":
|
|
@@ -274,7 +300,7 @@ def resolve_gateway_request(
|
|
|
274
300
|
request_body["description"] = options["description"]
|
|
275
301
|
if not request_body:
|
|
276
302
|
raise ValueError("At least one of --status, --assignee-id, --title, or --description is required for --update-task")
|
|
277
|
-
if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and
|
|
303
|
+
if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and explicit_task_id(options) is None:
|
|
278
304
|
return f"{base_url}/v1/channels/{channel_id}/current-task", "PATCH", request_body, True
|
|
279
305
|
return f"{base_url}/v1/tasks/{quote(str(task_id), safe='')}", "PATCH", request_body, False
|
|
280
306
|
raise ValueError(f"Unsupported gateway action: {action}")
|
|
@@ -296,11 +322,11 @@ def read_gateway_auth(auth_path: Path | None, expected_channel_id: object) -> di
|
|
|
296
322
|
|
|
297
323
|
|
|
298
324
|
def call_gateway(payload: dict[str, object], action: str, options: dict[str, object]) -> object:
|
|
325
|
+
url, method, request_body, uses_current_thread_fallback = resolve_gateway_request(payload, action, options)
|
|
299
326
|
auth = read_gateway_auth(
|
|
300
327
|
options.get("auth_path") if isinstance(options.get("auth_path"), Path) else None,
|
|
301
328
|
payload.get("channelId"),
|
|
302
329
|
)
|
|
303
|
-
url, method, request_body, uses_current_thread_fallback = resolve_gateway_request(payload, action, options)
|
|
304
330
|
|
|
305
331
|
data = None
|
|
306
332
|
headers = {"Authorization": f"Bearer {auth['token']}"}
|
|
@@ -341,11 +367,12 @@ def call_gateway(payload: dict[str, object], action: str, options: dict[str, obj
|
|
|
341
367
|
response_body = response.read().decode("utf-8")
|
|
342
368
|
return json.loads(response_body) if response_body else None
|
|
343
369
|
except HTTPError as exc:
|
|
344
|
-
|
|
370
|
+
error_body = exc.read().decode("utf-8")
|
|
371
|
+
error_payload = json.loads(error_body) if error_body else None
|
|
372
|
+
if uses_current_thread_fallback and isinstance(error_payload, dict) and error_payload.get("error") == "not_found":
|
|
345
373
|
raise RuntimeError(TASK_THREAD_MISSING_TASK_ID_ERROR) from exc
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
raise RuntimeError(f"Gateway request failed: {exc}") from exc
|
|
374
|
+
reported_body = json.dumps(error_payload, separators=(",", ":"), ensure_ascii=False)
|
|
375
|
+
raise RuntimeError(f"Gateway request failed with {exc.code}: {reported_body}") from exc
|
|
349
376
|
|
|
350
377
|
|
|
351
378
|
def redact_bootstrap(payload: dict[str, object], context_path: Path) -> dict[str, object]:
|