@gajae-code/coding-agent 0.5.0 → 0.5.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 +36 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +29 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +7 -0
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +39 -3
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +27 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +5 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli/setup-cli.ts +138 -3
- package/src/cli.ts +1 -0
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +7 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +193 -0
- package/src/config/file-lock.ts +66 -10
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +39 -30
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +459 -3
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +232 -19
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +48 -30
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +179 -2
- package/src/gjc-runtime/tmux-common.ts +14 -0
- package/src/gjc-runtime/tmux-gc.ts +177 -0
- package/src/gjc-runtime/tmux-sessions.ts +49 -1
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +14 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/harness-control-plane/storage.ts +70 -0
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/docs-index.generated.ts +22 -12
- package/src/lsp/defaults.json +1 -0
- package/src/main.ts +18 -3
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +2 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +51 -8
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +81 -1
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +266 -34
- package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/agents/executor.md +5 -2
- package/src/sdk.ts +29 -4
- package/src/session/agent-session.ts +99 -19
- package/src/session/blob-store.ts +59 -3
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +72 -20
- package/src/setup/credential-import.ts +429 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +16 -1
- package/src/task/render.ts +18 -7
- package/src/tools/ask.ts +59 -2
- package/src/tools/cron.ts +1 -1
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/subagent-render.ts +128 -29
- package/src/tools/subagent.ts +173 -9
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- package/src/web/search/types.ts +47 -22
package/src/lsp/defaults.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { activateModelProfile } from "./config/model-profile-activation";
|
|
|
30
30
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
31
31
|
import { resolveCliModel, resolveModelRoleValue, resolveModelScope, type ScopedModel } from "./config/model-resolver";
|
|
32
32
|
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
33
|
+
import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory } from "./defaults/gjc-grok-cli";
|
|
33
34
|
import { initializeWithSettings } from "./discovery";
|
|
34
35
|
import { exportFromFile } from "./export/html";
|
|
35
36
|
import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
@@ -742,7 +743,9 @@ export async function runRootCommand(
|
|
|
742
743
|
await runListModelsCommand({
|
|
743
744
|
modelRegistry,
|
|
744
745
|
cwd: getProjectDir(),
|
|
745
|
-
|
|
746
|
+
extensionFactories: [
|
|
747
|
+
{ factory: getBundledGrokBuildExtensionFactory(), name: BUNDLED_GROK_BUILD_EXTENSION_ID },
|
|
748
|
+
],
|
|
746
749
|
settingsExtensions: [],
|
|
747
750
|
disabledExtensionIds: [],
|
|
748
751
|
disableExtensionDiscovery: true,
|
|
@@ -974,8 +977,20 @@ export async function runRootCommand(
|
|
|
974
977
|
}
|
|
975
978
|
|
|
976
979
|
if (mode === "rpc" || mode === "rpc-ui") {
|
|
977
|
-
const { runRpcMode } = await import("./modes/rpc/rpc-mode");
|
|
978
|
-
|
|
980
|
+
const { RpcListenRefusedError, runRpcMode } = await import("./modes/rpc/rpc-mode");
|
|
981
|
+
try {
|
|
982
|
+
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, {
|
|
983
|
+
listen: parsedArgs.rpcListen,
|
|
984
|
+
});
|
|
985
|
+
} catch (error) {
|
|
986
|
+
if (!(error instanceof RpcListenRefusedError)) throw error;
|
|
987
|
+
logger.setTransports({ console: true, file: true });
|
|
988
|
+
logger.error(error.message);
|
|
989
|
+
await session.dispose();
|
|
990
|
+
stopThemeWatcher();
|
|
991
|
+
await postmortem.quit(1);
|
|
992
|
+
process.exit(1);
|
|
993
|
+
}
|
|
979
994
|
} else if (mode === "bridge") {
|
|
980
995
|
const { runBridgeMode } = await import("./modes/bridge/bridge-mode");
|
|
981
996
|
await runBridgeMode(session, setToolUIContext);
|
|
@@ -43,7 +43,8 @@ import {
|
|
|
43
43
|
type Usage,
|
|
44
44
|
} from "@agentclientprotocol/sdk";
|
|
45
45
|
import type { AssistantMessage, Model } from "@gajae-code/ai";
|
|
46
|
-
import { logger
|
|
46
|
+
import { logger } from "@gajae-code/utils";
|
|
47
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
47
48
|
import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
|
|
48
49
|
import { Settings } from "../../config/settings";
|
|
49
50
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -98,6 +99,7 @@ const SESSION_PAGE_SIZE = 50;
|
|
|
98
99
|
* wait past this guard without hard-coding the literal.
|
|
99
100
|
*/
|
|
100
101
|
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
102
|
+
const CODING_AGENT_VERSION: string = packageJson.version;
|
|
101
103
|
const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
|
|
102
104
|
const ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS = 250;
|
|
103
105
|
const ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES = 3;
|
|
@@ -414,7 +416,7 @@ export class AcpAgent implements Agent {
|
|
|
414
416
|
agentInfo: {
|
|
415
417
|
name: "gajae-code",
|
|
416
418
|
title: "Gajae Code",
|
|
417
|
-
version:
|
|
419
|
+
version: CODING_AGENT_VERSION,
|
|
418
420
|
},
|
|
419
421
|
authMethods,
|
|
420
422
|
agentCapabilities: {
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { UiRequestBroker } from "../shared/agent-wire/ui-request-broker";
|
|
30
30
|
import type { BridgeUiResult } from "../shared/agent-wire/ui-result";
|
|
31
31
|
import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
|
|
32
|
-
import { UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
32
|
+
import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
33
33
|
import { FileGateStore } from "../shared/agent-wire/workflow-gate-broker";
|
|
34
34
|
import { assertSafeBridgeBind, isBridgeTokenAuthorized } from "./auth";
|
|
35
35
|
import { type BridgePermissionRequestPayload, createBridgeClientBridge } from "./bridge-client-bridge";
|
|
@@ -599,6 +599,7 @@ export async function runBridgeMode(
|
|
|
599
599
|
emitFrame: gate => eventStream.publish(toBridgeWorkflowGateFrame(gate, sequencer)),
|
|
600
600
|
store: gateStore,
|
|
601
601
|
audit: recordAudit,
|
|
602
|
+
providerSupportsTokenCostMetrics: modelSupportsTokenCostMetrics(session.model),
|
|
602
603
|
getUsageSnapshot: () => {
|
|
603
604
|
const stats = session.getSessionStats();
|
|
604
605
|
return { tokens: stats.tokens.total, costUsd: stats.cost };
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
truncateToWidth,
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@gajae-code/tui";
|
|
13
|
+
import { getProjectDir } from "@gajae-code/utils";
|
|
13
14
|
import { theme } from "../../modes/theme/theme";
|
|
14
15
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
15
16
|
import type { HistoryEntry, HistoryStorage } from "../../session/history-storage";
|
|
@@ -72,6 +73,7 @@ class HistoryResultsList implements Component {
|
|
|
72
73
|
|
|
73
74
|
export class HistorySearchComponent extends Container {
|
|
74
75
|
#historyStorage: HistoryStorage;
|
|
76
|
+
#cwd: string;
|
|
75
77
|
#searchInput: Input;
|
|
76
78
|
#results: HistoryEntry[] = [];
|
|
77
79
|
#selectedIndex = 0;
|
|
@@ -83,6 +85,7 @@ export class HistorySearchComponent extends Container {
|
|
|
83
85
|
constructor(historyStorage: HistoryStorage, onSelect: (prompt: string) => void, onCancel: () => void) {
|
|
84
86
|
super();
|
|
85
87
|
this.#historyStorage = historyStorage;
|
|
88
|
+
this.#cwd = getProjectDir();
|
|
86
89
|
this.#onSelect = onSelect;
|
|
87
90
|
this.#onCancel = onCancel;
|
|
88
91
|
|
|
@@ -150,8 +153,8 @@ export class HistorySearchComponent extends Container {
|
|
|
150
153
|
#updateResults(): void {
|
|
151
154
|
const query = this.#searchInput.getValue().trim();
|
|
152
155
|
this.#results = query
|
|
153
|
-
? this.#historyStorage.search(query, this.#resultLimit)
|
|
154
|
-
: this.#historyStorage.getRecent(this.#resultLimit);
|
|
156
|
+
? this.#historyStorage.search(query, this.#resultLimit, this.#cwd)
|
|
157
|
+
: this.#historyStorage.getRecent(this.#resultLimit, this.#cwd);
|
|
155
158
|
this.#selectedIndex = 0;
|
|
156
159
|
this.#resultsList.setResults(this.#results, this.#selectedIndex);
|
|
157
160
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Displays a list of string options with keyboard navigation.
|
|
4
4
|
*/
|
|
5
5
|
import {
|
|
6
|
+
type AutocompleteProvider,
|
|
6
7
|
Container,
|
|
7
8
|
Editor,
|
|
8
9
|
Markdown,
|
|
@@ -73,6 +74,12 @@ export interface HookSelectorOptions {
|
|
|
73
74
|
optionLabel: string;
|
|
74
75
|
onSubmit: (text: string) => void;
|
|
75
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Autocomplete provider for the inline custom-input editor. When present,
|
|
79
|
+
* the "Other (type your own)" editor gains the same `@` file-link and `/`
|
|
80
|
+
* completion behavior as the main prompt editor.
|
|
81
|
+
*/
|
|
82
|
+
autocompleteProvider?: AutocompleteProvider;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
class OutlinedList extends Container {
|
|
@@ -320,6 +327,7 @@ export class HookSelectorComponent extends Container {
|
|
|
320
327
|
#helpTextComponent: Text;
|
|
321
328
|
#baseHelpText: string;
|
|
322
329
|
#tui: TUI | undefined;
|
|
330
|
+
#autocompleteProvider: AutocompleteProvider | undefined;
|
|
323
331
|
constructor(
|
|
324
332
|
title: string,
|
|
325
333
|
options: string[],
|
|
@@ -342,6 +350,7 @@ export class HookSelectorComponent extends Container {
|
|
|
342
350
|
this.#outline = opts?.outline === true;
|
|
343
351
|
this.#customInput = opts?.customInput;
|
|
344
352
|
this.#tui = opts?.tui;
|
|
353
|
+
this.#autocompleteProvider = opts?.autocompleteProvider;
|
|
345
354
|
|
|
346
355
|
this.addChild(new DynamicBorder());
|
|
347
356
|
this.addChild(new Spacer(1));
|
|
@@ -491,6 +500,13 @@ export class HookSelectorComponent extends Container {
|
|
|
491
500
|
|
|
492
501
|
/** Keys while the inline custom-input editor is open below the option list. */
|
|
493
502
|
#handleInputModeKey(keyData: string, editor: Editor): void {
|
|
503
|
+
// While the autocomplete dropdown is open, every key belongs to the
|
|
504
|
+
// editor (navigate/apply/cancel the suggestion) instead of submitting
|
|
505
|
+
// or backing out of input mode.
|
|
506
|
+
if (editor.isAutocompleteOpen()) {
|
|
507
|
+
editor.handleInput(keyData);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
494
510
|
// Escape backs out to option selection instead of cancelling the dialog,
|
|
495
511
|
// so a stray Esc never throws away the question context.
|
|
496
512
|
if (matchesKey(keyData, "escape") || matchesAppInterrupt(keyData)) {
|
|
@@ -521,6 +537,9 @@ export class HookSelectorComponent extends Container {
|
|
|
521
537
|
editor.setBorderVisible(false);
|
|
522
538
|
editor.setPromptGutter("> ");
|
|
523
539
|
editor.disableSubmit = true;
|
|
540
|
+
if (this.#autocompleteProvider) {
|
|
541
|
+
editor.setAutocompleteProvider(this.#autocompleteProvider);
|
|
542
|
+
}
|
|
524
543
|
this.#inlineEditor = editor;
|
|
525
544
|
this.#inputArea.addChild(new Spacer(1));
|
|
526
545
|
this.#inputArea.addChild(editor);
|
|
@@ -154,6 +154,20 @@ interface PresetBrowseRow {
|
|
|
154
154
|
|
|
155
155
|
type PresetLandingRow = PresetGroupRow | PresetProfileRow | PresetBrowseRow;
|
|
156
156
|
|
|
157
|
+
// Stable logical identity for a preset landing row, independent of its current
|
|
158
|
+
// list position. Used to relocate the cursor after the expanded group changes so
|
|
159
|
+
// navigation does not silently overshoot the destination group header/profiles.
|
|
160
|
+
function presetRowIdentity(row: PresetLandingRow): string {
|
|
161
|
+
switch (row.kind) {
|
|
162
|
+
case "group":
|
|
163
|
+
return `group:${row.groupId}`;
|
|
164
|
+
case "profile":
|
|
165
|
+
return `profile:${row.groupId}:${row.profile.name}`;
|
|
166
|
+
case "browse":
|
|
167
|
+
return "browse";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
157
171
|
const PROFILE_ROLE_PREVIEW_ORDER: GjcModelAssignmentTargetId[] = [
|
|
158
172
|
"default",
|
|
159
173
|
"executor",
|
|
@@ -705,6 +719,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
705
719
|
return this.#getMissingProviders(profileOrProfiles).length === 0;
|
|
706
720
|
}
|
|
707
721
|
|
|
722
|
+
/**
|
|
723
|
+
* A preset group is a list of alternative presets, not an all-or-nothing
|
|
724
|
+
* bundle. Treat the group as usable when at least one member preset has all
|
|
725
|
+
* of its required providers authenticated.
|
|
726
|
+
*/
|
|
727
|
+
#isPresetGroupUsable(profiles: ModelProfileDefinition[]): boolean {
|
|
728
|
+
return profiles.some(profile => this.#isPresetAuthenticated(profile));
|
|
729
|
+
}
|
|
730
|
+
|
|
708
731
|
async #refreshProviderAuth(): Promise<void> {
|
|
709
732
|
const providers = new Set<string>();
|
|
710
733
|
for (const profiles of this.#getPresetGroups().values()) {
|
|
@@ -730,6 +753,18 @@ export class ModelSelectorComponent extends Container {
|
|
|
730
753
|
if (selected?.kind === "group") this.#expandedPresetProviderId = selected.groupId;
|
|
731
754
|
if (selected?.kind === "profile") this.#expandedPresetProviderId = selected.groupId;
|
|
732
755
|
const rows = this.#getPresetRows();
|
|
756
|
+
// Expanding/collapsing a group shifts row positions. Relocate the cursor by
|
|
757
|
+
// the selected row's logical identity so crossing a provider group boundary
|
|
758
|
+
// keeps it on the same logical row instead of overshooting into the
|
|
759
|
+
// destination group's profiles (or off the end of the list).
|
|
760
|
+
if (selected) {
|
|
761
|
+
const targetIdentity = presetRowIdentity(selected);
|
|
762
|
+
const relocated = rows.findIndex(row => presetRowIdentity(row) === targetIdentity);
|
|
763
|
+
if (relocated >= 0) {
|
|
764
|
+
this.#presetCursor = relocated;
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
733
768
|
this.#presetCursor = Math.min(this.#presetCursor, Math.max(0, rows.length - 1));
|
|
734
769
|
}
|
|
735
770
|
|
|
@@ -763,7 +798,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
763
798
|
continue;
|
|
764
799
|
}
|
|
765
800
|
if (row.kind === "group") {
|
|
766
|
-
const authenticated = this.#
|
|
801
|
+
const authenticated = this.#isPresetGroupUsable(row.profiles);
|
|
767
802
|
const mark = this.#providerAuthPending ? "…" : authenticated ? "✓" : "✗";
|
|
768
803
|
const label = `${mark} ${row.groupId}`;
|
|
769
804
|
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
@@ -1158,18 +1193,26 @@ export class ModelSelectorComponent extends Container {
|
|
|
1158
1193
|
this.#switchToModelMode();
|
|
1159
1194
|
return;
|
|
1160
1195
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1196
|
+
if (row.kind === "group") {
|
|
1197
|
+
// A group is a list of alternative presets; only surface a login hint
|
|
1198
|
+
// when none of its members are usable. A partially-usable group stays
|
|
1199
|
+
// navigable so the user can drill in and pick a usable member.
|
|
1200
|
+
if (!this.#isPresetGroupUsable(row.profiles)) {
|
|
1201
|
+
const missing = this.#getMissingProviders(row.profiles);
|
|
1202
|
+
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1203
|
+
this.#renderPresetLanding();
|
|
1204
|
+
}
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
const missing = this.#getMissingProviders(row.profile);
|
|
1163
1208
|
if (missing.length > 0) {
|
|
1164
1209
|
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1165
1210
|
this.#renderPresetLanding();
|
|
1166
1211
|
return;
|
|
1167
1212
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
this.#renderPresetLanding();
|
|
1172
|
-
}
|
|
1213
|
+
this.#previewProfileName = row.profile.name;
|
|
1214
|
+
this.#presetLoginHint = undefined;
|
|
1215
|
+
this.#renderPresetLanding();
|
|
1173
1216
|
}
|
|
1174
1217
|
|
|
1175
1218
|
#beginActionMenuOrSelect(item: ModelItem | CanonicalModelItem): void {
|
|
@@ -4,7 +4,7 @@ import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
|
4
4
|
import { formatModelOnboardingGuidance } from "../../setup/model-onboarding-guidance";
|
|
5
5
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
6
|
|
|
7
|
-
export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "api-guide";
|
|
7
|
+
export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "import-credentials" | "api-guide";
|
|
8
8
|
|
|
9
9
|
interface ProviderOnboardingOption {
|
|
10
10
|
label: string;
|
|
@@ -28,6 +28,11 @@ const PROVIDER_ONBOARDING_OPTIONS: ProviderOnboardingOption[] = [
|
|
|
28
28
|
description: "Show the /provider add and gjc setup provider commands.",
|
|
29
29
|
action: "api-guide",
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
label: "Import existing credentials",
|
|
33
|
+
description: "Detect and import Claude Code / Codex CLI logins already on this machine.",
|
|
34
|
+
action: "import-credentials",
|
|
35
|
+
},
|
|
31
36
|
];
|
|
32
37
|
|
|
33
38
|
export class ProviderOnboardingSelectorComponent extends Container {
|
|
@@ -66,7 +66,7 @@ function classifyProjectDir(pwd: string): { scratch: boolean; relative: string |
|
|
|
66
66
|
const gajaeSegment: StatusLineSegment = {
|
|
67
67
|
id: "gajae",
|
|
68
68
|
render(_ctx) {
|
|
69
|
-
return { content: theme.fg("accent", "
|
|
69
|
+
return { content: theme.fg("accent", "🦞"), visible: true };
|
|
70
70
|
},
|
|
71
71
|
};
|
|
72
72
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@gajae-code/tui";
|
|
14
14
|
import { formatDuration, Snowflake, setProjectDir } from "@gajae-code/utils";
|
|
15
15
|
import { $ } from "bun";
|
|
16
|
+
import { jobElapsedMs } from "../../async";
|
|
16
17
|
import { reset as resetCapabilities } from "../../capability";
|
|
17
18
|
import { clearClaudePluginRootsCache } from "../../discovery/helpers";
|
|
18
19
|
import { loadCustomShare } from "../../export/custom-share";
|
|
@@ -1279,7 +1280,7 @@ const BAR_WIDTH_MAX = 24;
|
|
|
1279
1280
|
const BAR_WIDTH_MIN = 4;
|
|
1280
1281
|
|
|
1281
1282
|
function renderJobLine(job: AsyncJobSnapshotItem, now: number): string {
|
|
1282
|
-
const duration = formatDuration(
|
|
1283
|
+
const duration = formatDuration(jobElapsedMs(job, now));
|
|
1283
1284
|
const status = formatJobStatus(job.status);
|
|
1284
1285
|
return `${theme.fg("dim", job.id)} ${theme.fg("dim", `[${job.type}]`)} ${status} ${theme.fg("dim", `(${duration})`)}`;
|
|
1285
1286
|
}
|
|
@@ -1538,7 +1539,7 @@ function resolveColumnWidth(count: number, available: number, trailing: number):
|
|
|
1538
1539
|
return ideal;
|
|
1539
1540
|
}
|
|
1540
1541
|
|
|
1541
|
-
function renderUsageReports(
|
|
1542
|
+
export function renderUsageReports(
|
|
1542
1543
|
reports: UsageReport[],
|
|
1543
1544
|
uiTheme: typeof theme,
|
|
1544
1545
|
nowMs: number,
|
|
@@ -1592,17 +1593,35 @@ function renderUsageReports(
|
|
|
1592
1593
|
|
|
1593
1594
|
lines.push(uiTheme.bold(uiTheme.fg("accent", providerName)));
|
|
1594
1595
|
|
|
1596
|
+
// Stable account column order shared across every window group, so each
|
|
1597
|
+
// account keeps the same column in the 5h / 7d / ... rows. Rank accounts
|
|
1598
|
+
// by total usage across their windows (a per-account value identical for
|
|
1599
|
+
// every group), tie-broken by label for determinism.
|
|
1600
|
+
const rankedAccounts = providerReports
|
|
1601
|
+
.map(report => {
|
|
1602
|
+
const [firstLimit] = report.limits;
|
|
1603
|
+
return {
|
|
1604
|
+
report,
|
|
1605
|
+
total: report.limits.reduce((sum, limit) => sum + (resolveFraction(limit) ?? 0), 0),
|
|
1606
|
+
label: firstLimit ? formatAccountLabel(firstLimit, report, 0) : formatUnlimitedReportLabel(report, 0),
|
|
1607
|
+
};
|
|
1608
|
+
})
|
|
1609
|
+
.sort((a, b) => (a.total !== b.total ? b.total - a.total : a.label.localeCompare(b.label)));
|
|
1610
|
+
const accountRank = new Map<UsageReport, number>();
|
|
1611
|
+
rankedAccounts.forEach((entry, rank) => {
|
|
1612
|
+
accountRank.set(entry.report, rank);
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1595
1615
|
const renderableGroups = Array.from(limitGroups.values()).map(group => {
|
|
1596
1616
|
const entries = group.limits.map((limit, index) => ({
|
|
1597
1617
|
limit,
|
|
1598
1618
|
report: group.reports[index],
|
|
1599
|
-
fraction: resolveFraction(limit),
|
|
1600
1619
|
index,
|
|
1601
1620
|
}));
|
|
1602
1621
|
entries.sort((a, b) => {
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
if (
|
|
1622
|
+
const aRank = accountRank.get(a.report) ?? Number.MAX_SAFE_INTEGER;
|
|
1623
|
+
const bRank = accountRank.get(b.report) ?? Number.MAX_SAFE_INTEGER;
|
|
1624
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
1606
1625
|
return a.index - b.index;
|
|
1607
1626
|
});
|
|
1608
1627
|
const sortedLimits = entries.map(entry => entry.limit);
|
|
@@ -645,6 +645,9 @@ export class ExtensionUiController {
|
|
|
645
645
|
timeout: dialogOptions?.timeout,
|
|
646
646
|
onTimeout: dialogOptions?.onTimeout,
|
|
647
647
|
tui: this.ctx.ui,
|
|
648
|
+
// Share the main prompt editor's autocomplete provider so the
|
|
649
|
+
// inline "Other (type your own)" editor supports `@` file links.
|
|
650
|
+
autocompleteProvider: dialogOptions?.customInput ? this.ctx.editor.getAutocompleteProvider() : undefined,
|
|
648
651
|
outline: dialogOptions?.outline,
|
|
649
652
|
wrapFocused: dialogOptions?.wrapFocused,
|
|
650
653
|
scrollTitleRows,
|
|
@@ -32,13 +32,20 @@ import {
|
|
|
32
32
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
33
33
|
import { type SessionInfo, SessionManager } from "../../session/session-manager";
|
|
34
34
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
35
|
+
import { discoverExternalCredentials, formatDiscoverySummary, importCredentials } from "../../setup/credential-import";
|
|
35
36
|
import {
|
|
36
37
|
MODEL_ONBOARDING_API_PROVIDER_COMMAND,
|
|
37
38
|
MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
|
|
38
39
|
MODEL_ONBOARDING_SETUP_COMMAND,
|
|
39
40
|
} from "../../setup/model-onboarding-guidance";
|
|
40
41
|
import { addApiCompatibleProvider, formatProviderSetupResult } from "../../setup/provider-onboarding";
|
|
41
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
isConfigurableSearchProviderId,
|
|
44
|
+
isSearchProviderPreference,
|
|
45
|
+
setPreferredImageProvider,
|
|
46
|
+
setPreferredSearchProvider,
|
|
47
|
+
setSearchFallbackProviders,
|
|
48
|
+
} from "../../tools";
|
|
42
49
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
43
50
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
44
51
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
@@ -70,6 +77,7 @@ const CALLBACK_SERVER_PROVIDERS = new Set<string>([
|
|
|
70
77
|
"google-gemini-cli",
|
|
71
78
|
"google-antigravity",
|
|
72
79
|
"xai",
|
|
80
|
+
"grok-build",
|
|
73
81
|
]);
|
|
74
82
|
|
|
75
83
|
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
|
@@ -127,6 +135,8 @@ export class SelectorController {
|
|
|
127
135
|
this.showCustomProviderWizard();
|
|
128
136
|
} else if (action === "oauth-login") {
|
|
129
137
|
void this.showOAuthSelector("login");
|
|
138
|
+
} else if (action === "import-credentials") {
|
|
139
|
+
void this.#handleCredentialImport();
|
|
130
140
|
} else {
|
|
131
141
|
this.ctx.showStatus(formatProviderOnboardingCommandGuide());
|
|
132
142
|
}
|
|
@@ -140,6 +150,69 @@ export class SelectorController {
|
|
|
140
150
|
});
|
|
141
151
|
}
|
|
142
152
|
|
|
153
|
+
async #handleCredentialImport(): Promise<void> {
|
|
154
|
+
this.ctx.showStatus("Scanning for existing Claude Code / Codex CLI credentials…");
|
|
155
|
+
const result = await discoverExternalCredentials();
|
|
156
|
+
const summaryLines = formatDiscoverySummary(result);
|
|
157
|
+
|
|
158
|
+
if (result.importable.length === 0) {
|
|
159
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
160
|
+
for (const line of summaryLines) {
|
|
161
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", line), 1, 0));
|
|
162
|
+
}
|
|
163
|
+
this.ctx.chatContainer.addChild(
|
|
164
|
+
new Text(
|
|
165
|
+
theme.fg(
|
|
166
|
+
"warning",
|
|
167
|
+
"No importable Claude/Codex credentials found. Use /login or add a custom provider.",
|
|
168
|
+
),
|
|
169
|
+
1,
|
|
170
|
+
0,
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
this.ctx.ui.requestRender();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const confirmed = await this.ctx.showHookConfirm(
|
|
178
|
+
`Import ${result.importable.length} credential(s)?`,
|
|
179
|
+
summaryLines.join("\n"),
|
|
180
|
+
);
|
|
181
|
+
if (!confirmed) {
|
|
182
|
+
this.ctx.showStatus("Credential import cancelled.");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const summary = await importCredentials(result.importable, (provider, credential) =>
|
|
187
|
+
this.ctx.session.modelRegistry.authStorage.upsertCredential(provider, credential),
|
|
188
|
+
);
|
|
189
|
+
await this.ctx.session.modelRegistry.refresh();
|
|
190
|
+
|
|
191
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
192
|
+
for (const credential of summary.imported) {
|
|
193
|
+
this.ctx.chatContainer.addChild(
|
|
194
|
+
new Text(
|
|
195
|
+
theme.fg("success", `${theme.status.success} Imported ${credential.provider} (${credential.source})`),
|
|
196
|
+
1,
|
|
197
|
+
0,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
for (const failure of summary.failed) {
|
|
202
|
+
this.ctx.chatContainer.addChild(
|
|
203
|
+
new Text(
|
|
204
|
+
theme.fg("error", `${theme.status.error} Failed ${failure.credential.provider}: ${failure.error}`),
|
|
205
|
+
1,
|
|
206
|
+
0,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (summary.imported.length > 0) {
|
|
211
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0));
|
|
212
|
+
}
|
|
213
|
+
this.ctx.ui.requestRender();
|
|
214
|
+
}
|
|
215
|
+
|
|
143
216
|
showCustomProviderWizard(): void {
|
|
144
217
|
this.showSelector(done => {
|
|
145
218
|
let wizard: CustomProviderWizardComponent;
|
|
@@ -506,6 +579,13 @@ export class SelectorController {
|
|
|
506
579
|
setPreferredSearchProvider(value);
|
|
507
580
|
}
|
|
508
581
|
break;
|
|
582
|
+
case "web_search.fallback":
|
|
583
|
+
if (Array.isArray(value)) {
|
|
584
|
+
setSearchFallbackProviders(
|
|
585
|
+
value.filter(item => typeof item === "string" && isConfigurableSearchProviderId(item)),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
break;
|
|
509
589
|
case "providers.image":
|
|
510
590
|
if (
|
|
511
591
|
value === "auto" ||
|
|
@@ -283,7 +283,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
283
283
|
}
|
|
284
284
|
autoCompactionEscapeHandler?: () => void;
|
|
285
285
|
retryEscapeHandler?: () => void;
|
|
286
|
-
retryCountdownTimer?:
|
|
286
|
+
retryCountdownTimer?: NodeJS.Timeout;
|
|
287
287
|
unsubscribe?: () => void;
|
|
288
288
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
289
289
|
optimisticUserMessageSignature: string | undefined = undefined;
|
|
@@ -709,6 +709,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
709
709
|
if (this.#pendingSubmittedInput) return;
|
|
710
710
|
if (this.editor.getText().trim().length > 0) return;
|
|
711
711
|
if ((this.pendingImages?.length ?? 0) > 0) return;
|
|
712
|
+
// Never fire an autonomous continuation prompt() while the session is
|
|
713
|
+
// busy. A wedged/orphaned subagent turn can leave isStreaming stuck true;
|
|
714
|
+
// firing prompt() here throws AgentBusyError, which submitInteractiveInput
|
|
715
|
+
// surfaces as a red "Error: Agent is already processing…" and then loops
|
|
716
|
+
// back to getUserInput(), re-arming this timer — an infinite error spam.
|
|
717
|
+
// Re-arm and only fire once the session returns to idle.
|
|
718
|
+
if (this.session.isStreaming || this.session.isCompacting) {
|
|
719
|
+
this.#scheduleGoalContinuation();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
712
722
|
const latestState = this.session.getGoalModeState();
|
|
713
723
|
if (!latestState?.enabled || latestState.goal.status !== "active") return;
|
|
714
724
|
this.#goalContinuationTurnInFlight = true;
|