@gajae-code/coding-agent 0.12.0 → 0.12.1
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 +82 -1
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +54 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +38 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/src/async/job-manager.ts +78 -20
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +51 -0
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-runtime.ts +411 -17
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
- package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
- package/src/gjc-runtime/workflow-manifest.ts +4 -68
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -0
- package/src/modes/controllers/selector-controller.ts +45 -0
- package/src/modes/runtime-init.ts +3 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session.ts +88 -9
- package/src/session/agent-session.ts +822 -172
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
package/src/tools/find.ts
CHANGED
|
@@ -158,6 +158,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
158
158
|
const resource = await internalRouter.resolve(rawPattern, {
|
|
159
159
|
cwd: this.session.cwd,
|
|
160
160
|
getArtifactsDir: this.session.getArtifactsDir,
|
|
161
|
+
getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
|
|
161
162
|
});
|
|
162
163
|
if (!resource.sourcePath) {
|
|
163
164
|
throw new ToolError(`Cannot find internal URL without a backing file: ${rawPattern}`);
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
URL_PATHS,
|
|
13
13
|
} from "@gajae-code/ai/providers/openai-codex/constants";
|
|
14
14
|
import {
|
|
15
|
-
$
|
|
15
|
+
$credentialEnv,
|
|
16
16
|
isEnoent,
|
|
17
17
|
parseImageMetadata,
|
|
18
18
|
prompt,
|
|
@@ -794,6 +794,23 @@ async function findAlibabaImageCredentials(
|
|
|
794
794
|
return { provider: "alibaba", apiKey };
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Google image-provider key fallback, from trusted environment sources only.
|
|
799
|
+
*
|
|
800
|
+
* `$env` merges the caller's `cwd/.env`, so reading the key there would let
|
|
801
|
+
* repository content supply the credential these image requests authenticate
|
|
802
|
+
* with. Provider credentials resolve from the launching shell plus GJC/user-owned
|
|
803
|
+
* `.env` files, never the project `.env`.
|
|
804
|
+
*/
|
|
805
|
+
function googleImageApiKeyFromEnv(): string | undefined {
|
|
806
|
+
return $credentialEnv("GOOGLE_API_KEY");
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** Test seam: the Google image key fallback as resolved from trusted env. */
|
|
810
|
+
export function googleImageApiKeyFromEnvForTest(): string | undefined {
|
|
811
|
+
return googleImageApiKeyFromEnv();
|
|
812
|
+
}
|
|
813
|
+
|
|
797
814
|
async function findImageApiKey(
|
|
798
815
|
modelRegistry?: ModelRegistry,
|
|
799
816
|
activeModel?: Model,
|
|
@@ -827,7 +844,7 @@ async function findImageApiKey(
|
|
|
827
844
|
if (config.provider === "gemini") {
|
|
828
845
|
const geminiKey = getEnvApiKey("google");
|
|
829
846
|
if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
|
|
830
|
-
const googleKey =
|
|
847
|
+
const googleKey = googleImageApiKeyFromEnv();
|
|
831
848
|
return googleKey ? { provider: "gemini", apiKey: googleKey } : null;
|
|
832
849
|
}
|
|
833
850
|
if (config.provider === "openrouter") {
|
|
@@ -850,7 +867,7 @@ async function findImageApiKey(
|
|
|
850
867
|
} else if (preferredImageProvider === "gemini") {
|
|
851
868
|
const geminiKey = getEnvApiKey("google");
|
|
852
869
|
if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
|
|
853
|
-
const googleKey =
|
|
870
|
+
const googleKey = googleImageApiKeyFromEnv();
|
|
854
871
|
return googleKey ? { provider: "gemini", apiKey: googleKey } : null;
|
|
855
872
|
} else if (preferredImageProvider === "openrouter") {
|
|
856
873
|
const openRouterKey = getEnvApiKey("openrouter");
|
|
@@ -874,7 +891,7 @@ async function findImageApiKey(
|
|
|
874
891
|
const geminiKey = getEnvApiKey("google");
|
|
875
892
|
if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
|
|
876
893
|
|
|
877
|
-
const googleKey =
|
|
894
|
+
const googleKey = googleImageApiKeyFromEnv();
|
|
878
895
|
if (googleKey) return { provider: "gemini", apiKey: googleKey };
|
|
879
896
|
|
|
880
897
|
const alibaba = await findAlibabaImageCredentials(modelRegistry, sessionId);
|
|
@@ -1106,7 +1123,9 @@ function getOpenAIBaseUrl(model: Model, authCredentialType?: "api_key" | "oauth"
|
|
|
1106
1123
|
return (model.baseUrl || CODEX_BASE_URL).replace(/\/+$/, "");
|
|
1107
1124
|
}
|
|
1108
1125
|
if (authCredentialType === "oauth") return DEFAULT_OPENAI_BASE_URL;
|
|
1109
|
-
|
|
1126
|
+
// Trusted sources only: this base URL becomes the endpoint for authenticated
|
|
1127
|
+
// image requests, and `$env` merges the caller's `cwd/.env`.
|
|
1128
|
+
const envBaseUrl = $credentialEnv("OPENAI_BASE_URL");
|
|
1110
1129
|
const configuredBaseUrl = model.baseUrl?.trim();
|
|
1111
1130
|
if (envBaseUrl && (!configuredBaseUrl || configuredBaseUrl.toLowerCase().includes("api.openai.com"))) {
|
|
1112
1131
|
return envBaseUrl.replace(/\/+$/, "");
|
|
@@ -1114,6 +1133,11 @@ function getOpenAIBaseUrl(model: Model, authCredentialType?: "api_key" | "oauth"
|
|
|
1114
1133
|
return (configuredBaseUrl || envBaseUrl || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
|
|
1115
1134
|
}
|
|
1116
1135
|
|
|
1136
|
+
/** Test seam: the image-request base URL as resolved from trusted env. */
|
|
1137
|
+
export function getOpenAIImageBaseUrlForTest(model: Model, authCredentialType?: "api_key" | "oauth"): string {
|
|
1138
|
+
return getOpenAIBaseUrl(model, authCredentialType);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1117
1141
|
function getOpenAIResponsesUrl(model: Model, authCredentialType?: "api_key" | "oauth"): string {
|
|
1118
1142
|
const baseUrl = getOpenAIBaseUrl(model, authCredentialType);
|
|
1119
1143
|
if (model.api !== "openai-codex-responses" && model.provider !== "openai-codex") {
|
package/src/tools/index.ts
CHANGED
|
@@ -292,6 +292,8 @@ export interface ToolSession {
|
|
|
292
292
|
strictToolNames?: boolean;
|
|
293
293
|
/** Get artifacts directory for artifact:// URLs */
|
|
294
294
|
getArtifactsDir?: () => string | null;
|
|
295
|
+
/** Additional artifacts directories explicitly authorized for this session's tree (parent/child/sibling reads). Derived only from the explicitly adopted/shared `ArtifactManager`; never enumerates unrelated live sessions. */
|
|
296
|
+
getAuthorizedArtifactsDirs?: () => readonly string[];
|
|
295
297
|
/** Get the ArtifactManager backing this session (shared across parent + subagents). */
|
|
296
298
|
getArtifactManager?: () => ArtifactManager | null;
|
|
297
299
|
/** Allocate a new artifact path and ID for session-scoped truncated output. */
|
|
@@ -314,6 +316,8 @@ export interface ToolSession {
|
|
|
314
316
|
settings: Settings;
|
|
315
317
|
/** Live service-tier intent of the parent session, inherited by `inherit` subagents. */
|
|
316
318
|
serviceTier?: ServiceTier;
|
|
319
|
+
/** Whether the effective subagent tier grants fast mode for a resolved provider. */
|
|
320
|
+
isFastForSubagentProvider?: (provider?: string) => boolean;
|
|
317
321
|
/** Plan mode state (if active) */
|
|
318
322
|
getPlanModeState?: () => PlanModeState | undefined;
|
|
319
323
|
/** Goal mode state (if active or paused) */
|
package/src/tools/output-meta.ts
CHANGED
|
@@ -53,8 +53,12 @@ export interface TruncationMeta {
|
|
|
53
53
|
elidedBytes?: number;
|
|
54
54
|
/** Lines elided from the middle. */
|
|
55
55
|
elidedLines?: number;
|
|
56
|
-
/** Artifact ID
|
|
56
|
+
/** Artifact ID when output was persisted; completeness is tracked separately below. */
|
|
57
57
|
artifactId?: string;
|
|
58
|
+
/** Bytes omitted from an artifact after its hard storage cap was reached. */
|
|
59
|
+
artifactTruncatedBytes?: number;
|
|
60
|
+
/** Bounded diagnostic when artifact writer creation, write, or finalization failed. */
|
|
61
|
+
artifactFailureDiagnostic?: string;
|
|
58
62
|
/** Next offset for pagination (head truncation only) */
|
|
59
63
|
nextOffset?: number;
|
|
60
64
|
}
|
|
@@ -323,13 +327,30 @@ export class OutputMetaBuilder {
|
|
|
323
327
|
return this;
|
|
324
328
|
}
|
|
325
329
|
|
|
326
|
-
/** Add truncation info from OutputSummary. No-op if not truncated. */
|
|
330
|
+
/** Add truncation info from OutputSummary. No-op if not truncated or artifact evidence is absent. */
|
|
327
331
|
truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this {
|
|
328
|
-
|
|
332
|
+
const artifactFailureDiagnostic = summary.artifactFailureDiagnostic;
|
|
333
|
+
const artifactTruncatedBytes =
|
|
334
|
+
summary.artifactTruncatedBytes != null && summary.artifactTruncatedBytes > 0
|
|
335
|
+
? summary.artifactTruncatedBytes
|
|
336
|
+
: undefined;
|
|
337
|
+
const hasArtifactEvidence =
|
|
338
|
+
summary.artifactId !== undefined ||
|
|
339
|
+
artifactFailureDiagnostic !== undefined ||
|
|
340
|
+
artifactTruncatedBytes !== undefined;
|
|
341
|
+
if (!summary.truncated && !hasArtifactEvidence) return this;
|
|
329
342
|
|
|
330
343
|
const { direction, startLine = 1, totalFileLines, noticeOwner } = options;
|
|
331
344
|
const totalLines = totalFileLines ?? summary.totalLines;
|
|
332
|
-
const
|
|
345
|
+
const bodyHasArtifact =
|
|
346
|
+
summary.artifactId !== undefined && summary.output.includes(`artifact://${summary.artifactId}`);
|
|
347
|
+
const bodyOwnsArtifact =
|
|
348
|
+
bodyHasArtifact &&
|
|
349
|
+
(artifactTruncatedBytes === undefined ||
|
|
350
|
+
(summary.artifactId !== undefined &&
|
|
351
|
+
summary.output.includes(formatArtifactReference(summary.artifactId, artifactTruncatedBytes))));
|
|
352
|
+
const owner =
|
|
353
|
+
noticeOwner !== undefined ? { noticeOwner } : bodyOwnsArtifact ? { noticeOwner: "body" as const } : {};
|
|
333
354
|
|
|
334
355
|
// Middle elision: the sink retained head + tail with an elision marker.
|
|
335
356
|
if (summary.elidedBytes != null && summary.elidedBytes > 0) {
|
|
@@ -350,6 +371,8 @@ export class OutputMetaBuilder {
|
|
|
350
371
|
elidedBytes: summary.elidedBytes,
|
|
351
372
|
elidedLines,
|
|
352
373
|
artifactId: summary.artifactId,
|
|
374
|
+
artifactTruncatedBytes,
|
|
375
|
+
artifactFailureDiagnostic,
|
|
353
376
|
};
|
|
354
377
|
return this;
|
|
355
378
|
}
|
|
@@ -382,6 +405,8 @@ export class OutputMetaBuilder {
|
|
|
382
405
|
outputBytes: summary.outputBytes,
|
|
383
406
|
shownRange: { start: shownStart, end: shownEnd },
|
|
384
407
|
artifactId: summary.artifactId,
|
|
408
|
+
artifactTruncatedBytes,
|
|
409
|
+
artifactFailureDiagnostic,
|
|
385
410
|
nextOffset: direction === "head" ? shownEnd + 1 : undefined,
|
|
386
411
|
};
|
|
387
412
|
|
|
@@ -524,6 +549,37 @@ export function formatFullOutputReference(artifactId: string): string {
|
|
|
524
549
|
return `Read artifact://${artifactId} for full output`;
|
|
525
550
|
}
|
|
526
551
|
|
|
552
|
+
/**
|
|
553
|
+
* Format an artifact reference without claiming completeness when storage was hard-capped.
|
|
554
|
+
*/
|
|
555
|
+
export function formatArtifactReference(artifactId: string, artifactTruncatedBytes?: number): string {
|
|
556
|
+
if (artifactTruncatedBytes != null && artifactTruncatedBytes > 0) {
|
|
557
|
+
return `Read artifact://${artifactId} for retained output (at least ${formatBytes(artifactTruncatedBytes)} omitted by the artifact storage cap)`;
|
|
558
|
+
}
|
|
559
|
+
return formatFullOutputReference(artifactId);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function formatTruncationArtifactNotice(truncation: TruncationMeta): string {
|
|
563
|
+
const reference = truncation.artifactId
|
|
564
|
+
? formatArtifactReference(truncation.artifactId, truncation.artifactTruncatedBytes)
|
|
565
|
+
: undefined;
|
|
566
|
+
if (truncation.artifactFailureDiagnostic) {
|
|
567
|
+
const failure = `Artifact storage failed: ${truncation.artifactFailureDiagnostic}`;
|
|
568
|
+
if (reference) return `${reference}; ${failure}`;
|
|
569
|
+
return failure;
|
|
570
|
+
}
|
|
571
|
+
if (reference) return reference;
|
|
572
|
+
return `Artifact storage omitted at least ${formatBytes(truncation.artifactTruncatedBytes ?? 0)}; no artifact reference is available`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function hasArtifactNotice(truncation: TruncationMeta): boolean {
|
|
576
|
+
return (
|
|
577
|
+
truncation.artifactId != null ||
|
|
578
|
+
(truncation.artifactTruncatedBytes ?? 0) > 0 ||
|
|
579
|
+
truncation.artifactFailureDiagnostic != null
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
527
583
|
function formatTruncationRangeTotal(truncation: TruncationMeta): string {
|
|
528
584
|
return truncation.rangeBase === "window"
|
|
529
585
|
? `the selected ${truncation.totalLines}-line range`
|
|
@@ -537,8 +593,8 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
|
|
537
593
|
if (truncation.partialLine.sourceBytes > truncation.partialLine.bytes) {
|
|
538
594
|
notice += ` (line is ${formatBytes(truncation.partialLine.sourceBytes)})`;
|
|
539
595
|
}
|
|
540
|
-
if (truncation
|
|
541
|
-
notice += `. ${
|
|
596
|
+
if (hasArtifactNotice(truncation)) {
|
|
597
|
+
notice += `. ${formatTruncationArtifactNotice(truncation)}`;
|
|
542
598
|
}
|
|
543
599
|
return notice;
|
|
544
600
|
}
|
|
@@ -553,13 +609,15 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
|
|
553
609
|
const elidedLines = truncation.elidedLines ?? Math.max(0, totalLines - truncation.outputLines);
|
|
554
610
|
const headPart = head ? `lines ${head.start}-${head.end}` : "";
|
|
555
611
|
const tailPart = tail ? `${tail.start}-${tail.end}` : "";
|
|
556
|
-
if (
|
|
612
|
+
if (head && tail && elidedLines === 0 && head.start === tail.start && head.end === tail.end) {
|
|
613
|
+
notice = `Showing head and tail of line ${head.start} of ${rangeTotal}; ${formatBytes(elidedBytes)} middle bytes elided`;
|
|
614
|
+
} else if (headPart && tailPart) {
|
|
557
615
|
notice = `Showing ${headPart} and ${tailPart} of ${rangeTotal}; ${elidedLines.toLocaleString()} middle line${elidedLines === 1 ? "" : "s"} (${formatBytes(elidedBytes)}) elided`;
|
|
558
616
|
} else {
|
|
559
|
-
notice = `Showing ${truncation.outputLines} of ${rangeTotal}${truncation.rangeBase === "window" ? "" : " lines"}; middle elided`;
|
|
617
|
+
notice = `Showing ${truncation.outputLines} of ${rangeTotal}${truncation.rangeBase === "window" ? "" : " lines"}; ${formatBytes(elidedBytes)} middle bytes elided`;
|
|
560
618
|
}
|
|
561
|
-
if (truncation
|
|
562
|
-
notice += `. ${
|
|
619
|
+
if (hasArtifactNotice(truncation)) {
|
|
620
|
+
notice += `. ${formatTruncationArtifactNotice(truncation)}`;
|
|
563
621
|
}
|
|
564
622
|
return notice;
|
|
565
623
|
}
|
|
@@ -580,8 +638,8 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
|
|
580
638
|
notice += `. Use :${truncation.nextOffset} to continue`;
|
|
581
639
|
}
|
|
582
640
|
|
|
583
|
-
if (truncation
|
|
584
|
-
notice += `. ${
|
|
641
|
+
if (hasArtifactNotice(truncation)) {
|
|
642
|
+
notice += `. ${formatTruncationArtifactNotice(truncation)}`;
|
|
585
643
|
}
|
|
586
644
|
|
|
587
645
|
return notice;
|
|
@@ -642,8 +700,25 @@ export function formatOutputNotice(meta: OutputMeta | undefined): string {
|
|
|
642
700
|
* Returns null if no truncation metadata present.
|
|
643
701
|
*/
|
|
644
702
|
export function formatStyledTruncationWarning(meta: OutputMeta | undefined, theme: Theme): string | null {
|
|
645
|
-
|
|
646
|
-
|
|
703
|
+
const truncation = meta?.truncation;
|
|
704
|
+
if (!truncation) return null;
|
|
705
|
+
if (
|
|
706
|
+
truncation.noticeOwner === "body" &&
|
|
707
|
+
truncation.totalBytes === truncation.outputBytes &&
|
|
708
|
+
truncation.totalLines === truncation.outputLines
|
|
709
|
+
) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
const warningMeta =
|
|
713
|
+
truncation.noticeOwner === "body"
|
|
714
|
+
? {
|
|
715
|
+
...truncation,
|
|
716
|
+
artifactId: undefined,
|
|
717
|
+
artifactTruncatedBytes: undefined,
|
|
718
|
+
artifactFailureDiagnostic: undefined,
|
|
719
|
+
}
|
|
720
|
+
: truncation;
|
|
721
|
+
const message = formatTruncationMetaNotice(warningMeta);
|
|
647
722
|
return theme.fg("warning", wrapBrackets(message, theme));
|
|
648
723
|
}
|
|
649
724
|
|
|
@@ -735,6 +810,25 @@ export function resolveOutputSinkHeadBytes(s: Settings | undefined): number {
|
|
|
735
810
|
return getSpillConfig(s).headBytes;
|
|
736
811
|
}
|
|
737
812
|
|
|
813
|
+
export const BASH_DEFAULT_OUTPUT_TAIL_BYTES = 1024;
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Bash uses a deliberately small tail window to nudge callers toward focused
|
|
817
|
+
* commands and dedicated search tools. An explicitly configured shared tail
|
|
818
|
+
* budget still wins.
|
|
819
|
+
*/
|
|
820
|
+
export function resolveBashOutputSinkTailBytes(s: Settings): number {
|
|
821
|
+
return s.has("tools.artifactTailBytes") ? s.get("tools.artifactTailBytes") * 1024 : BASH_DEFAULT_OUTPUT_TAIL_BYTES;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Bash keeps only the tail unless the user explicitly opts into the shared
|
|
825
|
+
* head-retention setting. Schema defaults still apply to other streaming
|
|
826
|
+
* tools without silently turning Bash back into middle-elision mode.
|
|
827
|
+
*/
|
|
828
|
+
export function resolveBashOutputSinkHeadBytes(s: Settings): number {
|
|
829
|
+
return s.has("tools.artifactHeadBytes") ? resolveOutputSinkHeadBytes(s) : 0;
|
|
830
|
+
}
|
|
831
|
+
|
|
738
832
|
/**
|
|
739
833
|
* Resolve the per-line column cap from session settings. Shared by streaming
|
|
740
834
|
* executors (bash/python/ssh/eval via OutputSink) and the `read` tool's
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -637,6 +637,7 @@ export interface ToolScopeOptions {
|
|
|
637
637
|
rawPaths: string[];
|
|
638
638
|
cwd: string;
|
|
639
639
|
getArtifactsDir?: () => string | null;
|
|
640
|
+
getAuthorizedArtifactsDirs?: () => readonly string[];
|
|
640
641
|
/** Verb used in the "Cannot {action} internal URL without a backing file: …" message. */
|
|
641
642
|
internalUrlAction: string;
|
|
642
643
|
/** Collect absolute paths flagged immutable by their internal-URL handler. */
|
|
@@ -667,7 +668,7 @@ export interface ToolScopeResolution {
|
|
|
667
668
|
* 5. stat the resolved base path so callers can branch on directory vs file scope.
|
|
668
669
|
*/
|
|
669
670
|
export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<ToolScopeResolution> {
|
|
670
|
-
const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir } = opts;
|
|
671
|
+
const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir, getAuthorizedArtifactsDirs } = opts;
|
|
671
672
|
const rawPaths = inputs.map(normalizePathLikeInput);
|
|
672
673
|
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
673
674
|
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
@@ -683,7 +684,7 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
|
|
|
683
684
|
if (hasGlobPathChars(rawPath)) {
|
|
684
685
|
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
|
|
685
686
|
}
|
|
686
|
-
const resource = await internalRouter.resolve(rawPath, { cwd, getArtifactsDir });
|
|
687
|
+
const resource = await internalRouter.resolve(rawPath, { cwd, getArtifactsDir, getAuthorizedArtifactsDirs });
|
|
687
688
|
if (!resource.sourcePath) {
|
|
688
689
|
throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);
|
|
689
690
|
}
|
package/src/tools/read.ts
CHANGED
|
@@ -1419,6 +1419,21 @@ interface ResolvedSqliteReadPath {
|
|
|
1419
1419
|
* Reads files with support for images, converted documents (via markit), and text.
|
|
1420
1420
|
* Directories return a formatted listing with modification times.
|
|
1421
1421
|
*/
|
|
1422
|
+
/**
|
|
1423
|
+
* A client-authority denial is a decision, not a transport failure: falling back to
|
|
1424
|
+
* disk would bypass the permission the client just refused. Availability failures
|
|
1425
|
+
* still fall back so an unreachable bridge cannot break local reads.
|
|
1426
|
+
*/
|
|
1427
|
+
function isClientAuthorityDenial(error: unknown): boolean {
|
|
1428
|
+
const code =
|
|
1429
|
+
typeof error === "object" && error !== null && "code" in error ? (error as { code?: unknown }).code : undefined;
|
|
1430
|
+
// ACP clients surface refusals as an application error; -32001 is the reserved
|
|
1431
|
+
// client-authority denial code and -32603 covers hosts without a dedicated code.
|
|
1432
|
+
if (code === "permission_denied" || code === "forbidden" || code === -32001 || code === -32603) return true;
|
|
1433
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
1434
|
+
return /permission denied|not permitted|access denied|forbidden/i.test(message);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1422
1437
|
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
1423
1438
|
readonly name = "read";
|
|
1424
1439
|
readonly label = "Read";
|
|
@@ -1991,6 +2006,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1991
2006
|
}
|
|
1992
2007
|
return { outputText: "", contentLineCount: 0, columnTruncated: 0, bridgeResult };
|
|
1993
2008
|
} catch (error) {
|
|
2009
|
+
if (isClientAuthorityDenial(error)) throw error;
|
|
1994
2010
|
logger.warn("ACP fs readTextFile failed; falling back to disk", { path: absolutePath, error });
|
|
1995
2011
|
}
|
|
1996
2012
|
}
|
|
@@ -2893,6 +2909,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2893
2909
|
}
|
|
2894
2910
|
return bridgeResult;
|
|
2895
2911
|
} catch (error) {
|
|
2912
|
+
if (isClientAuthorityDenial(error)) throw error;
|
|
2896
2913
|
logger.warn("ACP fs readTextFile failed; falling back to disk", { path: absolutePath, error });
|
|
2897
2914
|
}
|
|
2898
2915
|
}
|
|
@@ -3396,6 +3413,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
3396
3413
|
const resource = await internalRouter.resolve(url, {
|
|
3397
3414
|
cwd: this.session.cwd,
|
|
3398
3415
|
getArtifactsDir: this.session.getArtifactsDir,
|
|
3416
|
+
getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
|
|
3399
3417
|
settings: this.session.settings,
|
|
3400
3418
|
signal,
|
|
3401
3419
|
});
|
package/src/tools/review.ts
CHANGED
|
@@ -188,15 +188,13 @@ export interface SubmitReviewDetails {
|
|
|
188
188
|
// Re-export types for external use
|
|
189
189
|
export type { ReportFindingDetails };
|
|
190
190
|
/**
|
|
191
|
-
*
|
|
192
|
-
* `ReviewFinding` shape
|
|
191
|
+
* Convert tool-side `ReportFindingDetails` into the numeric-priority
|
|
192
|
+
* `ReviewFinding` shape used by caller-owned reviewer payloads.
|
|
193
193
|
*
|
|
194
194
|
* The `report_finding` tool exposes `priority` as a string enum (`"P0".."P3"`)
|
|
195
|
-
* for ergonomics,
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* that surfaces a finding is rejected with `findings.0.priority: expected
|
|
199
|
-
* number, received string`.
|
|
195
|
+
* for ergonomics, while reviewer schemas may explicitly declare numeric
|
|
196
|
+
* priorities. This pure conversion does not add findings to completion data;
|
|
197
|
+
* callers that include `findings` own both that field and its output schema.
|
|
200
198
|
*/
|
|
201
199
|
export function toReviewFinding(details: ReportFindingDetails): ReviewFinding {
|
|
202
200
|
return {
|
package/src/tools/search.ts
CHANGED
|
@@ -287,6 +287,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
287
287
|
rawPaths: resolvedPaths,
|
|
288
288
|
cwd: this.session.cwd,
|
|
289
289
|
getArtifactsDir: this.session.getArtifactsDir,
|
|
290
|
+
getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
|
|
290
291
|
internalUrlAction: "search",
|
|
291
292
|
trackImmutableSources: true,
|
|
292
293
|
surfaceExactFilePaths: true,
|
|
@@ -192,6 +192,9 @@ function renderSubagentSnapshotBody(
|
|
|
192
192
|
}
|
|
193
193
|
if (snapshot.description) lines.push(` ${theme.fg("dim", `Description: ${snapshot.description}`)}`);
|
|
194
194
|
if (snapshot.outputRef) lines.push(` ${theme.fg("dim", `Output: ${snapshot.outputRef}`)}`);
|
|
195
|
+
if (snapshot.setupFailureSummary) {
|
|
196
|
+
lines.push(` ${theme.fg("error", `Setup failure: ${snapshot.setupFailureSummary}`)}`);
|
|
197
|
+
}
|
|
195
198
|
if (snapshot.assignment) {
|
|
196
199
|
lines.push(` ${theme.fg("dim", "Assignment:")}`);
|
|
197
200
|
for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
|
|
@@ -312,8 +315,9 @@ export const subagentToolRenderer = {
|
|
|
312
315
|
);
|
|
313
316
|
}
|
|
314
317
|
|
|
315
|
-
snapshotSignatures ??= subagents.map(
|
|
316
|
-
|
|
318
|
+
snapshotSignatures ??= subagents.map(
|
|
319
|
+
snapshot =>
|
|
320
|
+
`${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}`,
|
|
317
321
|
);
|
|
318
322
|
subagents.forEach((snapshot, index) => {
|
|
319
323
|
// Fresh per-subagent status line (cheap), then a cached or dynamic body.
|
package/src/tools/subagent.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface SubagentSnapshot {
|
|
|
65
65
|
durationMs: number;
|
|
66
66
|
resultText?: string;
|
|
67
67
|
errorText?: string;
|
|
68
|
+
/** Safe setup failure cause retained from the executor launch path. */
|
|
69
|
+
setupFailureSummary?: string;
|
|
68
70
|
resultPreview?: string;
|
|
69
71
|
outputRef?: string;
|
|
70
72
|
truncated?: boolean;
|
|
@@ -603,6 +605,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
603
605
|
}
|
|
604
606
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
605
607
|
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
608
|
+
if (snapshot.setupFailureSummary) lines.push(`Setup failure: ${snapshot.setupFailureSummary}`);
|
|
606
609
|
if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
|
|
607
610
|
if (snapshot.steerMessage) {
|
|
608
611
|
lines.push(`Steer (${snapshot.steerState ?? "queued"}):`, "```", snapshot.steerMessage, "```");
|
|
@@ -731,6 +734,9 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
731
734
|
truncated: output.truncated,
|
|
732
735
|
}
|
|
733
736
|
: {}),
|
|
737
|
+
...(job.setupFailureSummary
|
|
738
|
+
? { setupFailureSummary: sanitizeText(job.setupFailureSummary, RECEIPT_PREVIEW_WIDTH) }
|
|
739
|
+
: {}),
|
|
734
740
|
...(outputRef ? { outputRef } : {}),
|
|
735
741
|
...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
|
|
736
742
|
...this.#modelFields(record),
|
|
@@ -31,8 +31,10 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
|
|
|
31
31
|
readonly summary = "Send a workspace file to Telegram";
|
|
32
32
|
readonly loadMode = "discoverable";
|
|
33
33
|
readonly description =
|
|
34
|
-
"Send a file from the current workspace to the connected Telegram chat
|
|
35
|
-
"
|
|
34
|
+
"Send a file from the current workspace to the connected Telegram chat. Recognized images are converted to " +
|
|
35
|
+
"Telegram-compatible photos when possible, including WebP; other files are sent as documents with their MIME " +
|
|
36
|
+
"type preserved. The path must resolve (after following symlinks) to a regular file inside the project root; " +
|
|
37
|
+
"paths outside the workspace are rejected.";
|
|
36
38
|
readonly parameters = telegramSendSchema;
|
|
37
39
|
readonly strict = true;
|
|
38
40
|
|
|
@@ -109,6 +111,7 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
|
|
|
109
111
|
};
|
|
110
112
|
}
|
|
111
113
|
const abs = contained.path;
|
|
114
|
+
const mime = Bun.file(abs).type;
|
|
112
115
|
|
|
113
116
|
const sink = getTelegramFileSink(sessionId);
|
|
114
117
|
if (!sink) {
|
|
@@ -126,7 +129,7 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
|
|
|
126
129
|
};
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
const result = await sink({ path: abs, caption: params.caption });
|
|
132
|
+
const result = await sink({ path: abs, caption: params.caption, mime });
|
|
130
133
|
if (result.ok) {
|
|
131
134
|
return {
|
|
132
135
|
content: [{ type: "text", text: `Sent ${path.basename(abs)} to Telegram.` }],
|
package/src/utils/gh.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DEFAULT_GH_TIMEOUT_MS = 5_000;
|
|
2
|
+
|
|
3
|
+
export interface GhResult {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
timedOut?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RunGh = (args: string[], options?: { timeoutMs?: number }) => Promise<GhResult>;
|
|
11
|
+
|
|
12
|
+
/** Run GitHub CLI without allowing it to consume the parent terminal's input. */
|
|
13
|
+
export async function runGhDefault(args: string[], options?: { timeoutMs?: number }): Promise<GhResult> {
|
|
14
|
+
const ghPath = Bun.which("gh");
|
|
15
|
+
if (!ghPath) {
|
|
16
|
+
return { exitCode: -1, stdout: "", stderr: "gh not found", timedOut: false };
|
|
17
|
+
}
|
|
18
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS;
|
|
19
|
+
let timedOut = false;
|
|
20
|
+
try {
|
|
21
|
+
const proc = Bun.spawn([ghPath, ...args], { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
timedOut = true;
|
|
24
|
+
proc.kill();
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
try {
|
|
27
|
+
const [stdout, stderr] = await Promise.all([
|
|
28
|
+
new Response(proc.stdout).text(),
|
|
29
|
+
new Response(proc.stderr).text(),
|
|
30
|
+
]);
|
|
31
|
+
const exitCode = await proc.exited;
|
|
32
|
+
return { exitCode, stdout, stderr, timedOut };
|
|
33
|
+
} finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return { exitCode: -1, stdout: "", stderr: err instanceof Error ? err.message : String(err), timedOut };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -6,6 +6,7 @@ export interface ImageResizeOptions {
|
|
|
6
6
|
maxBytes?: number;
|
|
7
7
|
jpegQuality?: number;
|
|
8
8
|
excludeWebP?: boolean;
|
|
9
|
+
forceReencode?: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface ResizedImage {
|
|
@@ -30,6 +31,7 @@ const DEFAULT_OPTIONS: Required<Omit<ImageResizeOptions, "excludeWebP">> = {
|
|
|
30
31
|
maxHeight: 1568,
|
|
31
32
|
maxBytes: DEFAULT_IMAGE_RESIZE_MAX_BYTES,
|
|
32
33
|
jpegQuality: 80,
|
|
34
|
+
forceReencode: false,
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -94,6 +96,7 @@ export async function resizeImageBuffer(
|
|
|
94
96
|
originalWidth <= opts.maxWidth &&
|
|
95
97
|
originalHeight <= opts.maxHeight &&
|
|
96
98
|
originalSize <= comfortableSize &&
|
|
99
|
+
!opts.forceReencode &&
|
|
97
100
|
!(opts.excludeWebP && sourceMime === "image/webp")
|
|
98
101
|
) {
|
|
99
102
|
return {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
buildAnthropicUrl,
|
|
15
15
|
stripClaudeToolPrefix,
|
|
16
16
|
} from "@gajae-code/ai";
|
|
17
|
-
import { $env } from "@gajae-code/utils";
|
|
17
|
+
import { $credentialEnv, $env } from "@gajae-code/utils";
|
|
18
18
|
import type {
|
|
19
19
|
AnthropicApiResponse,
|
|
20
20
|
AnthropicCitation,
|
|
@@ -275,8 +275,8 @@ export async function searchAnthropic(
|
|
|
275
275
|
params: SearchParams | AnthropicSearchParams,
|
|
276
276
|
_legacyStorage?: unknown,
|
|
277
277
|
): Promise<SearchResponse> {
|
|
278
|
-
const searchApiKey = $
|
|
279
|
-
const searchBaseUrl = $
|
|
278
|
+
const searchApiKey = $credentialEnv("ANTHROPIC_SEARCH_API_KEY");
|
|
279
|
+
const searchBaseUrl = $credentialEnv("ANTHROPIC_SEARCH_BASE_URL");
|
|
280
280
|
let auth: AnthropicAuthConfig | undefined;
|
|
281
281
|
// When reusing the active model's own credentials (native search over a
|
|
282
282
|
// proxy), prefer its wire model id and carry its request headers through.
|
|
@@ -360,7 +360,7 @@ export class AnthropicProvider extends SearchProvider {
|
|
|
360
360
|
readonly label = "Anthropic";
|
|
361
361
|
|
|
362
362
|
isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean {
|
|
363
|
-
return Boolean($
|
|
363
|
+
return Boolean($credentialEnv("ANTHROPIC_SEARCH_API_KEY")) || authStorage.hasAuth("anthropic");
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Endpoint: POST https://api.kimi.com/coding/v1/search
|
|
6
6
|
*/
|
|
7
7
|
import type { AuthStorage } from "@gajae-code/ai";
|
|
8
|
-
import { $
|
|
8
|
+
import { $credentialEnv } from "@gajae-code/utils";
|
|
9
9
|
|
|
10
10
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
11
11
|
import { SearchProviderError } from "../../../web/search/types";
|
|
@@ -55,8 +55,17 @@ function asTrimmed(value: string | undefined): string | undefined {
|
|
|
55
55
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/** Test seam: the Kimi search base URL as resolved from trusted env. */
|
|
59
|
+
export function resolveKimiSearchBaseUrlForTest(): string {
|
|
60
|
+
return resolveBaseUrl();
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
function resolveBaseUrl(): string {
|
|
59
|
-
return
|
|
64
|
+
return (
|
|
65
|
+
asTrimmed($credentialEnv("MOONSHOT_SEARCH_BASE_URL")) ??
|
|
66
|
+
asTrimmed($credentialEnv("KIMI_SEARCH_BASE_URL")) ??
|
|
67
|
+
KIMI_SEARCH_URL
|
|
68
|
+
);
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/** Find Kimi search credentials from environment or AuthStorage. */
|
|
@@ -65,7 +74,8 @@ async function findApiKey(
|
|
|
65
74
|
sessionId: string | undefined,
|
|
66
75
|
signal: AbortSignal | undefined,
|
|
67
76
|
): Promise<string | null> {
|
|
68
|
-
const envKey =
|
|
77
|
+
const envKey =
|
|
78
|
+
asTrimmed($credentialEnv("MOONSHOT_SEARCH_API_KEY")) ?? asTrimmed($credentialEnv("KIMI_SEARCH_API_KEY"));
|
|
69
79
|
if (envKey) return envKey;
|
|
70
80
|
|
|
71
81
|
return (
|
|
@@ -157,8 +167,8 @@ export class KimiProvider extends SearchProvider {
|
|
|
157
167
|
|
|
158
168
|
isAvailable(authStorage: AuthStorage): boolean {
|
|
159
169
|
return (
|
|
160
|
-
!!asTrimmed($
|
|
161
|
-
!!asTrimmed($
|
|
170
|
+
!!asTrimmed($credentialEnv("MOONSHOT_SEARCH_API_KEY")) ||
|
|
171
|
+
!!asTrimmed($credentialEnv("KIMI_SEARCH_API_KEY")) ||
|
|
162
172
|
authStorage.hasAuth("moonshot") ||
|
|
163
173
|
authStorage.hasAuth("kimi-code")
|
|
164
174
|
);
|