@gajae-code/coding-agent 0.7.0 → 0.7.2

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 (101) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/cli/notify-cli.d.ts +2 -0
  3. package/dist/types/config/settings-schema.d.ts +39 -2
  4. package/dist/types/extensibility/shared-events.d.ts +1 -0
  5. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  6. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +1 -1
  7. package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
  8. package/dist/types/gjc-runtime/tmux-sessions.d.ts +2 -0
  9. package/dist/types/lsp/types.d.ts +2 -0
  10. package/dist/types/notifications/attachment-registry.d.ts +17 -0
  11. package/dist/types/notifications/chat-adapters.d.ts +9 -0
  12. package/dist/types/notifications/config.d.ts +9 -1
  13. package/dist/types/notifications/engine.d.ts +59 -0
  14. package/dist/types/notifications/managed-daemon.d.ts +48 -0
  15. package/dist/types/notifications/telegram-daemon.d.ts +19 -0
  16. package/dist/types/notifications/threaded-inbound.d.ts +19 -0
  17. package/dist/types/notifications/threaded-render.d.ts +6 -1
  18. package/dist/types/session/agent-session.d.ts +2 -0
  19. package/dist/types/tools/fetch.d.ts +23 -0
  20. package/dist/types/tools/index.d.ts +1 -0
  21. package/dist/types/tools/telegram-send.d.ts +32 -0
  22. package/dist/types/web/insane/bridge.d.ts +103 -0
  23. package/dist/types/web/insane/url-guard.d.ts +22 -0
  24. package/dist/types/web/search/provider.d.ts +18 -1
  25. package/dist/types/web/search/providers/insane.d.ts +53 -0
  26. package/dist/types/web/search/providers/text-citations.d.ts +23 -0
  27. package/dist/types/web/search/types.d.ts +12 -4
  28. package/package.json +10 -8
  29. package/scripts/verify-insane-vendor.ts +132 -0
  30. package/src/cli/args.ts +1 -1
  31. package/src/cli/fast-help.ts +1 -1
  32. package/src/cli/notify-cli.ts +152 -5
  33. package/src/cli.ts +1 -3
  34. package/src/commands/team.ts +1 -1
  35. package/src/config/settings-schema.ts +30 -1
  36. package/src/defaults/gjc/skills/ralplan/SKILL.md +11 -4
  37. package/src/edit/modes/replace.ts +1 -1
  38. package/src/extensibility/shared-events.ts +1 -0
  39. package/src/gjc-runtime/launch-tmux.ts +27 -5
  40. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  41. package/src/gjc-runtime/ralplan-runtime.ts +2 -2
  42. package/src/gjc-runtime/tmux-common.ts +8 -0
  43. package/src/gjc-runtime/tmux-sessions.ts +8 -1
  44. package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
  45. package/src/gjc-runtime/workflow-manifest.ts +7 -2
  46. package/src/hashline/hash.ts +1 -1
  47. package/src/internal-urls/docs-index.generated.ts +9 -8
  48. package/src/lsp/config.ts +16 -3
  49. package/src/lsp/defaults.json +7 -0
  50. package/src/lsp/types.ts +2 -0
  51. package/src/modes/controllers/event-controller.ts +15 -0
  52. package/src/modes/interactive-mode.ts +46 -2
  53. package/src/modes/utils/context-usage.ts +2 -2
  54. package/src/notifications/attachment-registry.ts +23 -0
  55. package/src/notifications/chat-adapters.ts +147 -0
  56. package/src/notifications/config.ts +23 -2
  57. package/src/notifications/engine.ts +100 -0
  58. package/src/notifications/index.ts +224 -45
  59. package/src/notifications/managed-daemon.ts +163 -0
  60. package/src/notifications/telegram-daemon.ts +235 -14
  61. package/src/notifications/threaded-inbound.ts +60 -4
  62. package/src/notifications/threaded-render.ts +20 -2
  63. package/src/session/agent-session.ts +82 -51
  64. package/src/tools/ask.ts +3 -2
  65. package/src/tools/fetch.ts +78 -1
  66. package/src/tools/index.ts +3 -0
  67. package/src/tools/telegram-send.ts +137 -0
  68. package/src/web/insane/bridge.ts +350 -0
  69. package/src/web/insane/url-guard.ts +155 -0
  70. package/src/web/search/provider.ts +77 -18
  71. package/src/web/search/providers/anthropic.ts +70 -3
  72. package/src/web/search/providers/codex.ts +1 -119
  73. package/src/web/search/providers/gemini.ts +99 -0
  74. package/src/web/search/providers/insane.ts +551 -0
  75. package/src/web/search/providers/openai-compatible.ts +66 -32
  76. package/src/web/search/providers/text-citations.ts +111 -0
  77. package/src/web/search/types.ts +13 -2
  78. package/vendor/insane-search/LICENSE +21 -0
  79. package/vendor/insane-search/MANIFEST.json +24 -0
  80. package/vendor/insane-search/engine/__init__.py +23 -0
  81. package/vendor/insane-search/engine/__main__.py +128 -0
  82. package/vendor/insane-search/engine/bias_check.py +183 -0
  83. package/vendor/insane-search/engine/executor.py +254 -0
  84. package/vendor/insane-search/engine/fetch_chain.py +725 -0
  85. package/vendor/insane-search/engine/learning.py +175 -0
  86. package/vendor/insane-search/engine/phase0.py +214 -0
  87. package/vendor/insane-search/engine/safety.py +91 -0
  88. package/vendor/insane-search/engine/templates/package.json +11 -0
  89. package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
  90. package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
  91. package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
  92. package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
  93. package/vendor/insane-search/engine/tests/test_u1.py +200 -0
  94. package/vendor/insane-search/engine/tests/test_u4.py +131 -0
  95. package/vendor/insane-search/engine/tests/test_u5.py +163 -0
  96. package/vendor/insane-search/engine/tests/test_u7.py +124 -0
  97. package/vendor/insane-search/engine/transport.py +211 -0
  98. package/vendor/insane-search/engine/url_transforms.py +98 -0
  99. package/vendor/insane-search/engine/validators.py +331 -0
  100. package/vendor/insane-search/engine/waf_detector.py +214 -0
  101. package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
@@ -0,0 +1,254 @@
1
+ """Capability-matched executor for fallback attempts.
2
+
3
+ The fetch_chain's probe/grid phase uses curl_cffi directly. When curl can't
4
+ punch through (JS challenge, real-TLS detection), this module routes to the
5
+ right browser executor based on the profile's `capabilities_needed` tags:
6
+
7
+ needs_real_tls_stack + needs_js_exec → playwright_real_chrome.js
8
+ needs_js_exec only → Playwright MCP (if available)
9
+ needs_mobile_context (+ real_tls) → playwright_mobile_chrome.js
10
+
11
+ The JS templates live in `engine/templates/` and accept only generic
12
+ parameters ({{url}}, {{waitSelector}}, {{profileDir}}, {{device}}). No
13
+ site-specific logic.
14
+
15
+ Playwright MCP invocation requires caller's tool access; this module
16
+ provides the subprocess path for local JS templates but only stubs the MCP
17
+ path (MCP must be driven from the Claude session itself).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import shutil
24
+ import subprocess
25
+ import tempfile
26
+ import time
27
+ from typing import Optional
28
+
29
+ from .validators import Verdict, validate
30
+ from .waf_detector import load_profile
31
+ from .fetch_chain import Attempt
32
+
33
+
34
+ TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), "templates")
35
+
36
+
37
+ def _profile_dir_for(url: str, choice: str) -> str:
38
+ """Per-host + per-device Chrome profile directory.
39
+
40
+ The host is hashed (never stored as a site name) so the No-Site-Name Rule
41
+ holds while each host keeps an isolated, reusable profile. Desktop and
42
+ mobile get separate subdirs so emulation state never bleeds across.
43
+ """
44
+ import hashlib
45
+ from urllib.parse import urlsplit
46
+ host = (urlsplit(url).hostname or "unknown").lower()
47
+ host_hash = hashlib.sha1(host.encode("utf-8", "ignore")).hexdigest()[:16]
48
+ device = "mobile" if "mobile" in choice else "desktop"
49
+ return os.path.join(tempfile.gettempdir(), ".insane_pw", host_hash, device)
50
+
51
+
52
+ def _node_available() -> bool:
53
+ return shutil.which("node") is not None
54
+
55
+
56
+ def _chrome_channel_available() -> bool:
57
+ """Heuristic: try `node -e` to import playwright. Fallback to True, let script fail loudly."""
58
+ if not _node_available():
59
+ return False
60
+ if shutil.which("npx") is None:
61
+ return False
62
+ return True
63
+
64
+
65
+ def _pick_executor(capabilities: list[str], device_class: str) -> str:
66
+ caps = set(capabilities or [])
67
+ if device_class == "mobile" or "needs_mobile_context" in caps:
68
+ if "needs_real_tls_stack" in caps:
69
+ return "playwright_mobile_chrome"
70
+ return "playwright_mcp_mobile"
71
+ if "needs_real_tls_stack" in caps:
72
+ return "playwright_real_chrome"
73
+ if "needs_js_exec" in caps:
74
+ return "playwright_mcp"
75
+ return "playwright_real_chrome" # safest general fallback
76
+
77
+
78
+ def _run_node_template(template: str, args: dict, timeout: int = 90) -> tuple[int, str, str]:
79
+ """Run a Node.js template with args as JSON on stdin.
80
+
81
+ Template convention: reads `process.stdin` → JSON → runs fetch → writes
82
+ HTML to stdout; errors go to stderr with non-zero exit code.
83
+ """
84
+ path = os.path.join(TEMPLATES_DIR, template)
85
+ if not os.path.isfile(path):
86
+ return 127, "", f"template not found: {path}"
87
+ try:
88
+ proc = subprocess.run(
89
+ ["node", path],
90
+ input=json.dumps(args),
91
+ cwd=TEMPLATES_DIR,
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=timeout,
95
+ )
96
+ return proc.returncode, proc.stdout, proc.stderr
97
+ except subprocess.TimeoutExpired:
98
+ return 124, "", f"timeout after {timeout}s"
99
+ except Exception as e:
100
+ return 1, "", f"{type(e).__name__}:{e}"
101
+
102
+
103
+ class _FakeResp:
104
+ """Minimal response shim so validators.validate() works on Playwright HTML."""
105
+ def __init__(self, html: str, status: int = 200, final_url: str = ""):
106
+ self.text = html
107
+ self.status_code = status
108
+ self.url = final_url
109
+ self.cookies = _FakeCookies()
110
+ self.headers = {}
111
+
112
+
113
+ class _FakeCookies:
114
+ class _Jar:
115
+ def __iter__(self):
116
+ return iter([])
117
+ def __init__(self):
118
+ self.jar = self._Jar()
119
+ def __iter__(self):
120
+ return iter([])
121
+
122
+
123
+ def run_playwright_fallback(
124
+ url: str,
125
+ *,
126
+ profile_id: str,
127
+ success_selectors: Optional[list[str]] = None,
128
+ device_class: str = "auto",
129
+ timeout: int = 90,
130
+ profile_dir: Optional[str] = None,
131
+ force_executor: Optional[str] = None,
132
+ ) -> tuple[Attempt, str]:
133
+ """Invoke the appropriate Playwright executor.
134
+
135
+ force_executor: caller-specified executor name (from a profile's
136
+ `fallback_when_challenge` list). When set, it overrides capability-based
137
+ inference. Recognized values: "playwright_real_chrome",
138
+ "playwright_mobile_chrome", "playwright_mcp".
139
+
140
+ Returns (Attempt, html_content). Attempt.verdict reflects validation.
141
+ """
142
+ profile = load_profile(profile_id)
143
+ capabilities = profile.get("capabilities_needed") or []
144
+ choice = force_executor or _pick_executor(capabilities, device_class)
145
+
146
+ t0 = time.time()
147
+ att = Attempt(
148
+ phase="fallback",
149
+ executor=choice,
150
+ url=url,
151
+ url_transform="original",
152
+ impersonate=None,
153
+ referer="",
154
+ )
155
+
156
+ if choice.startswith("playwright_mcp"):
157
+ att.error = (
158
+ "Playwright MCP must be invoked from the Claude session — "
159
+ "call mcp__playwright__* tools directly instead of fetch_chain."
160
+ )
161
+ att.verdict = Verdict.UNKNOWN.value
162
+ att.elapsed_s = round(time.time() - t0, 3)
163
+ return att, ""
164
+
165
+ if not _chrome_channel_available():
166
+ att.error = "node/npx not available for local Playwright template"
167
+ att.verdict = Verdict.UNKNOWN.value
168
+ att.elapsed_s = round(time.time() - t0, 3)
169
+ return att, ""
170
+
171
+ template_map = {
172
+ "playwright_real_chrome": "playwright_real_chrome.js",
173
+ "playwright_mobile_chrome": "playwright_mobile_chrome.js",
174
+ }
175
+ template = template_map.get(choice)
176
+ if template is None:
177
+ att.error = f"no template for executor {choice}"
178
+ att.verdict = Verdict.UNKNOWN.value
179
+ att.elapsed_s = round(time.time() - t0, 3)
180
+ return att, ""
181
+
182
+ args: dict = {
183
+ "url": url,
184
+ # Per-host + per-device profile isolation. A single shared profile dir
185
+ # (the old default) leaked cookies/storage across hosts and caused
186
+ # profile-lock collisions when two fallbacks ran concurrently. Hashing
187
+ # the host (not storing it) keeps the No-Site-Name Rule intact while
188
+ # letting a host reuse its own warm storageState across calls.
189
+ "profileDir": profile_dir or _profile_dir_for(url, choice),
190
+ "timeout": timeout * 1000,
191
+ }
192
+ if choice == "playwright_mobile_chrome":
193
+ args["device"] = "iPhone 13 Pro"
194
+ if success_selectors:
195
+ args["waitSelector"] = success_selectors[0]
196
+
197
+ rc, stdout, stderr = _run_node_template(template, args, timeout=timeout + 10)
198
+ att.elapsed_s = round(time.time() - t0, 3)
199
+
200
+ if rc != 0 or not stdout:
201
+ att.error = (stderr or "no stdout")[:300]
202
+ att.verdict = Verdict.UNKNOWN.value
203
+ return att, ""
204
+
205
+ # stdout is a JSON envelope {html, finalUrl, status, cookies, userAgent}.
206
+ # Fall back to treating raw stdout as HTML for forward/backward compat.
207
+ html, final_url, status, cookies, user_agent, automation = _parse_envelope(stdout, url)
208
+
209
+ resp = _FakeResp(html, status=status, final_url=final_url)
210
+ vr = validate(resp, success_selectors=success_selectors)
211
+ att.status = status
212
+ att.body_size = len(html)
213
+ att.verdict = vr.verdict.value
214
+ att.reasons = list(vr.reasons) + ([f"automation:{automation}"] if automation else [])
215
+ att.url = final_url or url
216
+
217
+ # Cookie bridge: a browser that cleared a JS challenge yields exactly the
218
+ # cookies + UA a plain HTTP client needs. Seed the curl_cffi pool so
219
+ # subsequent same-host pages are collected cheaply (FlareSolverr pattern).
220
+ if vr.verdict in (Verdict.STRONG_OK, Verdict.WEAK_OK) and cookies:
221
+ _bridge_cookies_to_pool(url, cookies, user_agent)
222
+
223
+ return att, html
224
+
225
+
226
+ def _parse_envelope(stdout: str, url: str):
227
+ """Return (html, final_url, status, cookies, user_agent) from a JSON
228
+ envelope, or treat stdout as raw HTML if it isn't JSON."""
229
+ import json
230
+ s = stdout.lstrip()
231
+ if s[:1] == "{":
232
+ try:
233
+ env = json.loads(s)
234
+ html = env.get("html", "") or ""
235
+ final_url = env.get("finalUrl", "") or url
236
+ status = int(env.get("status") or 0) or 200
237
+ cookies = env.get("cookies") or []
238
+ user_agent = env.get("userAgent") or None
239
+ automation = env.get("automation") or None
240
+ return html, final_url, status, cookies, user_agent, automation
241
+ except Exception:
242
+ pass
243
+ return stdout, url, 200, [], None, None
244
+
245
+
246
+ def _bridge_cookies_to_pool(url: str, cookies: list, user_agent: Optional[str]) -> None:
247
+ try:
248
+ from .transport import POOL, pool_enabled, _host_of
249
+ if not pool_enabled():
250
+ return
251
+ # Browser is real Chrome → seed the "chrome" curl identity for this host.
252
+ POOL.inject_cookies(_host_of(url), "chrome", cookies, user_agent=user_agent)
253
+ except Exception:
254
+ pass