@gajae-code/coding-agent 0.7.1 → 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.
- package/CHANGELOG.md +19 -0
- package/dist/types/cli/notify-cli.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +39 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +9 -1
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/telegram-daemon.d.ts +19 -0
- package/dist/types/notifications/threaded-inbound.d.ts +19 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +22 -0
- package/dist/types/web/search/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +10 -8
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +152 -5
- package/src/commands/team.ts +1 -1
- package/src/config/settings-schema.ts +30 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +11 -4
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/launch-tmux.ts +17 -3
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-runtime.ts +2 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +7 -2
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/modes/controllers/event-controller.ts +15 -0
- package/src/modes/interactive-mode.ts +46 -2
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config.ts +23 -2
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/index.ts +180 -38
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/telegram-daemon.ts +235 -14
- package/src/notifications/threaded-inbound.ts +60 -4
- package/src/notifications/threaded-render.ts +20 -2
- package/src/session/agent-session.ts +82 -51
- package/src/tools/fetch.ts +78 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/telegram-send.ts +137 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +155 -0
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- 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
|