@gajae-code/coding-agent 0.11.7 → 0.11.9
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 +73 -1
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/keybindings.d.ts +33 -3
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/settings-schema.d.ts +15 -2
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +44 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
- package/dist/types/main.d.ts +8 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +3 -2
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +25 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +15 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +22 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +5 -1
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/image-gen.d.ts +47 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/cli/args.ts +2 -92
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +50 -0
- package/src/commands/deep-interview.ts +9 -4
- package/src/commands/sdk.ts +2 -2
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +157 -24
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +2 -3
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +17 -2
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +80 -165
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
- package/src/gjc-runtime/deep-interview-draft.ts +842 -0
- package/src/gjc-runtime/deep-interview-payload.ts +262 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
- package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
- package/src/gjc-runtime/deep-interview-state.ts +916 -4
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +78 -4
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/state-writer.ts +166 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +5 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
- package/src/gjc-runtime/workflow-manifest.ts +191 -5
- package/src/internal-urls/docs-index.generated.ts +8 -6
- package/src/internal-urls/local-protocol.ts +33 -16
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +25 -10
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/components/model-selector.ts +42 -8
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +20 -4
- package/src/modes/components/welcome.ts +32 -20
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +7 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +46 -54
- package/src/modes/interactive-mode.ts +70 -36
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +7 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/runtime-mcp/json-rpc.ts +7 -4
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/transports/http.ts +13 -6
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-control.ts +20 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +240 -65
- package/src/sdk/bus/lifecycle-commands.ts +47 -3
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +27 -2
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +853 -112
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +47 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +2 -0
- package/src/sdk/startup-capability.ts +9 -1
- package/src/session/agent-session.ts +138 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +998 -133
- package/src/session/internal/managed-session-storage.ts +268 -203
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +406 -360
- package/src/session/session-storage.ts +151 -11
- package/src/skill-state/active-state.ts +10 -0
- package/src/task/executor.ts +13 -5
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -88
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/image-gen.ts +178 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/url-guard.ts +3 -1
- package/src/web/scrapers/docs-rs.ts +30 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Component, padding, TERMINAL, truncateToWidth, visibleWidth } from "@gajae-code/tui";
|
|
2
2
|
import { APP_NAME } from "@gajae-code/utils";
|
|
3
3
|
import { formatBuildLabel } from "../../build-metadata";
|
|
4
|
+
import { formatKeyHint, type KeyDisplayContext } from "../../config/keybindings";
|
|
4
5
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
5
6
|
|
|
6
7
|
export interface RecentSession {
|
|
@@ -21,26 +22,28 @@ export interface WelcomeComponentOptions {
|
|
|
21
22
|
changelogMarkdown?: string;
|
|
22
23
|
collapseChangelog?: boolean;
|
|
23
24
|
buildLabel?: string;
|
|
25
|
+
keyDisplayContext?: KeyDisplayContext;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS = 9;
|
|
27
29
|
const DEFAULT_WHATS_NEW_ROWS = 3;
|
|
28
30
|
const MAX_WHATS_NEW_ROWS = 12;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
];
|
|
31
|
+
|
|
32
|
+
function flowKeyItems(context: KeyDisplayContext): ReadonlyArray<{ key: string; label: string }> {
|
|
33
|
+
const newlineKey = context.platform === "win32" ? "alt+enter" : "ctrl+j";
|
|
34
|
+
return [
|
|
35
|
+
{ key: "/", label: "commands" },
|
|
36
|
+
{ key: "#", label: "actions" },
|
|
37
|
+
{ key: "!", label: "shell" },
|
|
38
|
+
{ key: "$", label: "python" },
|
|
39
|
+
{ key: "?", label: "keymap" },
|
|
40
|
+
{ key: "ctrl+l", label: "model" },
|
|
41
|
+
{ key: "shift+tab", label: "reasoning" },
|
|
42
|
+
{ key: "tab", label: "complete" },
|
|
43
|
+
{ key: newlineKey, label: "newline" },
|
|
44
|
+
{ key: "ctrl+c", label: "clear" },
|
|
45
|
+
];
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* GJC-native launch surface with compact command affordances, project
|
|
@@ -325,7 +328,8 @@ export class WelcomeComponent implements Component {
|
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
#flowKeyItemText(item: { key: string; label: string }): string {
|
|
328
|
-
|
|
331
|
+
const context = this.options.keyDisplayContext ?? { platform: process.platform };
|
|
332
|
+
return `${theme.fg("dim", formatKeyHint(item.key, context))}${theme.fg("muted", ` ${item.label}`)}`;
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
#flowKeyRows(width: number): string[] {
|
|
@@ -333,7 +337,7 @@ export class WelcomeComponent implements Component {
|
|
|
333
337
|
const separator = ` ${theme.fg("dim", "·")} `;
|
|
334
338
|
const rows: string[] = [];
|
|
335
339
|
let current = "";
|
|
336
|
-
for (const item of
|
|
340
|
+
for (const item of flowKeyItems(this.options.keyDisplayContext ?? { platform: process.platform })) {
|
|
337
341
|
const segment = this.#flowKeyItemText(item);
|
|
338
342
|
const next = current ? `${current}${separator}${segment}` : segment;
|
|
339
343
|
if (current && visibleWidth(next) > contentWidth) {
|
|
@@ -352,7 +356,7 @@ export class WelcomeComponent implements Component {
|
|
|
352
356
|
const rowLimit = Math.max(1, Math.floor(maxRows));
|
|
353
357
|
if (rows.length <= rowLimit) return rows;
|
|
354
358
|
if (rowLimit === 1) {
|
|
355
|
-
const firstItem =
|
|
359
|
+
const firstItem = flowKeyItems(this.options.keyDisplayContext ?? { platform: process.platform })[0];
|
|
356
360
|
const firstSegment = firstItem ? this.#flowKeyItemText(firstItem) : theme.fg("dim", "keys");
|
|
357
361
|
return [this.#fitToWidth(` ${firstSegment} ${theme.fg("dim", "· … ")}${theme.bold("/help")}`, width)];
|
|
358
362
|
}
|
|
@@ -661,8 +665,16 @@ function gradientLogo(lines: readonly string[], phase = 0, shine?: ShineConfig):
|
|
|
661
665
|
|
|
662
666
|
/** Total length of the intro animation. */
|
|
663
667
|
const INTRO_MS = 3000;
|
|
664
|
-
/**
|
|
665
|
-
|
|
668
|
+
/** Resolve the intro cadence without making tests mutate global process state. */
|
|
669
|
+
export function resolveWelcomeIntroTickMs(
|
|
670
|
+
platform: NodeJS.Platform = process.platform,
|
|
671
|
+
tmux = process.env.TMUX,
|
|
672
|
+
): number {
|
|
673
|
+
return platform === "win32" && tmux ? 100 : 33;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** Render at 30fps directly, but cap native Windows multiplexers at 10fps to avoid ConPTY output backpressure. */
|
|
677
|
+
const INTRO_TICK_MS = resolveWelcomeIntroTickMs();
|
|
666
678
|
/** Number of full gradient rotations the sweep performs before settling. */
|
|
667
679
|
const INTRO_SWEEPS = 2.5;
|
|
668
680
|
/** Number of times the shine highlight crosses the diagonal across the intro. */
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
type UsageLimit,
|
|
11
11
|
type UsageReport,
|
|
12
12
|
} from "@gajae-code/ai";
|
|
13
|
-
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@gajae-code/tui";
|
|
13
|
+
import { type Keybinding, Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@gajae-code/tui";
|
|
14
14
|
import { formatDuration, Snowflake, setProjectDir } from "@gajae-code/utils";
|
|
15
|
-
import { $ } from "bun";
|
|
16
15
|
import { resolveAppendOnlyMode } from "../../append-only-mode";
|
|
17
16
|
import { jobElapsedMs } from "../../async";
|
|
18
17
|
import { reset as resetCapabilities } from "../../capability";
|
|
18
|
+
import type { KeybindingsManager } from "../../config/keybindings";
|
|
19
19
|
import { clearClaudePluginRootsCache } from "../../discovery/helpers";
|
|
20
20
|
import { loadCustomShare } from "../../export/custom-share";
|
|
21
21
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
@@ -36,7 +36,7 @@ import { EvalExecutionComponent } from "../../modes/components/eval-execution";
|
|
|
36
36
|
import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
37
37
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
38
38
|
import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
|
|
39
|
-
import { buildHotkeysMarkdown } from "../../modes/utils/hotkeys-markdown";
|
|
39
|
+
import { buildHotkeysMarkdown, formatHotkeyMarkdownCode } from "../../modes/utils/hotkeys-markdown";
|
|
40
40
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
41
41
|
import type { AsyncJobSnapshotItem } from "../../session/agent-session";
|
|
42
42
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
@@ -61,6 +61,39 @@ function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown:
|
|
|
61
61
|
ctx.ui.requestRender();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export function buildHelpMarkdown(keybindings: Pick<KeybindingsManager, "getAccessibleDisplayString">): string {
|
|
65
|
+
const displayKey = (action: Keybinding): string => keybindings.getAccessibleDisplayString(action) || "Disabled";
|
|
66
|
+
const sessionNewKey = formatHotkeyMarkdownCode(displayKey("app.session.new"));
|
|
67
|
+
const selectModelKey = formatHotkeyMarkdownCode(displayKey("app.model.select"));
|
|
68
|
+
const autocompleteNavigationKeys = formatHotkeyMarkdownCode(
|
|
69
|
+
`${displayKey("tui.select.up")}/${displayKey("tui.select.down")}`,
|
|
70
|
+
false,
|
|
71
|
+
);
|
|
72
|
+
const autocompleteConfirmKeys = formatHotkeyMarkdownCode(
|
|
73
|
+
`${displayKey("tui.input.submit")}/${displayKey("tui.input.tab")}`,
|
|
74
|
+
false,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
"**Beginner actions**",
|
|
79
|
+
"",
|
|
80
|
+
"| Action | How |",
|
|
81
|
+
"|---|---|",
|
|
82
|
+
`| Start a fresh session | ${sessionNewKey} or \`/new\` |`,
|
|
83
|
+
"| Resume another session | `/resume` |",
|
|
84
|
+
"| Show session details | `/session info` |",
|
|
85
|
+
"| Delete current session transcript/artifacts | `/session delete` |",
|
|
86
|
+
`| Select a model | \`/model\` or ${selectModelKey} |`,
|
|
87
|
+
"| Show all shortcuts | `?` on an empty prompt or `/hotkeys` |",
|
|
88
|
+
"",
|
|
89
|
+
"**Finding commands**",
|
|
90
|
+
"",
|
|
91
|
+
`- Type \`/\` to browse slash commands, then use ${autocompleteNavigationKeys} and ${autocompleteConfirmKeys}.`,
|
|
92
|
+
"- Type `#` to browse prompt actions like starting a session, pasting an image, or moving the cursor.",
|
|
93
|
+
"- Type `!` for shell commands and `$` for Python snippets.",
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
64
97
|
export class CommandController {
|
|
65
98
|
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
66
99
|
|
|
@@ -120,21 +153,34 @@ export class CommandController {
|
|
|
120
153
|
}
|
|
121
154
|
|
|
122
155
|
async handleShareCommand(): Promise<void> {
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
let tempDir: string | undefined;
|
|
157
|
+
try {
|
|
158
|
+
let tmpFile: string;
|
|
125
159
|
try {
|
|
126
|
-
await fs.
|
|
127
|
-
|
|
128
|
-
|
|
160
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-share-"));
|
|
161
|
+
if (process.platform !== "win32") await fs.chmod(tempDir, 0o700);
|
|
162
|
+
tmpFile = path.join(tempDir, "session.html");
|
|
163
|
+
const file = await fs.open(tmpFile, "wx", 0o600);
|
|
164
|
+
await file.close();
|
|
165
|
+
await this.ctx.session.exportToHtml(tmpFile);
|
|
166
|
+
if (process.platform !== "win32") await fs.chmod(tmpFile, 0o600);
|
|
167
|
+
} catch (error: unknown) {
|
|
168
|
+
this.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await this.#shareExport(tmpFile);
|
|
172
|
+
} finally {
|
|
173
|
+
if (tempDir) {
|
|
174
|
+
try {
|
|
175
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
176
|
+
} catch {
|
|
177
|
+
// Ignore cleanup errors
|
|
178
|
+
}
|
|
129
179
|
}
|
|
130
|
-
};
|
|
131
|
-
try {
|
|
132
|
-
await this.ctx.session.exportToHtml(tmpFile);
|
|
133
|
-
} catch (error: unknown) {
|
|
134
|
-
this.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
135
|
-
return;
|
|
136
180
|
}
|
|
181
|
+
}
|
|
137
182
|
|
|
183
|
+
async #shareExport(tmpFile: string): Promise<void> {
|
|
138
184
|
try {
|
|
139
185
|
const customShare = await loadCustomShare();
|
|
140
186
|
if (customShare) {
|
|
@@ -149,7 +195,6 @@ export class CommandController {
|
|
|
149
195
|
this.ctx.editorContainer.clear();
|
|
150
196
|
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
151
197
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
152
|
-
await cleanupTempFile();
|
|
153
198
|
};
|
|
154
199
|
|
|
155
200
|
try {
|
|
@@ -176,20 +221,22 @@ export class CommandController {
|
|
|
176
221
|
}
|
|
177
222
|
}
|
|
178
223
|
} catch (err) {
|
|
179
|
-
await cleanupTempFile();
|
|
180
224
|
this.ctx.showError(err instanceof Error ? err.message : String(err));
|
|
181
225
|
return;
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
try {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
229
|
+
const authProcess = Bun.spawn(["gh", "auth", "status"], {
|
|
230
|
+
env: process.env,
|
|
231
|
+
stdin: "ignore",
|
|
232
|
+
stdout: "ignore",
|
|
233
|
+
stderr: "ignore",
|
|
234
|
+
});
|
|
235
|
+
if ((await authProcess.exited) !== 0) {
|
|
188
236
|
this.ctx.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
189
237
|
return;
|
|
190
238
|
}
|
|
191
239
|
} catch {
|
|
192
|
-
await cleanupTempFile();
|
|
193
240
|
this.ctx.showError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/");
|
|
194
241
|
return;
|
|
195
242
|
}
|
|
@@ -205,27 +252,40 @@ export class CommandController {
|
|
|
205
252
|
this.ctx.editorContainer.clear();
|
|
206
253
|
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
207
254
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
208
|
-
await cleanupTempFile();
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
loader.onAbort = () => {
|
|
212
|
-
void restoreEditor();
|
|
213
|
-
this.ctx.showStatus("Share cancelled");
|
|
214
255
|
};
|
|
215
256
|
|
|
257
|
+
let cancellationRequested = false;
|
|
216
258
|
try {
|
|
217
|
-
const
|
|
218
|
-
|
|
259
|
+
const gistProcess = Bun.spawn(["gh", "gist", "create", "--public=false", tmpFile], {
|
|
260
|
+
env: process.env,
|
|
261
|
+
stdin: "ignore",
|
|
262
|
+
stdout: "pipe",
|
|
263
|
+
stderr: "pipe",
|
|
264
|
+
signal: loader.signal,
|
|
265
|
+
});
|
|
266
|
+
loader.onAbort = () => {
|
|
267
|
+
cancellationRequested = gistProcess.exitCode === null;
|
|
268
|
+
};
|
|
269
|
+
const stdoutPromise = new Response(gistProcess.stdout).text();
|
|
270
|
+
const stderrPromise = new Response(gistProcess.stderr).text();
|
|
271
|
+
const exitCode = await gistProcess.exited;
|
|
272
|
+
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
273
|
+
|
|
274
|
+
if (cancellationRequested) {
|
|
275
|
+
await restoreEditor();
|
|
276
|
+
this.ctx.showStatus("Share cancelled");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
219
279
|
|
|
220
280
|
await restoreEditor();
|
|
221
281
|
|
|
222
|
-
if (
|
|
223
|
-
const errorMsg =
|
|
282
|
+
if (exitCode !== 0) {
|
|
283
|
+
const errorMsg = stderr.trim() || "Unknown error";
|
|
224
284
|
this.ctx.showError(`Failed to create gist: ${errorMsg}`);
|
|
225
285
|
return;
|
|
226
286
|
}
|
|
227
287
|
|
|
228
|
-
const gistUrl =
|
|
288
|
+
const gistUrl = stdout.trim();
|
|
229
289
|
const gistId = gistUrl.split("/").pop();
|
|
230
290
|
if (!gistId) {
|
|
231
291
|
this.ctx.showError("Failed to parse gist ID from gh output");
|
|
@@ -236,10 +296,12 @@ export class CommandController {
|
|
|
236
296
|
this.ctx.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
|
|
237
297
|
this.openInBrowser(previewUrl);
|
|
238
298
|
} catch (error: unknown) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.ctx.
|
|
299
|
+
await restoreEditor();
|
|
300
|
+
if (cancellationRequested) {
|
|
301
|
+
this.ctx.showStatus("Share cancelled");
|
|
302
|
+
return;
|
|
242
303
|
}
|
|
304
|
+
this.ctx.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
243
305
|
}
|
|
244
306
|
}
|
|
245
307
|
|
|
@@ -577,26 +639,7 @@ export class CommandController {
|
|
|
577
639
|
}
|
|
578
640
|
|
|
579
641
|
handleHelpCommand(): void {
|
|
580
|
-
|
|
581
|
-
const markdown = [
|
|
582
|
-
"**Beginner actions**",
|
|
583
|
-
"",
|
|
584
|
-
"| Action | How |",
|
|
585
|
-
"|---|---|",
|
|
586
|
-
`| Start a fresh session | \`${sessionNewKey}\` or \`/new\` |`,
|
|
587
|
-
"| Resume another session | `/resume` |",
|
|
588
|
-
"| Show session details | `/session info` |",
|
|
589
|
-
"| Delete current session transcript/artifacts | `/session delete` |",
|
|
590
|
-
"| Select a model | `/model` or `Ctrl+L` |",
|
|
591
|
-
"| Show all shortcuts | `?` on an empty prompt or `/hotkeys` |",
|
|
592
|
-
"",
|
|
593
|
-
"**Finding commands**",
|
|
594
|
-
"",
|
|
595
|
-
"- Type `/` to browse slash commands, then use arrows and Enter/Tab.",
|
|
596
|
-
"- Type `#` to browse prompt actions like starting a session, pasting an image, or moving the cursor.",
|
|
597
|
-
"- Type `!` for shell commands and `$` for Python snippets.",
|
|
598
|
-
].join("\n");
|
|
599
|
-
showMarkdownPanel(this.ctx, "GJC Help", markdown);
|
|
642
|
+
showMarkdownPanel(this.ctx, "GJC Help", buildHelpMarkdown(this.ctx.keybindings));
|
|
600
643
|
}
|
|
601
644
|
|
|
602
645
|
handleToolsCommand(): void {
|
|
@@ -242,6 +242,7 @@ export class EventController {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
245
|
+
this.ctx.promptSuggestion?.onAgentStart();
|
|
245
246
|
this.#lastIntent = undefined;
|
|
246
247
|
this.#readToolCallArgs.clear();
|
|
247
248
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -836,6 +837,7 @@ export class EventController {
|
|
|
836
837
|
this.ctx.ui.requestRender();
|
|
837
838
|
this.#scheduleIdleCompaction();
|
|
838
839
|
this.sendCompletionNotification();
|
|
840
|
+
this.ctx.promptSuggestion?.onAgentEnd();
|
|
839
841
|
}
|
|
840
842
|
|
|
841
843
|
async #handleAutoCompactionStart(
|
|
@@ -847,6 +849,10 @@ export class EventController {
|
|
|
847
849
|
this.ctx.editor.onEscape = () => {
|
|
848
850
|
this.ctx.session.abortCompaction();
|
|
849
851
|
};
|
|
852
|
+
if (this.ctx.loadingAnimation) {
|
|
853
|
+
this.ctx.loadingAnimation.stop();
|
|
854
|
+
this.ctx.loadingAnimation = undefined;
|
|
855
|
+
}
|
|
850
856
|
this.ctx.statusContainer.clear();
|
|
851
857
|
const reasonText =
|
|
852
858
|
event.reason === "overflow" ? "Context overflow detected, " : event.reason === "idle" ? "Idle " : "";
|
|
@@ -939,6 +945,10 @@ export class EventController {
|
|
|
939
945
|
this.ctx.session.abortRetry();
|
|
940
946
|
}
|
|
941
947
|
};
|
|
948
|
+
if (this.ctx.loadingAnimation) {
|
|
949
|
+
this.ctx.loadingAnimation.stop();
|
|
950
|
+
this.ctx.loadingAnimation = undefined;
|
|
951
|
+
}
|
|
942
952
|
this.ctx.statusContainer.clear();
|
|
943
953
|
// Stop any prior retry loader/timer before installing a new one.
|
|
944
954
|
this.ctx.retryLoader?.stop();
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { type AgentMessage, ThinkingLevel } from "@gajae-code/agent-core";
|
|
4
4
|
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@gajae-code/tui";
|
|
5
|
-
import { $
|
|
5
|
+
import { $pickenv, logger, sanitizeText } from "@gajae-code/utils";
|
|
6
6
|
import { type AppKeybinding, KEYBINDINGS } from "../../config/keybindings";
|
|
7
7
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
8
8
|
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/gjc-plugins";
|
|
@@ -725,8 +725,12 @@ export class InputController {
|
|
|
725
725
|
});
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
+
// Tab on an empty composer accepts the pending ghost-text prompt suggestion.
|
|
729
|
+
this.ctx.editor.onTab = (text: string) => this.ctx.promptSuggestion?.tryAcceptOnTab(text) === true;
|
|
730
|
+
|
|
728
731
|
this.ctx.editor.onChange = (text: string) => {
|
|
729
732
|
this.#resetEscapeGestures();
|
|
733
|
+
this.ctx.promptSuggestion?.notifyEditorChanged(text);
|
|
730
734
|
const wasBashMode = this.ctx.isBashMode;
|
|
731
735
|
const wasBashNoContext = this.ctx.isBashNoContext;
|
|
732
736
|
const wasPythonMode = this.ctx.isPythonMode;
|
|
@@ -930,7 +934,7 @@ export class InputController {
|
|
|
930
934
|
|
|
931
935
|
// Generate session title on first message
|
|
932
936
|
const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
|
|
933
|
-
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$
|
|
937
|
+
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$pickenv("GJC_NO_TITLE", "PI_NO_TITLE")) {
|
|
934
938
|
const registry = this.ctx.session.modelRegistry;
|
|
935
939
|
generateSessionTitle(
|
|
936
940
|
text,
|
|
@@ -1789,6 +1793,7 @@ export class InputController {
|
|
|
1789
1793
|
moveCursorToMessageStart: () => this.ctx.editor.moveToMessageStart(),
|
|
1790
1794
|
moveCursorToLineStart: () => this.ctx.editor.moveToLineStart(),
|
|
1791
1795
|
moveCursorToLineEnd: () => this.ctx.editor.moveToLineEnd(),
|
|
1796
|
+
getPromptSuggestion: () => this.ctx.promptSuggestion?.current ?? null,
|
|
1792
1797
|
});
|
|
1793
1798
|
}
|
|
1794
1799
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Handles /mcp subcommands for managing MCP servers.
|
|
5
5
|
*/
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
+
import { resolveMCPOAuthResourceOrigin, resolveMCPOAuthTokenEndpoint } from "@gajae-code/ai";
|
|
7
8
|
import { Spacer, Text } from "@gajae-code/tui";
|
|
8
9
|
import { getMCPConfigPath, getProjectDir } from "@gajae-code/utils";
|
|
9
10
|
import type { SourceMeta } from "../../capability/types";
|
|
@@ -61,6 +62,7 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string)
|
|
|
61
62
|
*/
|
|
62
63
|
interface OAuthFlowResult {
|
|
63
64
|
credentialId: string;
|
|
65
|
+
tokenEndpoint: string;
|
|
64
66
|
clientId?: string;
|
|
65
67
|
clientSecret?: string;
|
|
66
68
|
}
|
|
@@ -423,6 +425,7 @@ export class MCPCommandController {
|
|
|
423
425
|
try {
|
|
424
426
|
const oauthClientSecret = finalConfig.oauth?.clientSecret ?? "";
|
|
425
427
|
const oauthResult = await this.#handleOAuthFlow(
|
|
428
|
+
finalConfig.url,
|
|
426
429
|
oauth.authorizationUrl,
|
|
427
430
|
oauth.tokenUrl,
|
|
428
431
|
oauth.clientId ?? finalConfig.oauth?.clientId ?? "",
|
|
@@ -439,7 +442,7 @@ export class MCPCommandController {
|
|
|
439
442
|
auth: {
|
|
440
443
|
type: "oauth",
|
|
441
444
|
credentialId: oauthResult.credentialId,
|
|
442
|
-
tokenUrl:
|
|
445
|
+
tokenUrl: oauthResult.tokenEndpoint,
|
|
443
446
|
clientId: persistedClientId,
|
|
444
447
|
clientSecret: persistedClientSecret,
|
|
445
448
|
},
|
|
@@ -480,8 +483,15 @@ export class MCPCommandController {
|
|
|
480
483
|
done();
|
|
481
484
|
this.#handleWizardCancel();
|
|
482
485
|
},
|
|
483
|
-
async (
|
|
484
|
-
|
|
486
|
+
async (
|
|
487
|
+
endpointUrl: string,
|
|
488
|
+
authUrl: string,
|
|
489
|
+
tokenUrl: string,
|
|
490
|
+
clientId: string,
|
|
491
|
+
clientSecret: string,
|
|
492
|
+
scopes: string,
|
|
493
|
+
) => {
|
|
494
|
+
return await this.#handleOAuthFlow(endpointUrl, authUrl, tokenUrl, clientId, clientSecret, scopes);
|
|
485
495
|
},
|
|
486
496
|
async (config: MCPServerConfig) => {
|
|
487
497
|
return await this.#handleTestConnection(config);
|
|
@@ -503,6 +513,7 @@ export class MCPCommandController {
|
|
|
503
513
|
* Handle OAuth authentication flow for MCP server
|
|
504
514
|
*/
|
|
505
515
|
async #handleOAuthFlow(
|
|
516
|
+
endpointUrl: string,
|
|
506
517
|
authUrl: string,
|
|
507
518
|
tokenUrl: string,
|
|
508
519
|
clientId: string,
|
|
@@ -514,11 +525,13 @@ export class MCPCommandController {
|
|
|
514
525
|
): Promise<OAuthFlowResult> {
|
|
515
526
|
const authStorage = this.ctx.session.modelRegistry.authStorage;
|
|
516
527
|
let parsedAuthUrl: URL;
|
|
528
|
+
const resourceOrigin = resolveMCPOAuthResourceOrigin(endpointUrl);
|
|
529
|
+
const tokenEndpoint = resolveMCPOAuthTokenEndpoint(tokenUrl);
|
|
530
|
+
if (!resourceOrigin || !tokenEndpoint) throw new Error("Invalid MCP OAuth configuration.");
|
|
517
531
|
|
|
518
532
|
// Validate OAuth URLs
|
|
519
533
|
try {
|
|
520
534
|
parsedAuthUrl = new URL(authUrl);
|
|
521
|
-
new URL(tokenUrl);
|
|
522
535
|
} catch (_error) {
|
|
523
536
|
throw new Error(
|
|
524
537
|
`Invalid OAuth URLs. Please check:\n Authorization URL: ${authUrl}\n Token URL: ${tokenUrl}`,
|
|
@@ -533,7 +546,7 @@ export class MCPCommandController {
|
|
|
533
546
|
const flow = new MCPOAuthFlow(
|
|
534
547
|
{
|
|
535
548
|
authorizationUrl: authUrl,
|
|
536
|
-
tokenUrl:
|
|
549
|
+
tokenUrl: tokenEndpoint,
|
|
537
550
|
clientId: resolvedClientId,
|
|
538
551
|
clientSecret: resolvedClientSecret,
|
|
539
552
|
scopes: scopes || undefined,
|
|
@@ -618,6 +631,10 @@ export class MCPCommandController {
|
|
|
618
631
|
const oauthCredential: OAuthCredential = {
|
|
619
632
|
type: "oauth",
|
|
620
633
|
...credentials,
|
|
634
|
+
mcpBinding: {
|
|
635
|
+
resourceOrigin,
|
|
636
|
+
tokenEndpoint,
|
|
637
|
+
},
|
|
621
638
|
};
|
|
622
639
|
|
|
623
640
|
// Store under a synthetic provider name
|
|
@@ -625,6 +642,7 @@ export class MCPCommandController {
|
|
|
625
642
|
|
|
626
643
|
return {
|
|
627
644
|
credentialId,
|
|
645
|
+
tokenEndpoint,
|
|
628
646
|
clientId: flow.resolvedClientId,
|
|
629
647
|
clientSecret: flow.registeredClientSecret,
|
|
630
648
|
};
|
|
@@ -1328,6 +1346,10 @@ export class MCPCommandController {
|
|
|
1328
1346
|
this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
|
|
1329
1347
|
return;
|
|
1330
1348
|
}
|
|
1349
|
+
if (found.config.type !== "http" && found.config.type !== "sse") {
|
|
1350
|
+
this.ctx.showError(`Server "${name}" does not use a remote MCP transport.`);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1331
1353
|
|
|
1332
1354
|
const currentAuth = (found.config as MCPServerConfig & { auth?: MCPAuthConfig }).auth;
|
|
1333
1355
|
if (currentAuth?.type === "oauth") {
|
|
@@ -1341,6 +1363,7 @@ export class MCPCommandController {
|
|
|
1341
1363
|
this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
|
|
1342
1364
|
|
|
1343
1365
|
const oauthResult = await this.#handleOAuthFlow(
|
|
1366
|
+
found.config.url,
|
|
1344
1367
|
oauth.authorizationUrl,
|
|
1345
1368
|
oauth.tokenUrl,
|
|
1346
1369
|
oauth.clientId ?? found.config.oauth?.clientId ?? "",
|
|
@@ -1359,7 +1382,7 @@ export class MCPCommandController {
|
|
|
1359
1382
|
auth: {
|
|
1360
1383
|
type: "oauth",
|
|
1361
1384
|
credentialId: oauthResult.credentialId,
|
|
1362
|
-
tokenUrl:
|
|
1385
|
+
tokenUrl: oauthResult.tokenEndpoint,
|
|
1363
1386
|
clientId: persistedClientId,
|
|
1364
1387
|
clientSecret: persistedClientSecret,
|
|
1365
1388
|
},
|