@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
|
@@ -20,5 +20,22 @@ export declare function looksHostedModelId(modelId: string | undefined): boolean
|
|
|
20
20
|
export declare function isLocalBaseUrl(baseUrl: string | undefined): boolean;
|
|
21
21
|
export declare function inferNativeProviderFromModel(ctx: ActiveSearchModelContext | undefined): SearchProviderId | undefined;
|
|
22
22
|
export declare function canUseGenericCredentials(authStorage: AuthStorage, ctx: ActiveSearchModelContext | undefined, sessionId?: string, signal?: AbortSignal): Promise<boolean>;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Native web-search provider to attempt by reusing the ACTIVE model's own
|
|
25
|
+
* credentials + baseUrl, dispatched by the model's wire protocol.
|
|
26
|
+
*
|
|
27
|
+
* This is the "native search over a proxy" path: when a model is served through
|
|
28
|
+
* a proxy/custom endpoint, its canonical search credentials (e.g. a dedicated
|
|
29
|
+
* `anthropic` key, or ChatGPT OAuth for `codex`) are usually absent, but the
|
|
30
|
+
* credential that authenticates the model itself — stored under the active
|
|
31
|
+
* provider id and aimed at `ctx.baseUrl` — can drive native web search just as
|
|
32
|
+
* well. Each provider's `search()` falls back to those active credentials when
|
|
33
|
+
* its canonical ones are missing.
|
|
34
|
+
*
|
|
35
|
+
* Returned ids are matched purely from the wire `api` (+ model-id family where a
|
|
36
|
+
* native tool only makes sense for that family); the providers themselves fail
|
|
37
|
+
* closed (and the chain falls through to DuckDuckGo) if the endpoint does not
|
|
38
|
+
* actually support web search.
|
|
39
|
+
*/
|
|
40
|
+
export declare function activeContextNativeId(ctx: ActiveSearchModelContext | undefined): SearchProviderId | undefined;
|
|
24
41
|
export declare function resolveProviderChain(options: ResolveProviderChainOptions): Promise<SearchProvider[]>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insane Search Provider
|
|
3
|
+
*
|
|
4
|
+
* Native TypeScript, fail-closed adaptation of the MIT-licensed upstream
|
|
5
|
+
* fivetaku/insane-search public-route strategy. This ports only safe Phase 0
|
|
6
|
+
* concepts: deterministic no-auth public endpoints plus route-attempt tracing.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately excluded from upstream: TLS impersonation, browser/cookie warming,
|
|
9
|
+
* CAPTCHA/paywall/login bypasses, credential storage, Playwright automation, and
|
|
10
|
+
* auto dependency installation. Unsupported or terminal auth/paywall/block states
|
|
11
|
+
* throw instead of pretending a shallow fetch succeeded.
|
|
12
|
+
*/
|
|
13
|
+
import type { AuthStorage } from "@gajae-code/ai";
|
|
14
|
+
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
15
|
+
import type { SearchParams } from "./base";
|
|
16
|
+
import { SearchProvider } from "./base";
|
|
17
|
+
export interface InsaneRouteAttempt {
|
|
18
|
+
platform: InsanePlatform;
|
|
19
|
+
route: string;
|
|
20
|
+
ok: boolean;
|
|
21
|
+
status: number;
|
|
22
|
+
bytes: number;
|
|
23
|
+
note?: string;
|
|
24
|
+
}
|
|
25
|
+
type InsanePlatform = "reddit" | "x" | "youtube" | "hackernews";
|
|
26
|
+
interface RouteSuccess {
|
|
27
|
+
platform: InsanePlatform;
|
|
28
|
+
route: string;
|
|
29
|
+
finalUrl: string;
|
|
30
|
+
sources: SearchSource[];
|
|
31
|
+
attempts: InsaneRouteAttempt[];
|
|
32
|
+
}
|
|
33
|
+
interface RouteFailure {
|
|
34
|
+
platform: InsanePlatform;
|
|
35
|
+
attempts: InsaneRouteAttempt[];
|
|
36
|
+
}
|
|
37
|
+
type RouteResult = RouteSuccess | RouteFailure | null;
|
|
38
|
+
export declare function routeInsanePublicUrl(rawUrl: string, signal?: AbortSignal): Promise<RouteResult>;
|
|
39
|
+
/** Execute safe Insane Search public-route discovery. */
|
|
40
|
+
export declare function searchInsane(params: {
|
|
41
|
+
query: string;
|
|
42
|
+
num_results?: number;
|
|
43
|
+
recency?: "day" | "week" | "month" | "year";
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}): Promise<SearchResponse>;
|
|
46
|
+
/** Keyless provider that ports safe upstream public-route fallbacks only. */
|
|
47
|
+
export declare class InsaneProvider extends SearchProvider {
|
|
48
|
+
readonly id = "insane";
|
|
49
|
+
readonly label = "Insane";
|
|
50
|
+
isAvailable(_authStorage: AuthStorage): boolean;
|
|
51
|
+
search(params: SearchParams): Promise<SearchResponse>;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline citation extraction shared by native web-search providers.
|
|
3
|
+
*
|
|
4
|
+
* Web-search-capable models sometimes return a genuinely grounded answer whose
|
|
5
|
+
* sources are written inline (markdown links or bare URLs) instead of as
|
|
6
|
+
* structured citation annotations. When a provider has independent proof that a
|
|
7
|
+
* web search actually ran, these helpers recover sources from the answer text so
|
|
8
|
+
* the result is not discarded.
|
|
9
|
+
*/
|
|
10
|
+
import type { SearchSource } from "../types";
|
|
11
|
+
/** Append a source, de-duplicating by URL. */
|
|
12
|
+
export declare function addSource(sources: SearchSource[], source: SearchSource): void;
|
|
13
|
+
/**
|
|
14
|
+
* Strips prose punctuation and unmatched closing delimiters from extracted URLs.
|
|
15
|
+
* Models often return links embedded in markdown or sentence text.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeExtractedUrl(candidate: string): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Extracts citation sources from markdown links and bare URLs in answer text.
|
|
20
|
+
* Used only as a fallback when a provider confirms a search ran but omits
|
|
21
|
+
* structured citation annotations.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractTextSources(text: string): SearchSource[];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Unified types for web search responses across supported providers.
|
|
5
5
|
*/
|
|
6
6
|
/** Supported web search providers */
|
|
7
|
-
export type SearchProviderId = "duckduckgo" | "exa" | "brave" | "jina" | "kimi" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex" | "xai" | "tavily" | "parallel" | "kagi" | "synthetic" | "searxng" | "openai-compatible";
|
|
7
|
+
export type SearchProviderId = "duckduckgo" | "insane" | "exa" | "brave" | "jina" | "kimi" | "zai" | "anthropic" | "perplexity" | "gemini" | "codex" | "xai" | "tavily" | "parallel" | "kagi" | "synthetic" | "searxng" | "openai-compatible";
|
|
8
8
|
export type WebSearchMode = "on" | "off" | "auto";
|
|
9
9
|
export interface ActiveSearchModelContext {
|
|
10
10
|
provider: string;
|
|
@@ -15,7 +15,7 @@ export interface ActiveSearchModelContext {
|
|
|
15
15
|
headers?: Record<string, string>;
|
|
16
16
|
webSearch?: WebSearchMode;
|
|
17
17
|
}
|
|
18
|
-
export declare const CONFIGURABLE_SEARCH_PROVIDER_IDS: readonly ["duckduckgo", "exa", "brave", "jina", "kimi", "zai", "anthropic", "perplexity", "gemini", "codex", "xai", "tavily", "parallel", "kagi", "synthetic", "searxng"];
|
|
18
|
+
export declare const CONFIGURABLE_SEARCH_PROVIDER_IDS: readonly ["duckduckgo", "insane", "exa", "brave", "jina", "kimi", "zai", "anthropic", "perplexity", "gemini", "codex", "xai", "tavily", "parallel", "kagi", "synthetic", "searxng"];
|
|
19
19
|
export declare function isSearchProviderId(value: string): value is SearchProviderId;
|
|
20
20
|
export declare function isConfigurableSearchProviderId(value: string): value is (typeof CONFIGURABLE_SEARCH_PROVIDER_IDS)[number];
|
|
21
21
|
export declare function isSearchProviderPreference(value: string): value is (typeof CONFIGURABLE_SEARCH_PROVIDER_IDS)[number] | "auto";
|
|
@@ -96,6 +96,14 @@ export interface AnthropicCitation {
|
|
|
96
96
|
cited_text: string;
|
|
97
97
|
encrypted_index: string;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Error payload returned in `web_search_tool_result.content` when a server-side
|
|
101
|
+
* web search fails. Unlike the success case, this is an object, not an array.
|
|
102
|
+
*/
|
|
103
|
+
export interface AnthropicWebSearchToolResultError {
|
|
104
|
+
type: "web_search_tool_result_error";
|
|
105
|
+
error_code?: string;
|
|
106
|
+
}
|
|
99
107
|
export interface AnthropicContentBlock {
|
|
100
108
|
type: string;
|
|
101
109
|
/** Text content (for type="text") */
|
|
@@ -108,8 +116,8 @@ export interface AnthropicContentBlock {
|
|
|
108
116
|
input?: {
|
|
109
117
|
query: string;
|
|
110
118
|
};
|
|
111
|
-
/** Search results (
|
|
112
|
-
content?: AnthropicSearchResult[];
|
|
119
|
+
/** Search results array on success, or an error object on failure (type="web_search_tool_result") */
|
|
120
|
+
content?: AnthropicSearchResult[] | AnthropicWebSearchToolResultError;
|
|
113
121
|
}
|
|
114
122
|
export interface AnthropicApiResponse {
|
|
115
123
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -45,18 +45,19 @@
|
|
|
45
45
|
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
|
46
46
|
"prepack": "bun scripts/generate-docs-index.ts",
|
|
47
47
|
"generate-template": "bun scripts/generate-template.ts",
|
|
48
|
-
"install:defaults": "bun src/cli.ts setup defaults"
|
|
48
|
+
"install:defaults": "bun src/cli.ts setup defaults",
|
|
49
|
+
"verify:insane-vendor": "bun scripts/verify-insane-vendor.ts"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
52
53
|
"@babel/parser": "^7.29.3",
|
|
53
54
|
"@mozilla/readability": "^0.6.0",
|
|
54
|
-
"@gajae-code/stats": "0.7.
|
|
55
|
-
"@gajae-code/agent-core": "0.7.
|
|
56
|
-
"@gajae-code/ai": "0.7.
|
|
57
|
-
"@gajae-code/natives": "0.7.
|
|
58
|
-
"@gajae-code/tui": "0.7.
|
|
59
|
-
"@gajae-code/utils": "0.7.
|
|
55
|
+
"@gajae-code/stats": "0.7.2",
|
|
56
|
+
"@gajae-code/agent-core": "0.7.2",
|
|
57
|
+
"@gajae-code/ai": "0.7.2",
|
|
58
|
+
"@gajae-code/natives": "0.7.2",
|
|
59
|
+
"@gajae-code/tui": "0.7.2",
|
|
60
|
+
"@gajae-code/utils": "0.7.2",
|
|
60
61
|
"@puppeteer/browsers": "^2.13.0",
|
|
61
62
|
"@types/turndown": "5.0.6",
|
|
62
63
|
"@xterm/headless": "^6.0.0",
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
"examples",
|
|
85
86
|
"README.md",
|
|
86
87
|
"CHANGELOG.md",
|
|
88
|
+
"vendor",
|
|
87
89
|
"dist/types"
|
|
88
90
|
],
|
|
89
91
|
"exports": {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Vendor verification for packages/coding-agent/vendor/insane-search.
|
|
4
|
+
*
|
|
5
|
+
* Asserts:
|
|
6
|
+
* 1. Forbidden upstream paths/patterns are absent (install hooks, star-baiting,
|
|
7
|
+
* update-notifier, transcript-language scanner, .claude-plugin).
|
|
8
|
+
* 2. MANIFEST.json exists and pins a full 40-char upstream commit SHA.
|
|
9
|
+
* 3. The vendored runtime files are present.
|
|
10
|
+
* 4. `npm pack --dry-run --json` includes the vendor tree (engine entrypoint,
|
|
11
|
+
* templates, LICENSE, manifest) in the published package.
|
|
12
|
+
*
|
|
13
|
+
* Exit code 0 on success, 1 on any failure.
|
|
14
|
+
*/
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
17
|
+
import { dirname, join, relative } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
21
|
+
const vendorDir = join(pkgDir, "vendor", "insane-search");
|
|
22
|
+
const failures: string[] = [];
|
|
23
|
+
|
|
24
|
+
function fail(msg: string): void {
|
|
25
|
+
failures.push(msg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function walk(dir: string): string[] {
|
|
29
|
+
const out: string[] = [];
|
|
30
|
+
for (const entry of readdirSync(dir)) {
|
|
31
|
+
const full = join(dir, entry);
|
|
32
|
+
if (statSync(full).isDirectory()) out.push(...walk(full));
|
|
33
|
+
else out.push(full);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 1. Forbidden paths / patterns
|
|
39
|
+
if (!existsSync(vendorDir)) {
|
|
40
|
+
fail(`vendor tree missing at ${vendorDir}`);
|
|
41
|
+
} else {
|
|
42
|
+
const files = walk(vendorDir).map(f => relative(vendorDir, f));
|
|
43
|
+
const forbiddenNames = ["setup.sh", "gptaku-update-check.cjs"];
|
|
44
|
+
const forbiddenSubpaths = [".claude-plugin/", "/references/", "/tests/coverage_battery"];
|
|
45
|
+
for (const f of files) {
|
|
46
|
+
const base = f.split("/").pop() ?? f;
|
|
47
|
+
if (forbiddenNames.includes(base)) fail(`forbidden file present: ${f}`);
|
|
48
|
+
for (const sub of forbiddenSubpaths) {
|
|
49
|
+
if (`/${f}`.includes(sub)) fail(`forbidden path present: ${f}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Scan for star-baiting / settings.json mutation / transcript scanning patterns.
|
|
53
|
+
const forbiddenPatterns: Array<[RegExp, string]> = [
|
|
54
|
+
[/user\/starred/, "github star-baiting (user/starred)"],
|
|
55
|
+
[/gh\s+api\s+-X\s+PUT/, "gh api star write"],
|
|
56
|
+
[/SessionStart/, "settings.json SessionStart hook injection"],
|
|
57
|
+
[/\.claude\/projects/, "past-session transcript scanner"],
|
|
58
|
+
];
|
|
59
|
+
for (const f of files) {
|
|
60
|
+
if (f === "MANIFEST.json") continue; // manifest documents the excluded patterns by name
|
|
61
|
+
let body = "";
|
|
62
|
+
try {
|
|
63
|
+
body = readFileSync(join(vendorDir, f), "utf8");
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const [re, label] of forbiddenPatterns) {
|
|
68
|
+
if (re.test(body)) fail(`forbidden pattern (${label}) found in ${f}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Manifest with pinned SHA
|
|
74
|
+
const manifestPath = join(vendorDir, "MANIFEST.json");
|
|
75
|
+
let commit = "";
|
|
76
|
+
if (!existsSync(manifestPath)) {
|
|
77
|
+
fail("MANIFEST.json missing");
|
|
78
|
+
} else {
|
|
79
|
+
try {
|
|
80
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as {
|
|
81
|
+
upstream?: { commit?: string };
|
|
82
|
+
};
|
|
83
|
+
commit = manifest.upstream?.commit ?? "";
|
|
84
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) fail(`MANIFEST upstream.commit is not a full 40-char SHA: "${commit}"`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
fail(`MANIFEST.json is not valid JSON: ${(err as Error).message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3. Required runtime files present
|
|
91
|
+
const requiredFiles = [
|
|
92
|
+
"engine/__main__.py",
|
|
93
|
+
"engine/__init__.py",
|
|
94
|
+
"engine/fetch_chain.py",
|
|
95
|
+
"engine/templates/package.json",
|
|
96
|
+
"engine/templates/playwright_real_chrome.js",
|
|
97
|
+
"engine/waf_profiles.yaml",
|
|
98
|
+
"LICENSE",
|
|
99
|
+
"MANIFEST.json",
|
|
100
|
+
];
|
|
101
|
+
for (const rel of requiredFiles) {
|
|
102
|
+
if (!existsSync(join(vendorDir, rel))) fail(`required vendored file missing: ${rel}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. Package pack inclusion
|
|
106
|
+
try {
|
|
107
|
+
const raw = execFileSync("npm", ["pack", "--dry-run", "--json"], {
|
|
108
|
+
cwd: pkgDir,
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
111
|
+
});
|
|
112
|
+
const parsed = JSON.parse(raw) as Array<{ files?: Array<{ path: string }> }>;
|
|
113
|
+
const packed = new Set((parsed[0]?.files ?? []).map(f => f.path.replace(/\\/g, "/")));
|
|
114
|
+
const mustPack = [
|
|
115
|
+
"vendor/insane-search/engine/__main__.py",
|
|
116
|
+
"vendor/insane-search/engine/templates/playwright_real_chrome.js",
|
|
117
|
+
"vendor/insane-search/LICENSE",
|
|
118
|
+
"vendor/insane-search/MANIFEST.json",
|
|
119
|
+
];
|
|
120
|
+
for (const rel of mustPack) {
|
|
121
|
+
if (!packed.has(rel)) fail(`package pack does not include ${rel}`);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
fail(`npm pack --dry-run failed: ${(err as Error).message}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (failures.length > 0) {
|
|
128
|
+
console.error("insane-vendor verification FAILED:");
|
|
129
|
+
for (const f of failures) console.error(` - ${f}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log(`insane-vendor verification passed (pinned ${commit}).`);
|
package/src/cli/args.ts
CHANGED
|
@@ -265,7 +265,7 @@ export function getExtraHelpText(): string {
|
|
|
265
265
|
GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
|
|
266
266
|
GJC_PLAN_MODEL - Override planning model (see --plan)
|
|
267
267
|
GJC_NO_PTY - Disable PTY-based interactive bash execution
|
|
268
|
-
--tmux - Launch interactive startup inside a
|
|
268
|
+
--tmux - Launch interactive startup inside a fresh tmux session
|
|
269
269
|
gjc session - List, inspect, create, remove, or attach tagged GJC-managed tmux sessions
|
|
270
270
|
GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
|
|
271
271
|
GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -50,7 +50,7 @@ export function getExtraHelpText(): string {
|
|
|
50
50
|
GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
|
|
51
51
|
GJC_PLAN_MODEL - Override planning model (see --plan)
|
|
52
52
|
GJC_NO_PTY - Disable PTY-based interactive bash execution
|
|
53
|
-
--tmux - Launch interactive startup inside a
|
|
53
|
+
--tmux - Launch interactive startup inside a fresh tmux session
|
|
54
54
|
gjc session - List, inspect, create, remove, or attach tagged GJC-managed tmux sessions
|
|
55
55
|
GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
|
|
56
56
|
GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface NotifyCommandDeps {
|
|
|
29
29
|
pollIntervalMs?: number;
|
|
30
30
|
setupChatId?: string;
|
|
31
31
|
setupRedact?: boolean;
|
|
32
|
+
setupInteractive?: boolean;
|
|
33
|
+
threadedModePrompt?: (message: string) => Promise<string>;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface TelegramApiResponse<T> {
|
|
@@ -47,6 +49,18 @@ interface TelegramUpdate {
|
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
interface TelegramUser {
|
|
53
|
+
id: number;
|
|
54
|
+
is_bot?: boolean;
|
|
55
|
+
first_name?: string;
|
|
56
|
+
username?: string;
|
|
57
|
+
has_topics_enabled?: boolean;
|
|
58
|
+
allows_users_to_create_topics?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type ThreadedModeState = "enabled" | "disabled" | "unknown";
|
|
62
|
+
type ThreadedModeFinalLabel = "verified" | "unverified" | "unknown";
|
|
63
|
+
|
|
50
64
|
const DEFAULT_API_BASE = "https://api.telegram.org";
|
|
51
65
|
const DEFAULT_POLL_TIMEOUT_MS = 60_000;
|
|
52
66
|
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
@@ -120,7 +134,11 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
120
134
|
throw new Error("Telegram bot token is required.");
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
await
|
|
137
|
+
const user = await getMe(fetchImpl, apiBase, token);
|
|
138
|
+
const threadedState = await verifyThreadedMode(fetchImpl, apiBase, token, user, {
|
|
139
|
+
interactive: resolveSetupInteractive(deps),
|
|
140
|
+
prompt: deps.threadedModePrompt ?? promptForThreadedMode,
|
|
141
|
+
});
|
|
124
142
|
process.stdout.write(
|
|
125
143
|
"Token validated. Message your bot now from the private Telegram chat to pair notifications.\n",
|
|
126
144
|
);
|
|
@@ -145,7 +163,9 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
145
163
|
if (deps.setupRedact) settings.set("notifications.redact", true);
|
|
146
164
|
await settings.flush();
|
|
147
165
|
|
|
148
|
-
process.stdout.write(
|
|
166
|
+
process.stdout.write(
|
|
167
|
+
`Notifications enabled. botToken=${maskToken(token)} chatId=${chatId} threaded=${threadedLabel(threadedState)}\n`,
|
|
168
|
+
);
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
async function promptForToken(): Promise<string> {
|
|
@@ -160,14 +180,135 @@ async function promptForToken(): Promise<string> {
|
|
|
160
180
|
}
|
|
161
181
|
}
|
|
162
182
|
|
|
183
|
+
const THREADED_ENABLED_SUCCESS =
|
|
184
|
+
"Telegram Threaded Mode capability verified for this bot. GJC will request a private-chat topic per session; if Telegram ever refuses topic creation, notifications fall back to this flat chat with a one-time nudge.\n";
|
|
185
|
+
|
|
186
|
+
const THREADED_MISSING_WARNING =
|
|
187
|
+
"Warning: Telegram getMe did not include has_topics_enabled, so GJC cannot verify private-chat Threaded Mode capability for this bot. Setup will continue; update Telegram/Bot API support or re-run setup if per-session topics fail.\n";
|
|
188
|
+
|
|
189
|
+
const THREADED_NONINTERACTIVE_WARNING =
|
|
190
|
+
"Warning: Telegram Threaded Mode capability is OFF for this bot. Setup will be saved because this run is non-interactive, but per-session Telegram delivery may fail closed until the bot owner enables Threaded Mode in @BotFather. GJC cannot enable it through the Bot API.\n";
|
|
191
|
+
|
|
192
|
+
const THREADED_DISABLED_GUIDANCE =
|
|
193
|
+
"Telegram Threaded Mode is OFF for this bot. GJC needs Telegram private-chat topics so each session can use its own thread.\n" +
|
|
194
|
+
"GJC cannot enable this through the Bot API. Open @BotFather, select this bot, enable Threaded Mode / forum topics for private chats, then return here.\n" +
|
|
195
|
+
"Telegram may require an additional Stars purchase fee for private-chat topics.\n";
|
|
196
|
+
|
|
197
|
+
const THREADED_DISABLED_PROMPT =
|
|
198
|
+
"Press Enter after enabling Threaded Mode, or type skip to finish setup with a warning: ";
|
|
199
|
+
|
|
200
|
+
const THREADED_STILL_OFF = "Telegram still reports Threaded Mode OFF for this bot.\n";
|
|
201
|
+
|
|
202
|
+
const THREADED_RETRY_PROMPT = "Press Enter to check again, or type skip to finish setup with a warning: ";
|
|
203
|
+
|
|
204
|
+
const THREADED_SKIP_WARNING =
|
|
205
|
+
"Warning: continuing without verified Telegram Threaded Mode capability. Setup will be saved, but per-session Telegram delivery may fail closed until Threaded Mode is enabled in BotFather.\n";
|
|
206
|
+
|
|
207
|
+
const THREADED_INVALID_INPUT = "Type Enter to retry or skip to continue with a warning.\n";
|
|
208
|
+
|
|
209
|
+
const THREADED_RETRY_INPUTS = new Set(["", "y", "yes", "r", "retry"]);
|
|
210
|
+
const THREADED_SKIP_INPUTS = new Set(["s", "skip", "n", "no"]);
|
|
211
|
+
|
|
212
|
+
function isTelegramUser(value: unknown): value is TelegramUser {
|
|
213
|
+
return Boolean(value) && typeof value === "object" && typeof (value as { id?: unknown }).id === "number";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function getMe(fetchImpl: typeof fetch, apiBase: string, token: string): Promise<TelegramUser> {
|
|
217
|
+
const user = await callTelegram<unknown>(fetchImpl, apiBase, token, "getMe", {});
|
|
218
|
+
if (!isTelegramUser(user)) {
|
|
219
|
+
throw new Error("Telegram getMe returned invalid Telegram response: missing valid User result.");
|
|
220
|
+
}
|
|
221
|
+
return user;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function threadedModeState(user: TelegramUser): ThreadedModeState {
|
|
225
|
+
if (user.has_topics_enabled === true) return "enabled";
|
|
226
|
+
if (user.has_topics_enabled === false) return "disabled";
|
|
227
|
+
return "unknown";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function threadedLabel(state: ThreadedModeState): ThreadedModeFinalLabel {
|
|
231
|
+
if (state === "enabled") return "verified";
|
|
232
|
+
if (state === "disabled") return "unverified";
|
|
233
|
+
return "unknown";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function resolveSetupInteractive(deps: NotifyCommandDeps): boolean {
|
|
237
|
+
if (deps.setupInteractive !== undefined) return deps.setupInteractive;
|
|
238
|
+
return Boolean(process.stdin.isTTY) && !deps.setupChatId?.trim();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function promptForThreadedMode(message: string): Promise<string> {
|
|
242
|
+
if (!process.stdin.isTTY) return "skip";
|
|
243
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
244
|
+
try {
|
|
245
|
+
return (await rl.question(message)).trim();
|
|
246
|
+
} finally {
|
|
247
|
+
rl.close();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function verifyThreadedMode(
|
|
252
|
+
fetchImpl: typeof fetch,
|
|
253
|
+
apiBase: string,
|
|
254
|
+
token: string,
|
|
255
|
+
initialUser: TelegramUser,
|
|
256
|
+
opts: { interactive: boolean; prompt: (message: string) => Promise<string> },
|
|
257
|
+
): Promise<ThreadedModeState> {
|
|
258
|
+
const classify = (user: TelegramUser): ThreadedModeState | undefined => {
|
|
259
|
+
const state = threadedModeState(user);
|
|
260
|
+
if (state === "enabled") {
|
|
261
|
+
process.stdout.write(THREADED_ENABLED_SUCCESS);
|
|
262
|
+
return "enabled";
|
|
263
|
+
}
|
|
264
|
+
if (state === "unknown") {
|
|
265
|
+
process.stdout.write(THREADED_MISSING_WARNING);
|
|
266
|
+
return "unknown";
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const initial = classify(initialUser);
|
|
272
|
+
if (initial) return initial;
|
|
273
|
+
|
|
274
|
+
if (!opts.interactive) {
|
|
275
|
+
process.stdout.write(THREADED_NONINTERACTIVE_WARNING);
|
|
276
|
+
return "disabled";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
process.stdout.write(THREADED_DISABLED_GUIDANCE);
|
|
280
|
+
let firstPrompt = true;
|
|
281
|
+
for (;;) {
|
|
282
|
+
const answer = (await opts.prompt(firstPrompt ? THREADED_DISABLED_PROMPT : THREADED_RETRY_PROMPT))
|
|
283
|
+
.trim()
|
|
284
|
+
.toLowerCase();
|
|
285
|
+
firstPrompt = false;
|
|
286
|
+
if (THREADED_SKIP_INPUTS.has(answer)) {
|
|
287
|
+
process.stdout.write(THREADED_SKIP_WARNING);
|
|
288
|
+
return "disabled";
|
|
289
|
+
}
|
|
290
|
+
if (!THREADED_RETRY_INPUTS.has(answer)) {
|
|
291
|
+
process.stdout.write(THREADED_INVALID_INPUT);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const resolved = classify(await getMe(fetchImpl, apiBase, token));
|
|
295
|
+
if (resolved) return resolved;
|
|
296
|
+
process.stdout.write(THREADED_STILL_OFF);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
163
300
|
async function runStatus(deps: NotifyCommandDeps): Promise<void> {
|
|
164
301
|
const settings = await getSettings(deps);
|
|
165
302
|
const cfg = getNotificationConfig(settings);
|
|
166
303
|
process.stdout.write(
|
|
167
304
|
`${chalk.bold("Notifications")}\n` +
|
|
168
305
|
` enabled: ${cfg.enabled}\n` +
|
|
169
|
-
` botToken: ${maskToken(cfg.botToken)}\n` +
|
|
170
|
-
` chatId: ${cfg.chatId ?? "(unset)"}\n` +
|
|
306
|
+
` telegram.botToken: ${maskToken(cfg.botToken)}\n` +
|
|
307
|
+
` telegram.chatId: ${cfg.chatId ?? "(unset)"}\n` +
|
|
308
|
+
` discord.botToken: ${maskToken(cfg.discord.botToken)}\n` +
|
|
309
|
+
` discord.channelId: ${cfg.discord.channelId ?? "(unset)"}\n` +
|
|
310
|
+
` slack.botToken: ${maskToken(cfg.slack.botToken)}\n` +
|
|
311
|
+
` slack.channelId: ${cfg.slack.channelId ?? "(unset)"}\n` +
|
|
171
312
|
` redact: ${cfg.redact}\n`,
|
|
172
313
|
);
|
|
173
314
|
}
|
|
@@ -263,12 +404,18 @@ ${chalk.bold("Usage:")}
|
|
|
263
404
|
${APP_NAME} notify status
|
|
264
405
|
|
|
265
406
|
${chalk.bold("Subcommands:")}
|
|
266
|
-
setup Pair a Telegram bot token with a private chat
|
|
407
|
+
setup Pair a Telegram bot token with a private chat and verify Threaded Mode capability
|
|
267
408
|
status Show notification configuration without secrets
|
|
268
409
|
|
|
269
410
|
${chalk.bold("Examples:")}
|
|
270
411
|
${APP_NAME} notify setup
|
|
271
412
|
${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
|
|
272
413
|
${APP_NAME} notify status
|
|
414
|
+
|
|
415
|
+
${chalk.bold("Threaded Mode:")}
|
|
416
|
+
GJC uses Telegram private-chat topics for per-session threads. Setup verifies the bot
|
|
417
|
+
capability via getMe.has_topics_enabled. If it is off, enable Threaded Mode in @BotFather;
|
|
418
|
+
bots cannot toggle it through the Bot API. If Telegram refuses topic creation at runtime,
|
|
419
|
+
GJC delivers flat to the paired private chat and nudges you to enable Threaded Mode.
|
|
273
420
|
`);
|
|
274
421
|
}
|
package/src/commands/team.ts
CHANGED
|
@@ -95,7 +95,7 @@ export default class Team extends Command {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
static examples = [
|
|
98
|
-
"gjc --tmux # start
|
|
98
|
+
"gjc --tmux # start the required tmux-backed leader session first",
|
|
99
99
|
'gjc team 3:executor "Implement the approved plan"',
|
|
100
100
|
"gjc team status <team-name> --json",
|
|
101
101
|
"gjc team monitor <team-name> --json",
|
|
@@ -254,10 +254,14 @@ export const SETTINGS_SCHEMA = {
|
|
|
254
254
|
"auth.broker.url": { type: "string", default: undefined },
|
|
255
255
|
"auth.broker.token": { type: "string", default: undefined },
|
|
256
256
|
|
|
257
|
-
// Notifications (Telegram
|
|
257
|
+
// Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
|
|
258
258
|
"notifications.enabled": { type: "boolean", default: false },
|
|
259
259
|
"notifications.telegram.botToken": { type: "string", default: undefined },
|
|
260
260
|
"notifications.telegram.chatId": { type: "string", default: undefined },
|
|
261
|
+
"notifications.discord.botToken": { type: "string", default: undefined },
|
|
262
|
+
"notifications.discord.channelId": { type: "string", default: undefined },
|
|
263
|
+
"notifications.slack.botToken": { type: "string", default: undefined },
|
|
264
|
+
"notifications.slack.channelId": { type: "string", default: undefined },
|
|
261
265
|
"notifications.redact": { type: "boolean", default: false },
|
|
262
266
|
"notifications.verbosity": {
|
|
263
267
|
type: "string",
|
|
@@ -2104,6 +2108,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
2104
2108
|
ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
|
|
2105
2109
|
},
|
|
2106
2110
|
|
|
2111
|
+
"web.insaneFallback": {
|
|
2112
|
+
type: "boolean",
|
|
2113
|
+
default: false,
|
|
2114
|
+
ui: {
|
|
2115
|
+
tab: "tools",
|
|
2116
|
+
label: "Insane Search Fallback",
|
|
2117
|
+
description:
|
|
2118
|
+
"Opt in to the vendored insane-search escalation for blocked public URL reads (403/WAF/JS-gated). Off by default. Requires preinstalled python3 + curl_cffi (and node + playwright/stealth for the browser phase); changes network posture by enabling TLS/browser impersonation for public pages.",
|
|
2119
|
+
},
|
|
2120
|
+
},
|
|
2121
|
+
|
|
2107
2122
|
"github.enabled": {
|
|
2108
2123
|
type: "boolean",
|
|
2109
2124
|
default: false,
|
|
@@ -2731,6 +2746,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
2731
2746
|
values: [
|
|
2732
2747
|
"auto",
|
|
2733
2748
|
"duckduckgo",
|
|
2749
|
+
"insane",
|
|
2734
2750
|
"exa",
|
|
2735
2751
|
"brave",
|
|
2736
2752
|
"jina",
|
|
@@ -2763,6 +2779,11 @@ export const SETTINGS_SCHEMA = {
|
|
|
2763
2779
|
label: "DuckDuckGo",
|
|
2764
2780
|
description: "Keyless default — no API key or OAuth required",
|
|
2765
2781
|
},
|
|
2782
|
+
{
|
|
2783
|
+
value: "insane",
|
|
2784
|
+
label: "Insane",
|
|
2785
|
+
description: "Keyless safe public-route fallback inspired by upstream insane-search",
|
|
2786
|
+
},
|
|
2766
2787
|
{ value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set" },
|
|
2767
2788
|
{ value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
|
|
2768
2789
|
{ value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
|
|
@@ -3177,6 +3198,14 @@ export interface NotificationsSettings {
|
|
|
3177
3198
|
botToken: string | undefined;
|
|
3178
3199
|
chatId: string | undefined;
|
|
3179
3200
|
};
|
|
3201
|
+
discord: {
|
|
3202
|
+
botToken: string | undefined;
|
|
3203
|
+
channelId: string | undefined;
|
|
3204
|
+
};
|
|
3205
|
+
slack: {
|
|
3206
|
+
botToken: string | undefined;
|
|
3207
|
+
channelId: string | undefined;
|
|
3208
|
+
};
|
|
3180
3209
|
redact: boolean;
|
|
3181
3210
|
daemon: {
|
|
3182
3211
|
idleTimeoutMs: number;
|