@borgee/agents-host 0.2.33 → 0.2.44

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.
Files changed (60) hide show
  1. package/README.md +28 -7
  2. package/dist/agents-host.d.ts +19 -0
  3. package/dist/agents-host.js +163 -44
  4. package/dist/chat/chat-control-plane.d.ts +9 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +10 -1
  6. package/dist/chat/sdk-chat-control-plane.js +9 -0
  7. package/dist/cli-args.d.ts +1 -1
  8. package/dist/cli-args.js +5 -0
  9. package/dist/compatibility-gates.d.ts +1 -0
  10. package/dist/compatibility-gates.js +2 -0
  11. package/dist/config.d.ts +4 -1
  12. package/dist/config.js +21 -3
  13. package/dist/context/injection.d.ts +5 -3
  14. package/dist/context/injection.js +45 -29
  15. package/dist/context/main-session-delegation.d.ts +1 -0
  16. package/dist/context/main-session-delegation.js +6 -0
  17. package/dist/context/prompt.js +25 -35
  18. package/dist/context/skill-manual.d.ts +13 -0
  19. package/dist/context/skill-manual.js +18 -0
  20. package/dist/context/turn-preparation.js +13 -4
  21. package/dist/gateway/localhost-gateway.js +75 -1
  22. package/dist/hosted-turn-content.d.ts +15 -0
  23. package/dist/hosted-turn-content.js +50 -0
  24. package/dist/local-config.js +10 -1
  25. package/dist/managed-daemon.d.ts +3 -2
  26. package/dist/managed-daemon.js +82 -29
  27. package/dist/plugin-sdk.js +58 -1
  28. package/dist/plugin-sdk.js.map +2 -2
  29. package/dist/policy/gateway-authorization.d.ts +18 -3
  30. package/dist/policy/gateway-authorization.js +33 -1
  31. package/dist/providers/claude/adapter.d.ts +3 -1
  32. package/dist/providers/claude/adapter.js +13 -1
  33. package/dist/providers/claude/cli-client.d.ts +36 -3
  34. package/dist/providers/claude/cli-client.js +225 -37
  35. package/dist/providers/codex/adapter.d.ts +3 -1
  36. package/dist/providers/codex/adapter.js +13 -1
  37. package/dist/providers/codex/cli-client.d.ts +35 -3
  38. package/dist/providers/codex/cli-client.js +212 -31
  39. package/dist/providers/codex/project-doc.js +16 -29
  40. package/dist/providers/copilot/adapter.d.ts +3 -1
  41. package/dist/providers/copilot/adapter.js +13 -1
  42. package/dist/providers/copilot/cli-client.d.ts +34 -2
  43. package/dist/providers/copilot/cli-client.js +196 -18
  44. package/dist/providers/create-provider.d.ts +1 -1
  45. package/dist/providers/create-provider.js +30 -14
  46. package/dist/providers/idle-backend-shutdown.d.ts +16 -0
  47. package/dist/providers/idle-backend-shutdown.js +53 -0
  48. package/dist/providers/provider-adapter.d.ts +35 -0
  49. package/dist/providers/provider-adapter.js +44 -1
  50. package/dist/state-paths.d.ts +9 -1
  51. package/dist/state-paths.js +22 -3
  52. package/dist/types.d.ts +40 -2
  53. package/package.json +2 -2
  54. package/skills/borgee-agent/SKILL.md +133 -35
  55. package/skills/borgee-agent/references/errors.md +38 -0
  56. package/skills/borgee-agent/references/task-properties.md +30 -0
  57. package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
  58. package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
  59. package/skills/borgee-agent/borgee-agent.mjs +0 -507
  60. package/skills/borgee-agent/borgee-agent.py +0 -438
@@ -1,438 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import re
7
- import sys
8
- from pathlib import Path
9
- from urllib.error import HTTPError
10
- from urllib.parse import quote, urlencode
11
- from urllib.request import Request, urlopen
12
-
13
- TASK_THREAD_COLLECTION_COMMAND_ERROR = (
14
- "Task assignment threads only support --get-task and --update-task. "
15
- "Create/list tasks belong to the parent channel."
16
- )
17
- TASK_THREAD_MISSING_TASK_ID_ERROR = (
18
- "Task assignment thread could not resolve the current task from persisted context "
19
- "or local fallback. Pass --task-id explicitly."
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
27
-
28
-
29
- def parse_int(flag: str, value: str | None) -> int:
30
- if value is None:
31
- raise ValueError(f"Missing value after {flag}")
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
38
-
39
-
40
- def parse_args(argv: list[str]) -> dict[str, object]:
41
- context_path: Path | None = None
42
- auth_path: Path | None = None
43
- turn_execution_id: str | None = None
44
- action = "print-bootstrap"
45
- limit: int | None = None
46
- before: int | None = None
47
- after: int | None = None
48
- body: str | None = None
49
- reply_to_id: str | None = None
50
- mention_target_id: str | None = None
51
- mentions: list[str] = []
52
- title: str | None = None
53
- description: str | None = None
54
- assignee_id: str | None = None
55
- task_id: str | None = None
56
- status: str | None = None
57
- index = 0
58
-
59
- def set_action(next_action: str) -> None:
60
- nonlocal action
61
- if action != "print-bootstrap":
62
- raise ValueError("Only one action flag may be used per invocation")
63
- action = next_action
64
-
65
- while index < len(argv):
66
- arg = argv[index]
67
- if arg == "--context":
68
- index += 1
69
- if index >= len(argv):
70
- raise ValueError("Missing path after --context")
71
- context_path = Path(argv[index]).resolve()
72
- elif arg == "--auth-path":
73
- index += 1
74
- if index >= len(argv):
75
- raise ValueError("Missing path after --auth-path")
76
- auth_path = Path(argv[index]).resolve()
77
- elif arg == "--turn-execution-id":
78
- index += 1
79
- if index >= len(argv):
80
- raise ValueError("Missing value after --turn-execution-id")
81
- turn_execution_id = argv[index].strip()
82
- elif arg == "--print-bootstrap":
83
- action = "print-bootstrap"
84
- elif arg == "--health":
85
- set_action("health")
86
- elif arg == "--read-bootstrap":
87
- set_action("read-bootstrap")
88
- elif arg == "--get-me":
89
- set_action("get-me")
90
- elif arg == "--read-history":
91
- set_action("read-history")
92
- elif arg == "--read-task-history":
93
- set_action("read-task-history")
94
- elif arg == "--read-draft":
95
- set_action("read-draft")
96
- elif arg == "--list-users":
97
- set_action("list-users")
98
- elif arg == "--send-message":
99
- set_action("send-message")
100
- elif arg == "--send-mention":
101
- set_action("send-mention")
102
- index += 1
103
- if index >= len(argv):
104
- raise ValueError("Missing value after --send-mention")
105
- mention_target_id = argv[index].strip()
106
- if not mention_target_id:
107
- raise ValueError("Missing value after --send-mention")
108
- elif arg == "--create-task":
109
- set_action("create-task")
110
- elif arg == "--list-tasks":
111
- set_action("list-tasks")
112
- elif arg == "--get-task":
113
- set_action("get-task")
114
- elif arg == "--update-task":
115
- set_action("update-task")
116
- elif arg == "--limit":
117
- index += 1
118
- limit = parse_int(arg, argv[index] if index < len(argv) else None)
119
- elif arg == "--before":
120
- index += 1
121
- before = parse_int(arg, argv[index] if index < len(argv) else None)
122
- elif arg == "--after":
123
- index += 1
124
- after = parse_int(arg, argv[index] if index < len(argv) else None)
125
- elif arg == "--body":
126
- index += 1
127
- if index >= len(argv):
128
- raise ValueError("Missing value after --body")
129
- body = argv[index]
130
- elif arg == "--reply-to":
131
- index += 1
132
- if index >= len(argv):
133
- raise ValueError("Missing value after --reply-to")
134
- reply_to_id = argv[index]
135
- elif arg == "--mention":
136
- index += 1
137
- if index >= len(argv):
138
- raise ValueError("Missing value after --mention")
139
- mention = argv[index].strip()
140
- if not mention:
141
- raise ValueError("Missing value after --mention")
142
- mentions.append(mention)
143
- elif arg == "--title":
144
- index += 1
145
- title = argv[index] if index < len(argv) else None
146
- elif arg == "--description":
147
- index += 1
148
- description = argv[index] if index < len(argv) else None
149
- elif arg == "--assignee-id":
150
- index += 1
151
- assignee_id = argv[index] if index < len(argv) else None
152
- elif arg == "--task-id":
153
- index += 1
154
- task_id = argv[index] if index < len(argv) else None
155
- elif arg == "--status":
156
- index += 1
157
- status = argv[index] if index < len(argv) else None
158
- else:
159
- raise ValueError(f"Unknown argument: {arg}")
160
- index += 1
161
-
162
- if context_path is None:
163
- raise ValueError("Missing required --context <path> argument")
164
- return {
165
- "context_path": context_path,
166
- "auth_path": auth_path,
167
- "turn_execution_id": turn_execution_id,
168
- "action": action,
169
- "limit": limit,
170
- "before": before,
171
- "after": after,
172
- "body": body,
173
- "reply_to_id": reply_to_id,
174
- "mention_target_id": mention_target_id,
175
- "mentions": mentions,
176
- "title": title,
177
- "description": description,
178
- "assignee_id": assignee_id,
179
- "task_id": task_id,
180
- "status": status,
181
- }
182
-
183
-
184
- def ensure_visible_mention(body: str, participant_id: str) -> str:
185
- token = f"<@{participant_id}>"
186
- if token in body:
187
- return body
188
- separator = "" if body.endswith((" ", "\n", "\t")) else " "
189
- return f"{body}{separator}{token}"
190
-
191
-
192
- def ensure_visible_mentions(body: str, participant_ids: list[str]) -> str:
193
- next_body = body
194
- for participant_id in participant_ids:
195
- next_body = ensure_visible_mention(next_body, participant_id)
196
- return next_body
197
-
198
-
199
- def explicit_task_id(options: dict[str, object]) -> str | None:
200
- task_id = options.get("task_id")
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 ""
222
-
223
-
224
- def resolve_task_id(payload: dict[str, object], action: str, options: dict[str, object]) -> str | None:
225
- task_id = explicit_task_id(options)
226
- if task_id is not None:
227
- return task_id
228
- task_assignment_context = payload.get("taskAssignmentContext")
229
- if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True:
230
- current_task_id = task_assignment_context.get("currentTaskId")
231
- if isinstance(current_task_id, str) and current_task_id.strip():
232
- return current_task_id.strip()
233
- return None
234
- raise missing_task_id_error(action)
235
-
236
-
237
- def resolve_gateway_request(
238
- payload: dict[str, object],
239
- action: str,
240
- options: dict[str, object],
241
- ) -> tuple[str, str, object | None, bool]:
242
- gateway = payload.get("localhostGateway")
243
- if not isinstance(gateway, dict):
244
- raise ValueError("Missing localhostGateway bootstrap metadata in the context payload")
245
- base_url = gateway.get("baseUrl")
246
- if not isinstance(base_url, str):
247
- raise ValueError("Missing localhostGateway bootstrap metadata in the context payload")
248
- task_assignment_context = payload.get("taskAssignmentContext")
249
- if action in ("create-task", "list-tasks") and isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True:
250
- raise ValueError(TASK_THREAD_COLLECTION_COMMAND_ERROR)
251
- channel_id = quote(str(payload.get("channelId", "")), safe="")
252
- if action == "health":
253
- return f"{base_url}/health", "GET", None, False
254
- if action == "read-bootstrap":
255
- return f"{base_url}/v1/channels/{channel_id}/bootstrap", "GET", None, False
256
- if action == "get-me":
257
- return f"{base_url}/v1/channels/{channel_id}/me", "GET", None, False
258
- if action == "read-history":
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
263
- if action == "read-draft":
264
- turn_execution_id = options.get("turn_execution_id")
265
- if not isinstance(turn_execution_id, str) or not turn_execution_id.strip():
266
- raise ValueError("Missing required --turn-execution-id value for --read-draft")
267
- suffix = urlencode({"turnExecutionId": turn_execution_id.strip()})
268
- return f"{base_url}/v1/channels/{channel_id}/draft?{suffix}", "GET", None, False
269
- if action == "list-users":
270
- return f"{base_url}/v1/channels/{channel_id}/users", "GET", None, False
271
- if action in ("send-message", "send-mention"):
272
- return f"{base_url}/v1/channels/{channel_id}/messages", "POST", None, False
273
- if action == "create-task":
274
- title = options.get("title")
275
- if not isinstance(title, str) or title == "":
276
- raise ValueError("Missing required --title <value> for --create-task")
277
- request_body: dict[str, object] = {"title": title}
278
- if options.get("description") is not None:
279
- request_body["description"] = options["description"]
280
- if options.get("assignee_id") is not None:
281
- request_body["assigneeId"] = options["assignee_id"]
282
- return f"{base_url}/v1/channels/{channel_id}/tasks", "POST", request_body, False
283
- if action == "list-tasks":
284
- return f"{base_url}/v1/channels/{channel_id}/tasks", "GET", None, False
285
- if action == "get-task":
286
- task_id = resolve_task_id(payload, action, options)
287
- if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and explicit_task_id(options) is None:
288
- return f"{base_url}/v1/channels/{channel_id}/current-task", "GET", None, True
289
- return f"{base_url}/v1/tasks/{quote(str(task_id), safe='')}", "GET", None, False
290
- if action == "update-task":
291
- task_id = resolve_task_id(payload, action, options)
292
- request_body: dict[str, object] = {}
293
- if options.get("status") is not None:
294
- request_body["status"] = options["status"]
295
- if options.get("assignee_id") is not None:
296
- request_body["assigneeId"] = options["assignee_id"]
297
- if options.get("title") is not None:
298
- request_body["title"] = options["title"]
299
- if options.get("description") is not None:
300
- request_body["description"] = options["description"]
301
- if not request_body:
302
- raise ValueError("At least one of --status, --assignee-id, --title, or --description is required for --update-task")
303
- if isinstance(task_assignment_context, dict) and task_assignment_context.get("active") is True and explicit_task_id(options) is None:
304
- return f"{base_url}/v1/channels/{channel_id}/current-task", "PATCH", request_body, True
305
- return f"{base_url}/v1/tasks/{quote(str(task_id), safe='')}", "PATCH", request_body, False
306
- raise ValueError(f"Unsupported gateway action: {action}")
307
-
308
-
309
- def read_gateway_auth(auth_path: Path | None, expected_channel_id: object) -> dict[str, str | None]:
310
- if auth_path is None:
311
- raise ValueError("Missing required --auth-path <path> argument for gateway access")
312
- auth_payload = json.loads(auth_path.read_text(encoding="utf-8"))
313
- token = None
314
- localhost_gateway = auth_payload.get("localhostGateway")
315
- if isinstance(localhost_gateway, dict):
316
- raw_token = localhost_gateway.get("token")
317
- if isinstance(raw_token, str):
318
- token = raw_token
319
- if auth_payload.get("channelId") != expected_channel_id or not token:
320
- raise ValueError("Missing localhost gateway auth payload for this channel")
321
- return {"token": token}
322
-
323
-
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)
326
- auth = read_gateway_auth(
327
- options.get("auth_path") if isinstance(options.get("auth_path"), Path) else None,
328
- payload.get("channelId"),
329
- )
330
-
331
- data = None
332
- headers = {"Authorization": f"Bearer {auth['token']}"}
333
- if action in ("send-message", "send-mention"):
334
- body = options.get("body")
335
- if not isinstance(body, str) or not body.strip():
336
- raise ValueError(f"Missing required --body value for --{action}")
337
- turn_execution_id = options.get("turn_execution_id")
338
- if not isinstance(turn_execution_id, str) or not turn_execution_id.strip():
339
- raise ValueError(f"Missing required --turn-execution-id value for --{action}")
340
- request_mentions = options.get("mentions")
341
- if action == "send-mention":
342
- mention_target_id = options.get("mention_target_id")
343
- if not isinstance(mention_target_id, str) or not mention_target_id.strip():
344
- raise ValueError("Missing required target user id after --send-mention")
345
- extra_mentions = options.get("mentions")
346
- if isinstance(extra_mentions, list) and len(extra_mentions) > 0:
347
- raise ValueError("Do not combine --send-mention with additional --mention flags")
348
- request_mentions = [mention_target_id.strip()]
349
- if isinstance(request_mentions, list) and len(request_mentions) > 0:
350
- body = ensure_visible_mentions(body, request_mentions)
351
- headers["Content-Type"] = "application/json; charset=utf-8"
352
- request_body = {
353
- "body": body,
354
- "turnExecutionId": turn_execution_id,
355
- }
356
- reply_to_id = options.get("reply_to_id")
357
- if isinstance(reply_to_id, str) and reply_to_id:
358
- request_body["replyToId"] = reply_to_id
359
- data = json.dumps(request_body).encode("utf-8")
360
- elif request_body is not None:
361
- headers["Content-Type"] = "application/json; charset=utf-8"
362
- data = json.dumps(request_body).encode("utf-8")
363
-
364
- request = Request(url, headers=headers, method=method, data=data)
365
- try:
366
- with urlopen(request) as response:
367
- response_body = response.read().decode("utf-8")
368
- return json.loads(response_body) if response_body else None
369
- except HTTPError as exc:
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":
373
- raise RuntimeError(TASK_THREAD_MISSING_TASK_ID_ERROR) 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
376
-
377
-
378
- def redact_bootstrap(payload: dict[str, object], context_path: Path) -> dict[str, object]:
379
- localhost_gateway = payload.get("localhostGateway")
380
- base_url = None
381
- collaboration = None
382
- if isinstance(localhost_gateway, dict):
383
- raw_base_url = localhost_gateway.get("baseUrl")
384
- if isinstance(raw_base_url, str):
385
- base_url = raw_base_url
386
- raw_collaboration = localhost_gateway.get("collaboration")
387
- if isinstance(raw_collaboration, dict):
388
- collaboration = raw_collaboration
389
- return {
390
- "kind": "borgee-agent-skill-bootstrap",
391
- "contextPath": str(context_path),
392
- "channelId": payload.get("channelId"),
393
- "skillRuntime": payload.get("skillRuntime"),
394
- "taskAssignmentContext": payload.get("taskAssignmentContext"),
395
- "localhostGateway": (
396
- {
397
- "baseUrl": base_url,
398
- **({"collaboration": collaboration} if collaboration is not None else {}),
399
- }
400
- if base_url
401
- else None
402
- ),
403
- }
404
-
405
-
406
- parsed = parse_args(sys.argv[1:])
407
- context_path = parsed["context_path"]
408
- payload = json.loads(context_path.read_text(encoding="utf-8"))
409
-
410
- if parsed["action"] == "print-bootstrap":
411
- print(json.dumps(redact_bootstrap(payload, context_path), indent=2))
412
- else:
413
- print(
414
- json.dumps(
415
- call_gateway(
416
- payload,
417
- str(parsed["action"]),
418
- {
419
- "context_path": context_path,
420
- "auth_path": parsed["auth_path"],
421
- "turn_execution_id": parsed["turn_execution_id"],
422
- "limit": parsed["limit"],
423
- "before": parsed["before"],
424
- "after": parsed["after"],
425
- "body": parsed["body"],
426
- "reply_to_id": parsed["reply_to_id"],
427
- "mention_target_id": parsed["mention_target_id"],
428
- "mentions": parsed["mentions"],
429
- "title": parsed["title"],
430
- "description": parsed["description"],
431
- "assignee_id": parsed["assignee_id"],
432
- "task_id": parsed["task_id"],
433
- "status": parsed["status"],
434
- },
435
- ),
436
- indent=2,
437
- )
438
- )