@gajae-code/coding-agent 0.7.1 → 0.7.3
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 +57 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +39 -2
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- 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/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -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/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/telegram-daemon.d.ts +73 -16
- package/dist/types/notifications/threaded-inbound.d.ts +19 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -1
- package/dist/types/notifications/topic-registry.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -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 +25 -0
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- 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/mcp-cli.ts +272 -0
- package/src/cli/notify-cli.ts +152 -5
- package/src/cli.ts +6 -2
- package/src/commands/mcp.ts +117 -0
- package/src/commands/team.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/settings-schema.ts +30 -1
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +4 -3
- package/src/defaults/gjc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- 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/tmux-common.ts +3 -1
- package/src/gjc-runtime/ultragoal-guard.ts +25 -8
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +7 -2
- package/src/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +14 -11
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/model-selector.ts +12 -0
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/event-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +3 -0
- package/src/modes/interactive-mode.ts +48 -3
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- 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/operator-runtime.ts +171 -0
- package/src/notifications/telegram-daemon.ts +553 -236
- package/src/notifications/threaded-inbound.ts +60 -4
- package/src/notifications/threaded-render.ts +20 -2
- package/src/notifications/topic-registry.ts +5 -0
- package/src/session/agent-session.ts +82 -51
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +94 -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 +159 -0
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- 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
|
@@ -21,11 +21,30 @@ export interface InboundUpdate {
|
|
|
21
21
|
message?: {
|
|
22
22
|
message_id?: unknown;
|
|
23
23
|
text?: unknown;
|
|
24
|
+
caption?: unknown;
|
|
25
|
+
photo?: unknown;
|
|
26
|
+
document?: unknown;
|
|
27
|
+
video?: unknown;
|
|
28
|
+
audio?: unknown;
|
|
29
|
+
voice?: unknown;
|
|
30
|
+
animation?: unknown;
|
|
24
31
|
chat?: { id?: unknown };
|
|
25
32
|
message_thread_id?: unknown;
|
|
26
33
|
};
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
/** A downloadable media attachment referenced by an inbound message. */
|
|
37
|
+
export interface InboundAttachment {
|
|
38
|
+
/** Telegram file_id to resolve via getFile. */
|
|
39
|
+
fileId: string;
|
|
40
|
+
/** Source media kind; "photo" is always an image. */
|
|
41
|
+
kind: "photo" | "document" | "video" | "audio" | "voice" | "animation";
|
|
42
|
+
/** MIME type when Telegram provides one. */
|
|
43
|
+
mime?: string;
|
|
44
|
+
/** Original file name when provided. */
|
|
45
|
+
fileName?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/** Context for {@link decideThreadedInbound}. All lookups are injected. */
|
|
30
49
|
export interface ThreadedInboundCtx {
|
|
31
50
|
/** The single paired chat id (string-compared). */
|
|
@@ -38,7 +57,15 @@ export interface ThreadedInboundCtx {
|
|
|
38
57
|
|
|
39
58
|
/** Outcome of routing an inbound update. */
|
|
40
59
|
export type ThreadedInboundDecision =
|
|
41
|
-
| {
|
|
60
|
+
| {
|
|
61
|
+
kind: "inject";
|
|
62
|
+
sessionId: string;
|
|
63
|
+
text: string;
|
|
64
|
+
updateId: number;
|
|
65
|
+
threadId: string;
|
|
66
|
+
messageId?: number;
|
|
67
|
+
attachment?: InboundAttachment;
|
|
68
|
+
}
|
|
42
69
|
| { kind: "duplicate"; updateId: number }
|
|
43
70
|
| { kind: "ignore"; reason: string };
|
|
44
71
|
|
|
@@ -48,6 +75,29 @@ function asString(value: unknown): string | undefined {
|
|
|
48
75
|
return undefined;
|
|
49
76
|
}
|
|
50
77
|
|
|
78
|
+
function extractAttachment(message: NonNullable<InboundUpdate["message"]>): InboundAttachment | undefined {
|
|
79
|
+
if (Array.isArray(message.photo) && message.photo.length > 0) {
|
|
80
|
+
const photo = message.photo[message.photo.length - 1];
|
|
81
|
+
if (photo && typeof photo === "object" && "file_id" in photo && typeof photo.file_id === "string") {
|
|
82
|
+
return { fileId: photo.file_id, kind: "photo", mime: "image/jpeg" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const kind of ["document", "video", "audio", "voice", "animation"] as const) {
|
|
87
|
+
const file = message[kind];
|
|
88
|
+
if (file && typeof file === "object" && "file_id" in file && typeof file.file_id === "string") {
|
|
89
|
+
return {
|
|
90
|
+
fileId: file.file_id,
|
|
91
|
+
kind,
|
|
92
|
+
mime: "mime_type" in file && typeof file.mime_type === "string" ? file.mime_type : undefined,
|
|
93
|
+
fileName: "file_name" in file && typeof file.file_name === "string" ? file.file_name : undefined,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
51
101
|
/**
|
|
52
102
|
* Decide whether an inbound update should inject a user turn. Fail-closed:
|
|
53
103
|
* returns `ignore` (with a reason) or `duplicate` for anything that is not an
|
|
@@ -72,9 +122,15 @@ export function decideThreadedInbound(update: InboundUpdate, ctx: ThreadedInboun
|
|
|
72
122
|
const updateId = update.update_id;
|
|
73
123
|
if (ctx.isDuplicate(updateId)) return { kind: "duplicate", updateId };
|
|
74
124
|
|
|
75
|
-
const text =
|
|
76
|
-
|
|
125
|
+
const text =
|
|
126
|
+
typeof message.text === "string"
|
|
127
|
+
? message.text.trim()
|
|
128
|
+
: typeof message.caption === "string"
|
|
129
|
+
? message.caption.trim()
|
|
130
|
+
: "";
|
|
131
|
+
const attachment = extractAttachment(message);
|
|
132
|
+
if (!text && attachment === undefined) return { kind: "ignore", reason: "empty_text" };
|
|
77
133
|
|
|
78
134
|
const messageId = typeof message.message_id === "number" ? message.message_id : undefined;
|
|
79
|
-
return { kind: "inject", sessionId, text, updateId, threadId, messageId };
|
|
135
|
+
return { kind: "inject", sessionId, text, updateId, threadId, messageId, attachment };
|
|
80
136
|
}
|
|
@@ -15,15 +15,19 @@ import type { RateLimitLane } from "./rate-limit-pool";
|
|
|
15
15
|
|
|
16
16
|
/** A Telegram send derived from a threaded frame (topic id is applied by the daemon). */
|
|
17
17
|
export interface ThreadedSend {
|
|
18
|
-
method: "sendMessage" | "sendPhoto";
|
|
18
|
+
method: "sendMessage" | "sendPhoto" | "sendDocument";
|
|
19
19
|
/** Rate-limit lane for prioritisation/fairness. */
|
|
20
20
|
lane: RateLimitLane;
|
|
21
21
|
/** Message text (sendMessage) or photo caption (sendPhoto). */
|
|
22
22
|
text?: string;
|
|
23
23
|
/** Base64 image bytes for sendPhoto. */
|
|
24
24
|
photoBase64?: string;
|
|
25
|
+
/** Base64 file bytes for sendDocument. */
|
|
26
|
+
documentBase64?: string;
|
|
25
27
|
/** Image MIME type for sendPhoto. */
|
|
26
28
|
mime?: string;
|
|
29
|
+
/** Suggested document filename. */
|
|
30
|
+
fileName?: string;
|
|
27
31
|
/** Coalesce key for live edits (same key collapses to the latest). */
|
|
28
32
|
coalesceKey?: string;
|
|
29
33
|
/** True for the one-time identity header (the daemon pins it once). */
|
|
@@ -49,11 +53,12 @@ interface ThreadedFrame {
|
|
|
49
53
|
phase?: unknown;
|
|
50
54
|
text?: unknown;
|
|
51
55
|
messageRef?: unknown;
|
|
52
|
-
// image_attachment
|
|
56
|
+
// image_attachment / file_attachment
|
|
53
57
|
source?: unknown;
|
|
54
58
|
data?: unknown;
|
|
55
59
|
mime?: unknown;
|
|
56
60
|
caption?: unknown;
|
|
61
|
+
name?: unknown;
|
|
57
62
|
// config_update
|
|
58
63
|
verbosity?: unknown;
|
|
59
64
|
redact?: unknown;
|
|
@@ -141,6 +146,19 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
|
|
|
141
146
|
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
142
147
|
};
|
|
143
148
|
}
|
|
149
|
+
case "file_attachment": {
|
|
150
|
+
const data = str(frame.data);
|
|
151
|
+
if (!data) return undefined;
|
|
152
|
+
const caption = str(frame.caption);
|
|
153
|
+
return {
|
|
154
|
+
method: "sendDocument",
|
|
155
|
+
lane: "finalized",
|
|
156
|
+
documentBase64: data,
|
|
157
|
+
mime: str(frame.mime),
|
|
158
|
+
fileName: str(frame.name),
|
|
159
|
+
text: finalizeTelegramHtml(caption === undefined ? undefined : escapeHtml(caption)),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
144
162
|
case "config_update": {
|
|
145
163
|
const verbosity = str(frame.verbosity);
|
|
146
164
|
const redact = typeof frame.redact === "boolean" ? `redact ${frame.redact ? "on" : "off"}` : undefined;
|
|
@@ -65,6 +65,11 @@ export class TopicRegistry {
|
|
|
65
65
|
return this.byTopic.get(topicId);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** All session ids with a persisted topic record. */
|
|
69
|
+
sessionIds(): string[] {
|
|
70
|
+
return [...this.topics.keys()];
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
/** The existing topic record for a session, if any. */
|
|
69
74
|
get(sessionId: string): TopicRecord | undefined {
|
|
70
75
|
return this.topics.get(sessionId);
|
|
@@ -286,6 +286,8 @@ import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
|
286
286
|
import { YieldQueue } from "./yield-queue";
|
|
287
287
|
|
|
288
288
|
/** Session-specific events that extend the core AgentEvent */
|
|
289
|
+
export type AutoCompactionContinuationSkipReason = "auto_continue_disabled_non_resumable_tail";
|
|
290
|
+
|
|
289
291
|
export type AgentSessionEvent =
|
|
290
292
|
| AgentEvent
|
|
291
293
|
| { type: "auto_compaction_start"; reason: "threshold" | "overflow" | "idle"; action: "context-full" | "handoff" }
|
|
@@ -298,6 +300,7 @@ export type AgentSessionEvent =
|
|
|
298
300
|
errorMessage?: string;
|
|
299
301
|
/** True when compaction was skipped for a benign reason (no model, no candidates, nothing to compact). */
|
|
300
302
|
skipped?: boolean;
|
|
303
|
+
continuationSkipReason?: AutoCompactionContinuationSkipReason;
|
|
301
304
|
}
|
|
302
305
|
| {
|
|
303
306
|
type: "auto_retry_start";
|
|
@@ -1869,7 +1872,6 @@ export class AgentSession {
|
|
|
1869
1872
|
this.#resolveTtsrResume();
|
|
1870
1873
|
return;
|
|
1871
1874
|
}
|
|
1872
|
-
this.#ttsrAbortPending = false;
|
|
1873
1875
|
this.#perToolTtsrInjections.clear();
|
|
1874
1876
|
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
1875
1877
|
if (ttsrSettings?.contextMode === "discard" && targetAssistantIndex !== -1) {
|
|
@@ -1898,11 +1900,22 @@ export class AgentSession {
|
|
|
1898
1900
|
);
|
|
1899
1901
|
this.#markTtsrInjected(details.rules);
|
|
1900
1902
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1903
|
+
await this.#scheduleAgentContinue({
|
|
1904
|
+
delayMs: 0,
|
|
1905
|
+
generation,
|
|
1906
|
+
shouldContinue: () => {
|
|
1907
|
+
this.#ttsrAbortPending = false;
|
|
1908
|
+
return true;
|
|
1909
|
+
},
|
|
1910
|
+
onSkip: () => {
|
|
1911
|
+
this.#ttsrAbortPending = false;
|
|
1912
|
+
this.#resolveTtsrResume();
|
|
1913
|
+
},
|
|
1914
|
+
onError: () => {
|
|
1915
|
+
this.#ttsrAbortPending = false;
|
|
1916
|
+
this.#resolveTtsrResume();
|
|
1917
|
+
},
|
|
1918
|
+
});
|
|
1906
1919
|
},
|
|
1907
1920
|
{ delayMs: 50 },
|
|
1908
1921
|
);
|
|
@@ -2214,7 +2227,7 @@ export class AgentSession {
|
|
|
2214
2227
|
#schedulePostPromptTask(
|
|
2215
2228
|
task: (signal: AbortSignal) => Promise<void>,
|
|
2216
2229
|
options?: { delayMs?: number; generation?: number; onSkip?: () => void },
|
|
2217
|
-
): void {
|
|
2230
|
+
): Promise<void> {
|
|
2218
2231
|
const delayMs = options?.delayMs ?? 0;
|
|
2219
2232
|
const signal = this.#postPromptTasksAbortController.signal;
|
|
2220
2233
|
const scheduled = (async () => {
|
|
@@ -2236,18 +2249,20 @@ export class AgentSession {
|
|
|
2236
2249
|
await task(signal);
|
|
2237
2250
|
})();
|
|
2238
2251
|
this.#trackPostPromptTask(scheduled);
|
|
2252
|
+
return scheduled;
|
|
2239
2253
|
}
|
|
2240
2254
|
|
|
2241
2255
|
#scheduleAgentContinue(options?: {
|
|
2242
2256
|
delayMs?: number;
|
|
2243
2257
|
generation?: number;
|
|
2258
|
+
skipCompactionCheck?: boolean;
|
|
2244
2259
|
shouldContinue?: () => boolean;
|
|
2245
2260
|
onSkip?: (reason: "generation_changed" | "aborted_signal" | "queue_drained") => void;
|
|
2246
2261
|
onError?: (error: unknown) => void;
|
|
2247
|
-
}): void {
|
|
2262
|
+
}): Promise<void> {
|
|
2248
2263
|
const scheduledGeneration = options?.generation;
|
|
2249
2264
|
const signal = this.#postPromptTasksAbortController.signal;
|
|
2250
|
-
this.#schedulePostPromptTask(
|
|
2265
|
+
return this.#schedulePostPromptTask(
|
|
2251
2266
|
async () => {
|
|
2252
2267
|
if (signal.aborted) {
|
|
2253
2268
|
options?.onSkip?.("aborted_signal");
|
|
@@ -2263,6 +2278,9 @@ export class AgentSession {
|
|
|
2263
2278
|
}
|
|
2264
2279
|
try {
|
|
2265
2280
|
await this.#maybeRestoreRetryFallbackPrimary();
|
|
2281
|
+
if (!options?.skipCompactionCheck) {
|
|
2282
|
+
await this.#checkEstimatedContextBeforePrompt();
|
|
2283
|
+
}
|
|
2266
2284
|
await this.agent.continue();
|
|
2267
2285
|
} catch (error) {
|
|
2268
2286
|
logger.warn("agent.continue failed after scheduling", {
|
|
@@ -2307,6 +2325,34 @@ export class AgentSession {
|
|
|
2307
2325
|
}
|
|
2308
2326
|
}
|
|
2309
2327
|
|
|
2328
|
+
#detectOverflowRetryContinuationSkip(): AutoCompactionContinuationSkipReason | undefined {
|
|
2329
|
+
this.#stripOverflowFailedTurnForRetry();
|
|
2330
|
+
if (this.#isResumableAgentTail()) return undefined;
|
|
2331
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
2332
|
+
return compactionSettings.autoContinue === false ? "auto_continue_disabled_non_resumable_tail" : undefined;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
#scheduleOverflowRetryContinuation(generation: number): void {
|
|
2336
|
+
this.#stripOverflowFailedTurnForRetry();
|
|
2337
|
+
if (this.#isResumableAgentTail()) {
|
|
2338
|
+
this.#scheduleAgentContinue({
|
|
2339
|
+
delayMs: 100,
|
|
2340
|
+
generation,
|
|
2341
|
+
onSkip: reason => this.#logCompactionContinuationSkipped("overflow_retry", reason),
|
|
2342
|
+
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
2348
|
+
if (compactionSettings.autoContinue !== false) {
|
|
2349
|
+
this.#scheduleAutoContinuePrompt(generation);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
this.#logCompactionContinuationSkipped("overflow_retry", "auto_continue_disabled_non_resumable_tail");
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2310
2356
|
#scheduleAutoContinuePrompt(generation: number): void {
|
|
2311
2357
|
const continuePrompt = async () => {
|
|
2312
2358
|
await this.#promptWithMessage(
|
|
@@ -3052,6 +3098,7 @@ export class AgentSession {
|
|
|
3052
3098
|
willRetry: event.willRetry,
|
|
3053
3099
|
errorMessage: event.errorMessage,
|
|
3054
3100
|
skipped: event.skipped,
|
|
3101
|
+
continuationSkipReason: event.continuationSkipReason,
|
|
3055
3102
|
});
|
|
3056
3103
|
} else if (event.type === "auto_retry_start") {
|
|
3057
3104
|
await this.#extensionRunner.emit({
|
|
@@ -6722,6 +6769,8 @@ export class AgentSession {
|
|
|
6722
6769
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6723
6770
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
6724
6771
|
await this.#runAutoCompaction("overflow", true);
|
|
6772
|
+
} else {
|
|
6773
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
6725
6774
|
}
|
|
6726
6775
|
return;
|
|
6727
6776
|
}
|
|
@@ -6732,17 +6781,19 @@ export class AgentSession {
|
|
|
6732
6781
|
// Skip if this was an error (non-overflow errors don't have usage data)
|
|
6733
6782
|
if (assistantMessage.stopReason === "error") return;
|
|
6734
6783
|
let contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
6735
|
-
|
|
6784
|
+
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
6785
|
+
// Auto maintenance should track actual context fullness.
|
|
6786
|
+
const autoCompactionOutputReserveTokens = 0;
|
|
6736
6787
|
// Cache-epoch invariant: pruning rewrites already-sent toolResult history,
|
|
6737
6788
|
// which breaks the provider prompt-cache prefix mid-epoch. Only prune at a
|
|
6738
6789
|
// sanctioned maintenance boundary, i.e. when the un-pruned context already
|
|
6739
6790
|
// crosses the compaction threshold. Pruning may then avert full compaction.
|
|
6740
|
-
if (!shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6791
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) return;
|
|
6741
6792
|
const pruneResult = await this.#pruneToolOutputs();
|
|
6742
6793
|
if (pruneResult) {
|
|
6743
6794
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6744
6795
|
}
|
|
6745
|
-
if (shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6796
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
|
|
6746
6797
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
6747
6798
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
6748
6799
|
if (!promoted) {
|
|
@@ -6820,14 +6871,16 @@ export class AgentSession {
|
|
|
6820
6871
|
if (!compactionSettings.enabled || compactionSettings.strategy === "off") return;
|
|
6821
6872
|
|
|
6822
6873
|
let contextTokens = this.#estimateContextTokensForCompaction(pendingMessages).tokens;
|
|
6823
|
-
|
|
6824
|
-
|
|
6874
|
+
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
6875
|
+
// Auto maintenance should track actual context fullness.
|
|
6876
|
+
const autoCompactionOutputReserveTokens = 0;
|
|
6877
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) return;
|
|
6825
6878
|
|
|
6826
6879
|
const pruneResult = await this.#pruneToolOutputs();
|
|
6827
6880
|
if (pruneResult) {
|
|
6828
6881
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6829
6882
|
}
|
|
6830
|
-
if (shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6883
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
|
|
6831
6884
|
await this.#runAutoCompaction("threshold", false, false, {
|
|
6832
6885
|
continueAfterMaintenance: false,
|
|
6833
6886
|
deferHandoffMaintenance: false,
|
|
@@ -7730,32 +7783,18 @@ export class AgentSession {
|
|
|
7730
7783
|
|
|
7731
7784
|
const preparation = prepareCompaction(pathEntries, compactionSettings);
|
|
7732
7785
|
if (!preparation) {
|
|
7786
|
+
const continuationSkipReason = willRetry ? this.#detectOverflowRetryContinuationSkip() : undefined;
|
|
7733
7787
|
await this.#emitSessionEvent({
|
|
7734
7788
|
type: "auto_compaction_end",
|
|
7735
7789
|
action,
|
|
7736
7790
|
result: undefined,
|
|
7737
7791
|
aborted: false,
|
|
7738
|
-
willRetry:
|
|
7792
|
+
willRetry: willRetry && !continuationSkipReason,
|
|
7739
7793
|
skipped: true,
|
|
7794
|
+
continuationSkipReason,
|
|
7740
7795
|
});
|
|
7741
7796
|
if (willRetry) {
|
|
7742
|
-
this.#
|
|
7743
|
-
if (this.#isResumableAgentTail()) {
|
|
7744
|
-
this.#scheduleAgentContinue({
|
|
7745
|
-
delayMs: 100,
|
|
7746
|
-
generation,
|
|
7747
|
-
onSkip: skipReason => this.#logCompactionContinuationSkipped("overflow_retry", skipReason),
|
|
7748
|
-
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
7749
|
-
});
|
|
7750
|
-
} else {
|
|
7751
|
-
const tail = this.agent.state.messages.at(-1) as AssistantMessage | undefined;
|
|
7752
|
-
logger.warn("Auto-compaction continuation skipped", {
|
|
7753
|
-
source: "overflow_retry",
|
|
7754
|
-
reason: "not_resumable_tail",
|
|
7755
|
-
role: tail?.role,
|
|
7756
|
-
stopReason: tail?.stopReason,
|
|
7757
|
-
});
|
|
7758
|
-
}
|
|
7797
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
7759
7798
|
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7760
7799
|
this.#scheduleAgentContinue({
|
|
7761
7800
|
delayMs: 100,
|
|
@@ -7966,26 +8005,18 @@ export class AgentSession {
|
|
|
7966
8005
|
details,
|
|
7967
8006
|
preserveData,
|
|
7968
8007
|
};
|
|
7969
|
-
|
|
8008
|
+
const continuationSkipReason = willRetry ? this.#detectOverflowRetryContinuationSkip() : undefined;
|
|
8009
|
+
await this.#emitSessionEvent({
|
|
8010
|
+
type: "auto_compaction_end",
|
|
8011
|
+
action,
|
|
8012
|
+
result,
|
|
8013
|
+
aborted: false,
|
|
8014
|
+
willRetry: willRetry && !continuationSkipReason,
|
|
8015
|
+
continuationSkipReason,
|
|
8016
|
+
});
|
|
7970
8017
|
|
|
7971
8018
|
if (willRetry) {
|
|
7972
|
-
this.#
|
|
7973
|
-
if (!this.#isResumableAgentTail()) {
|
|
7974
|
-
const tail = this.agent.state.messages.at(-1) as AssistantMessage | undefined;
|
|
7975
|
-
logger.warn("Auto-compaction continuation skipped", {
|
|
7976
|
-
source: "overflow_retry",
|
|
7977
|
-
reason: "not_resumable_tail",
|
|
7978
|
-
role: tail?.role,
|
|
7979
|
-
stopReason: tail?.stopReason,
|
|
7980
|
-
});
|
|
7981
|
-
} else {
|
|
7982
|
-
this.#scheduleAgentContinue({
|
|
7983
|
-
delayMs: 100,
|
|
7984
|
-
generation,
|
|
7985
|
-
onSkip: reason => this.#logCompactionContinuationSkipped("overflow_retry", reason),
|
|
7986
|
-
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
7987
|
-
});
|
|
7988
|
-
}
|
|
8019
|
+
this.#scheduleOverflowRetryContinuation(generation);
|
|
7989
8020
|
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7990
8021
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
7991
8022
|
// Kick the loop so queued messages are actually delivered.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseCommandArgs } from "../../utils/command-args";
|
|
1
2
|
import type { ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime } from "../types";
|
|
2
3
|
|
|
3
4
|
export interface ParsedSubcommand {
|
|
@@ -65,7 +66,7 @@ export function errorMessage(error: unknown): string {
|
|
|
65
66
|
* "name required" diagnostics with their own messaging.
|
|
66
67
|
*/
|
|
67
68
|
export function parseNamedScopeArgs(rest: string, invalidScopeMessage: string): NamedScopeArgs {
|
|
68
|
-
const tokens = rest
|
|
69
|
+
const tokens = parseCommandArgs(rest);
|
|
69
70
|
let name: string | undefined;
|
|
70
71
|
let scope: ConfigScope = "project";
|
|
71
72
|
let i = 0;
|
package/src/tools/bash.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-intera
|
|
|
25
25
|
import { checkBashInterception } from "./bash-interceptor";
|
|
26
26
|
import { canUseInteractiveBashPty } from "./bash-pty-selection";
|
|
27
27
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
28
|
+
import { checkComposerBashPolicy } from "./composer-bash-policy";
|
|
28
29
|
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
29
30
|
import { resolveToCwd } from "./path-utils";
|
|
30
31
|
import { formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
@@ -570,6 +571,14 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
570
571
|
}
|
|
571
572
|
}
|
|
572
573
|
|
|
574
|
+
const composerPolicy = checkComposerBashPolicy({
|
|
575
|
+
modelId: this.session.getActiveModelString?.() ?? this.session.getModelString?.() ?? this.session.model?.id,
|
|
576
|
+
commands: rawCommand === command ? [command] : [rawCommand, command],
|
|
577
|
+
});
|
|
578
|
+
if (!composerPolicy.allowed) {
|
|
579
|
+
throw new ToolError(composerPolicy.message);
|
|
580
|
+
}
|
|
581
|
+
|
|
573
582
|
const internalUrlOptions: InternalUrlExpansionOptions = {
|
|
574
583
|
skills: this.session.skills ?? [],
|
|
575
584
|
internalRouter: InternalUrlRouter.instance(),
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isComposerHarnessModel } from "@gajae-code/ai/providers/composer-discipline";
|
|
2
|
+
|
|
3
|
+
export const COMPOSER_BASH_POLICY_ERROR =
|
|
4
|
+
"Composer bash policy blocked repository file I/O. Use find, search, read, and edit tools for file discovery, file inspection, and file mutation.";
|
|
5
|
+
|
|
6
|
+
type ComposerBashPolicyResult =
|
|
7
|
+
| { allowed: true }
|
|
8
|
+
| {
|
|
9
|
+
allowed: false;
|
|
10
|
+
reason: string;
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const BLOCK_PATTERNS: Array<{ id: string; pattern: RegExp }> = [
|
|
15
|
+
{ id: "pipe", pattern: /\|/ },
|
|
16
|
+
{ id: "process-substitution", pattern: /<[>(]/ },
|
|
17
|
+
{ id: "heredoc", pattern: /<<[-~]?/ },
|
|
18
|
+
{ id: "command-substitution", pattern: /\$\(|`/ },
|
|
19
|
+
{ id: "redirection", pattern: /(^|[^<>])(?:>>?|<)(?!=)/ },
|
|
20
|
+
{ id: "tee", pattern: /(?:^|[;&|\s])tee(?:\s|$)/ },
|
|
21
|
+
{
|
|
22
|
+
id: "shell-file-read-discovery",
|
|
23
|
+
pattern: /(?:^|[;&|()\s])(?:\S*\/)?(?:cat|head|tail|less|more|grep|rg|find|fd|tree|ls)\b/,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "shell-file-mutation",
|
|
27
|
+
pattern: /(?:^|[;&|()\s])(?:\S*\/)?(?:cp|mv|rm|touch|mkdir|chmod|chown|ln)\b/,
|
|
28
|
+
},
|
|
29
|
+
{ id: "sed-print", pattern: /(?:^|[;&|()\s])sed\s+(?:-[^\s]*n\b|.*\bp\b)/ },
|
|
30
|
+
{ id: "awk-print", pattern: /(?:^|[;&|()\s])awk\b/ },
|
|
31
|
+
{ id: "git-ls-files", pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+ls-files\b/ },
|
|
32
|
+
{ id: "git-grep", pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+grep\b/ },
|
|
33
|
+
{ id: "git-show-path", pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+show\s+\S+:\S+/ },
|
|
34
|
+
{ id: "git-diff", pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+diff(?:\s|$)/ },
|
|
35
|
+
{ id: "git-cat-file", pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+cat-file\b/ },
|
|
36
|
+
{
|
|
37
|
+
id: "git-show-discovery",
|
|
38
|
+
pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+show\b.*(?:--name-only|--name-status|--stat)/,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "git-log-path-discovery",
|
|
42
|
+
pattern: /(?:^|[;&|()\s])git(?:\s+-C\s+\S+)?\s+log\b.*(?:--name-only|--name-status|--stat)/,
|
|
43
|
+
},
|
|
44
|
+
{ id: "sed-in-place", pattern: /(?:^|[;&|()\s])sed\s+-[^\s]*i\b/ },
|
|
45
|
+
{ id: "perl-in-place", pattern: /(?:^|[;&|()\s])perl\s+-[^\s]*p[^\s]*i\b/ },
|
|
46
|
+
{
|
|
47
|
+
id: "script-file-io",
|
|
48
|
+
pattern:
|
|
49
|
+
/(?:^|[;&|()\s])(?:python3?|node|bun)\s+(?:-\s*<<|-c\b|-e\b|--eval\b).*?(?:read_text|read_bytes|write_text|iterdir|listdir|glob\.glob|readFile|readFileSync|writeFile|writeFileSync|readdir|readdirSync|stat|statSync|cpSync|rmSync|mkdirSync|createReadStream|createWriteStream|Bun\.file|Bun\.write|fs\.readFile|fs\.writeFile|fs\.readdir|fs\.stat|fs\.cp|fs\.rm|fs\.mkdir|open\s*\()/s,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "contaminated-command",
|
|
53
|
+
pattern: /```|^\s*(?:I\s+(?:will|need|am going)|We\s+(?:need|will)|First[, ]|Now[, ]|Let's)\b/im,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const ALLOWED_TERMINAL_PATTERNS: RegExp[] = [
|
|
58
|
+
/^bun\s+test(?:\s+[\w./:@=-]+)*$/,
|
|
59
|
+
/^bun\s+run\s+(?:check(?::[\w-]+)?|test(?::[\w-]+)?|build(?::[\w-]+)?)(?:\s+[\w./:@=-]+)*$/,
|
|
60
|
+
/^bun\s+--version$/,
|
|
61
|
+
/^mise\s+x\s+bun@\d+\.\d+\.\d+\s+--\s+bun\s+test(?:\s+[\w./:@=-]+)*$/,
|
|
62
|
+
/^mise\s+x\s+bun@\d+\.\d+\.\d+\s+--\s+bun\s+run\s+(?:check(?::[\w-]+)?|test(?::[\w-]+)?|build(?::[\w-]+)?)(?:\s+[\w./:@=-]+)*$/,
|
|
63
|
+
/^cargo\s+(?:test|check|build)(?:\s+[\w./:@=-]+)*$/,
|
|
64
|
+
/^git\s+status(?:\s+--short)?(?:\s+--branch)?$/,
|
|
65
|
+
/^git\s+rev-parse\s+HEAD$/,
|
|
66
|
+
/^npm\s+--version$/,
|
|
67
|
+
/^pnpm\s+--version$/,
|
|
68
|
+
/^yarn\s+--version$/,
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function isAllowedComposerTerminalCommand(command: string): boolean {
|
|
72
|
+
const normalized = command.trim().replace(/\s+/g, " ");
|
|
73
|
+
return ALLOWED_TERMINAL_PATTERNS.some(pattern => pattern.test(normalized));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isComposerBashPolicyModel(modelId: string | undefined): boolean {
|
|
77
|
+
return Boolean(modelId && isComposerHarnessModel(modelId));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function checkComposerBashPolicy(input: {
|
|
81
|
+
modelId?: string;
|
|
82
|
+
commands: readonly string[];
|
|
83
|
+
}): ComposerBashPolicyResult {
|
|
84
|
+
if (!isComposerBashPolicyModel(input.modelId)) return { allowed: true };
|
|
85
|
+
for (const command of input.commands) {
|
|
86
|
+
for (const block of BLOCK_PATTERNS) {
|
|
87
|
+
if (block.pattern.test(command)) {
|
|
88
|
+
return { allowed: false, reason: block.id, message: COMPOSER_BASH_POLICY_ERROR };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!isAllowedComposerTerminalCommand(command)) {
|
|
92
|
+
return { allowed: false, reason: "not-allowlisted", message: COMPOSER_BASH_POLICY_ERROR };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { allowed: true };
|
|
96
|
+
}
|