@gajae-code/coding-agent 0.7.11 → 0.8.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 +47 -0
- package/README.md +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +25 -2
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line.d.ts +8 -0
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/notifications/telegram-daemon.d.ts +3 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +10 -10
- package/src/capability/mcp.ts +2 -0
- package/src/config/mcp-schema.json +5 -1
- package/src/config/settings-schema.ts +18 -2
- package/src/coordinator-mcp/server.ts +39 -4
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/skills.ts +30 -2
- package/src/gjc-runtime/session-state-sidecar.ts +167 -24
- package/src/gjc-runtime/team-runtime.ts +56 -51
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +1 -34
- package/src/modes/components/custom-editor.ts +18 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/model-selector.ts +58 -2
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +13 -0
- package/src/modes/components/status-line.ts +215 -86
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +171 -35
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/event-controller.ts +4 -0
- package/src/modes/controllers/input-controller.ts +158 -38
- package/src/modes/controllers/selector-controller.ts +25 -28
- package/src/modes/interactive-mode.ts +20 -23
- package/src/modes/prompt-action-autocomplete.ts +17 -7
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +9 -3
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/types.ts +1 -0
- package/src/notifications/index.ts +20 -1
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/telegram-daemon.ts +14 -0
- package/src/prompts/system/system-prompt.md +15 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +21 -1
- package/src/session/session-manager.ts +58 -18
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +15 -6
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/dist/types/discovery/vscode.d.ts +0 -1
- package/src/discovery/vscode.ts +0 -105
|
@@ -15,7 +15,7 @@ import { formatModelProfileDisplayLabel, recommendModelProfileForProvider } from
|
|
|
15
15
|
import { GJC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
16
16
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
17
17
|
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
18
|
-
import { settings } from "../../config/settings";
|
|
18
|
+
import { type Settings, settings } from "../../config/settings";
|
|
19
19
|
import { DebugSelectorComponent } from "../../debug";
|
|
20
20
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
21
21
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
86
86
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
87
87
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
88
|
+
import type { StatusLineSettings } from "../components/status-line";
|
|
88
89
|
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
89
90
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
90
91
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
@@ -108,6 +109,24 @@ function isThemePreviewSuperseded(result: { success: boolean; error?: string }):
|
|
|
108
109
|
return !result.success && result.error?.includes("superseded by a newer request") === true;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Snapshot the persisted status-line settings that the status-line component
|
|
114
|
+
* cares about. Preview, cancel-restore, and commit paths all share this so the
|
|
115
|
+
* previewed row count (and every other field) can never drift out of sync.
|
|
116
|
+
*/
|
|
117
|
+
export function buildStatusLineSettings(settingsInstance: Settings): StatusLineSettings {
|
|
118
|
+
return {
|
|
119
|
+
preset: settingsInstance.get("statusLine.preset"),
|
|
120
|
+
leftSegments: settingsInstance.get("statusLine.leftSegments"),
|
|
121
|
+
rightSegments: settingsInstance.get("statusLine.rightSegments"),
|
|
122
|
+
separator: settingsInstance.get("statusLine.separator"),
|
|
123
|
+
showHookStatus: settingsInstance.get("statusLine.showHookStatus"),
|
|
124
|
+
sessionAccent: settingsInstance.get("statusLine.sessionAccent"),
|
|
125
|
+
maxRows: settingsInstance.get("statusLine.maxRows"),
|
|
126
|
+
segmentOptions: settingsInstance.get("statusLine.segmentOptions"),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
function formatProviderOnboardingCommandGuide(): string {
|
|
112
131
|
return [
|
|
113
132
|
"Provider preset setup:",
|
|
@@ -442,13 +461,7 @@ export class SelectorController {
|
|
|
442
461
|
onStatusLinePreview: previewSettings => {
|
|
443
462
|
// Update status line with preview settings
|
|
444
463
|
this.ctx.statusLine.updateSettings({
|
|
445
|
-
|
|
446
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
447
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
448
|
-
separator: settings.get("statusLine.separator"),
|
|
449
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
450
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
451
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
464
|
+
...buildStatusLineSettings(settings),
|
|
452
465
|
...previewSettings,
|
|
453
466
|
});
|
|
454
467
|
this.ctx.updateEditorTopBorder();
|
|
@@ -458,7 +471,7 @@ export class SelectorController {
|
|
|
458
471
|
// Return the rendered status line for inline preview
|
|
459
472
|
const availableWidth =
|
|
460
473
|
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
461
|
-
return this.ctx.statusLine.
|
|
474
|
+
return this.ctx.statusLine.getPreviewContent(availableWidth);
|
|
462
475
|
},
|
|
463
476
|
onPluginsChanged: () => {
|
|
464
477
|
this.ctx.ui.requestRender();
|
|
@@ -466,15 +479,7 @@ export class SelectorController {
|
|
|
466
479
|
onCancel: () => {
|
|
467
480
|
done();
|
|
468
481
|
// Restore status line to saved settings
|
|
469
|
-
this.ctx.statusLine.updateSettings(
|
|
470
|
-
preset: settings.get("statusLine.preset"),
|
|
471
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
472
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
473
|
-
separator: settings.get("statusLine.separator"),
|
|
474
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
475
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
476
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
477
|
-
});
|
|
482
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
478
483
|
this.ctx.updateEditorTopBorder();
|
|
479
484
|
this.ctx.ui.requestRender();
|
|
480
485
|
},
|
|
@@ -718,6 +723,7 @@ export class SelectorController {
|
|
|
718
723
|
case "statusLineShowHooks":
|
|
719
724
|
case "statusLine.showHookStatus":
|
|
720
725
|
case "statusLine.sessionAccent":
|
|
726
|
+
case "statusLine.maxRows":
|
|
721
727
|
case "statusLine.leftSegments":
|
|
722
728
|
case "statusLine.rightSegments":
|
|
723
729
|
case "statusLine.segmentOptions":
|
|
@@ -732,16 +738,7 @@ export class SelectorController {
|
|
|
732
738
|
case "statusLineGitShowUntracked":
|
|
733
739
|
case "statusLineTimeFormat":
|
|
734
740
|
case "statusLineTimeShowSeconds": {
|
|
735
|
-
|
|
736
|
-
preset: settings.get("statusLine.preset"),
|
|
737
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
738
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
739
|
-
separator: settings.get("statusLine.separator"),
|
|
740
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
741
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
742
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
743
|
-
};
|
|
744
|
-
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
741
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
745
742
|
this.ctx.updateEditorTopBorder();
|
|
746
743
|
this.ctx.ui.requestRender();
|
|
747
744
|
break;
|
|
@@ -321,6 +321,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
321
321
|
}
|
|
322
322
|
autoCompactionEscapeHandler?: () => void;
|
|
323
323
|
retryEscapeHandler?: () => void;
|
|
324
|
+
retryEscapePrimed = false;
|
|
324
325
|
retryCountdownTimer?: NodeJS.Timeout;
|
|
325
326
|
unsubscribe?: () => void;
|
|
326
327
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
@@ -422,7 +423,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
422
423
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
423
424
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
424
425
|
this.editor.onAutocompleteCancel = () => {
|
|
425
|
-
this.ui.requestRender(
|
|
426
|
+
this.ui.requestRender();
|
|
426
427
|
};
|
|
427
428
|
this.editor.onAutocompleteUpdate = () => {
|
|
428
429
|
this.ui.requestRender();
|
|
@@ -432,7 +433,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
432
433
|
this.#syncEditorMaxHeight();
|
|
433
434
|
this.updateEditorChrome();
|
|
434
435
|
this.editor.invalidate();
|
|
435
|
-
this.ui.
|
|
436
|
+
this.ui.requestResizeRender();
|
|
436
437
|
};
|
|
437
438
|
process.stdout.on("resize", this.#resizeHandler);
|
|
438
439
|
try {
|
|
@@ -530,6 +531,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
530
531
|
}
|
|
531
532
|
|
|
532
533
|
if (!startupQuiet) {
|
|
534
|
+
const getWelcomeReservedBottomRows = (width: number): number =>
|
|
535
|
+
[
|
|
536
|
+
this.statusLine,
|
|
537
|
+
this.hookWidgetContainerAbove,
|
|
538
|
+
this.editorContainer,
|
|
539
|
+
this.hookWidgetContainerBelow,
|
|
540
|
+
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
541
|
+
|
|
533
542
|
// Add welcome header
|
|
534
543
|
this.#welcomeComponent = new WelcomeComponent(
|
|
535
544
|
this.#version,
|
|
@@ -538,30 +547,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
538
547
|
recentSessions,
|
|
539
548
|
this.#getWelcomeLspServers(),
|
|
540
549
|
welcomeLogoMode,
|
|
550
|
+
{
|
|
551
|
+
getViewportRows: () => this.ui.terminal.rows,
|
|
552
|
+
getReservedBottomRows: getWelcomeReservedBottomRows,
|
|
553
|
+
changelogMarkdown: this.#changelogMarkdown,
|
|
554
|
+
collapseChangelog: settings.get("collapseChangelog"),
|
|
555
|
+
},
|
|
541
556
|
);
|
|
542
557
|
|
|
543
|
-
// Setup UI layout
|
|
544
|
-
this.ui.addChild(new Spacer(1));
|
|
545
558
|
this.ui.addChild(this.#welcomeComponent);
|
|
546
|
-
this.ui.addChild(new Spacer(1));
|
|
547
559
|
this.#welcomeComponent.playIntro(() => this.ui.requestRender());
|
|
548
|
-
|
|
549
|
-
// Add changelog if provided
|
|
550
|
-
if (this.#changelogMarkdown) {
|
|
551
|
-
this.ui.addChild(new DynamicBorder());
|
|
552
|
-
if (settings.get("collapseChangelog")) {
|
|
553
|
-
const versionMatch = this.#changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
554
|
-
const latestVersion = versionMatch ? versionMatch[1] : this.#version;
|
|
555
|
-
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
556
|
-
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
557
|
-
} else {
|
|
558
|
-
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
559
|
-
this.ui.addChild(new Spacer(1));
|
|
560
|
-
this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
561
|
-
this.ui.addChild(new Spacer(1));
|
|
562
|
-
}
|
|
563
|
-
this.ui.addChild(new DynamicBorder());
|
|
564
|
-
}
|
|
565
560
|
}
|
|
566
561
|
|
|
567
562
|
this.ui.addChild(this.chatContainer);
|
|
@@ -1963,6 +1958,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1963
1958
|
this.loadingAnimation.stop();
|
|
1964
1959
|
this.loadingAnimation = undefined;
|
|
1965
1960
|
}
|
|
1961
|
+
this.#welcomeComponent?.dispose();
|
|
1962
|
+
this.#welcomeComponent = undefined;
|
|
1966
1963
|
this.#cleanupMicAnimation();
|
|
1967
1964
|
this.#cancelGoalContinuation();
|
|
1968
1965
|
if (this.#sttController) {
|
|
@@ -2076,7 +2073,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2076
2073
|
nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
2077
2074
|
nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible"));
|
|
2078
2075
|
nextEditor.onAutocompleteCancel = () => {
|
|
2079
|
-
this.ui.requestRender(
|
|
2076
|
+
this.ui.requestRender();
|
|
2080
2077
|
};
|
|
2081
2078
|
nextEditor.onAutocompleteUpdate = () => {
|
|
2082
2079
|
this.ui.requestRender();
|
|
@@ -177,6 +177,9 @@ function getSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
|
177
177
|
return token;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
function isLeadingSlashToken(textBeforeCursor: string, slashPrefix: string): boolean {
|
|
181
|
+
return textBeforeCursor.trimStart() === slashPrefix;
|
|
182
|
+
}
|
|
180
183
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
181
184
|
#baseProvider: CombinedAutocompleteProvider;
|
|
182
185
|
#actions: PromptActionDefinition[];
|
|
@@ -223,10 +226,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
223
226
|
|
|
224
227
|
const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
|
|
225
228
|
if (slashPrefix) {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const isLeading = isLeadingSlashToken(textBeforeCursor, slashPrefix);
|
|
230
|
+
const baseSuggestions = isLeading
|
|
231
|
+
? withoutSkillCommandSuggestions(await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol))
|
|
232
|
+
: null;
|
|
233
|
+
const skillCommandSuggestions =
|
|
234
|
+
isLeading || slashPrefix.startsWith("/skill")
|
|
235
|
+
? this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: isLeading })
|
|
236
|
+
: null;
|
|
230
237
|
return sortSlashCommandSuggestions(
|
|
231
238
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
232
239
|
this.#commands,
|
|
@@ -300,7 +307,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
300
307
|
const baseSuggestions = withoutSkillCommandSuggestions(
|
|
301
308
|
this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null,
|
|
302
309
|
);
|
|
303
|
-
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
|
|
310
|
+
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, { includeEmpty: false });
|
|
304
311
|
return sortSlashCommandSuggestions(
|
|
305
312
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
306
313
|
this.#commands,
|
|
@@ -311,11 +318,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
311
318
|
return tryEmojiInlineReplace(textBeforeCursor);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
#getSkillCommandSuggestions(
|
|
321
|
+
#getSkillCommandSuggestions(
|
|
322
|
+
textBeforeCursor: string,
|
|
323
|
+
options: { includeEmpty: boolean },
|
|
324
|
+
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
315
325
|
const prefix = getSlashTokenPrefix(textBeforeCursor);
|
|
316
326
|
if (!prefix) return null;
|
|
317
327
|
const query = prefix.slice(1).toLowerCase();
|
|
318
|
-
if (query.length === 0) return null;
|
|
328
|
+
if (query.length === 0 && !options.includeEmpty) return null;
|
|
319
329
|
const normalizedQuery = query.startsWith("skill-") ? `skill:${query.slice("skill-".length)}` : query;
|
|
320
330
|
const exactNonSkillCommand = this.#commands.some(
|
|
321
331
|
command => command.name === query && !command.name.startsWith("skill:"),
|
|
@@ -131,7 +131,12 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
131
131
|
properties: {
|
|
132
132
|
selected: selectedBase,
|
|
133
133
|
other: { type: "boolean", description: "set true to provide a free-text answer in `custom`" },
|
|
134
|
-
custom: {
|
|
134
|
+
custom: {
|
|
135
|
+
type: "string",
|
|
136
|
+
minLength: 1,
|
|
137
|
+
pattern: "\\S",
|
|
138
|
+
description: "free-text answer; required when `other` is true",
|
|
139
|
+
},
|
|
135
140
|
action: {
|
|
136
141
|
type: "string",
|
|
137
142
|
enum: ["answer", "clarify"],
|
|
@@ -140,6 +145,7 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
140
145
|
question: {
|
|
141
146
|
type: "string",
|
|
142
147
|
minLength: 1,
|
|
148
|
+
pattern: "\\S",
|
|
143
149
|
description: "clarification question; required when action is `clarify`",
|
|
144
150
|
},
|
|
145
151
|
},
|
|
@@ -156,7 +162,7 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
156
162
|
properties: {
|
|
157
163
|
selected: selectedWithOther,
|
|
158
164
|
other: { const: true },
|
|
159
|
-
custom: { type: "string", minLength: 1 },
|
|
165
|
+
custom: { type: "string", minLength: 1, pattern: "\\S" },
|
|
160
166
|
action: { const: "answer" },
|
|
161
167
|
},
|
|
162
168
|
required: ["selected", "other", "custom"],
|
|
@@ -166,7 +172,7 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
|
|
|
166
172
|
type: "object",
|
|
167
173
|
properties: {
|
|
168
174
|
action: { const: "clarify" },
|
|
169
|
-
question: { type: "string", minLength: 1 },
|
|
175
|
+
question: { type: "string", minLength: 1, pattern: "\\S" },
|
|
170
176
|
},
|
|
171
177
|
required: ["action", "question"],
|
|
172
178
|
additionalProperties: false,
|
|
@@ -25,6 +25,7 @@ const SUPPORTED_KEYWORDS = new Set<keyof RpcJsonSchema>([
|
|
|
25
25
|
"items",
|
|
26
26
|
"minLength",
|
|
27
27
|
"maxLength",
|
|
28
|
+
"pattern",
|
|
28
29
|
"minItems",
|
|
29
30
|
"maxItems",
|
|
30
31
|
"uniqueItems",
|
|
@@ -107,6 +108,17 @@ function walkSchema(schema: RpcJsonSchema, depth: number, path: string): void {
|
|
|
107
108
|
throw new WorkflowGateSchemaError(`${meta} at ${path} must be a string`);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
111
|
+
if (schema.pattern !== undefined) {
|
|
112
|
+
if (typeof schema.pattern !== "string") {
|
|
113
|
+
throw new WorkflowGateSchemaError(`pattern at ${path} must be a string`);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
new RegExp(schema.pattern);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
119
|
+
throw new WorkflowGateSchemaError(`pattern at ${path} must be a valid regular expression: ${message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
110
122
|
for (const limit of ["minLength", "maxLength", "minItems", "maxItems"] as const) {
|
|
111
123
|
const v = schema[limit];
|
|
112
124
|
if (v !== undefined && (typeof v !== "number" || !Number.isInteger(v) || v < 0)) {
|
|
@@ -242,6 +254,14 @@ function validateValue(schema: RpcJsonSchema, value: unknown, path: string, erro
|
|
|
242
254
|
expected: schema.maxLength,
|
|
243
255
|
});
|
|
244
256
|
}
|
|
257
|
+
if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
|
|
258
|
+
errors.push({
|
|
259
|
+
path,
|
|
260
|
+
keyword: "pattern",
|
|
261
|
+
message: `does not match pattern ${schema.pattern}`,
|
|
262
|
+
expected: schema.pattern,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
245
265
|
}
|
|
246
266
|
if (typeof value === "number") {
|
|
247
267
|
if (schema.minimum !== undefined && value < schema.minimum) {
|
package/src/modes/types.ts
CHANGED
|
@@ -110,6 +110,7 @@ export interface InteractiveModeContext {
|
|
|
110
110
|
retryLoader: Loader | undefined;
|
|
111
111
|
autoCompactionEscapeHandler?: () => void;
|
|
112
112
|
retryEscapeHandler?: () => void;
|
|
113
|
+
retryEscapePrimed: boolean;
|
|
113
114
|
retryCountdownTimer?: NodeJS.Timeout;
|
|
114
115
|
unsubscribe?: () => void;
|
|
115
116
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
@@ -26,7 +26,7 @@ import * as path from "node:path";
|
|
|
26
26
|
import { promisify } from "node:util";
|
|
27
27
|
import type { ImageContent, TextContent } from "@gajae-code/ai";
|
|
28
28
|
import { NotificationServer } from "@gajae-code/natives";
|
|
29
|
-
import { logger } from "@gajae-code/utils";
|
|
29
|
+
import { logger, postmortem } from "@gajae-code/utils";
|
|
30
30
|
import { Settings } from "../config/settings";
|
|
31
31
|
import type { ExtensionCommandContext, ExtensionContext, ExtensionFactory } from "../extensibility/extensions";
|
|
32
32
|
import { registerAskAnswerSource } from "../tools/ask-answer-registry";
|
|
@@ -348,6 +348,8 @@ interface SessionRuntime {
|
|
|
348
348
|
/** Assistant text already flushed before an ask this turn (turn-scoped dedupe
|
|
349
349
|
* so turn_end does not re-emit the pre-ask lead-in). Reset each turn. */
|
|
350
350
|
preAskFlushedText?: string;
|
|
351
|
+
/** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
|
|
352
|
+
cancelPostmortemCleanup: () => void;
|
|
351
353
|
}
|
|
352
354
|
|
|
353
355
|
interface ResolvedSettings {
|
|
@@ -500,6 +502,7 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
500
502
|
if (!rt) return false;
|
|
501
503
|
runtimes.delete(id);
|
|
502
504
|
try {
|
|
505
|
+
rt.cancelPostmortemCleanup();
|
|
503
506
|
rt.disposeAnswerSource();
|
|
504
507
|
rt.disposeFileSink();
|
|
505
508
|
} catch {}
|
|
@@ -680,6 +683,7 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
680
683
|
pendingInteractive,
|
|
681
684
|
disposeAnswerSource,
|
|
682
685
|
disposeFileSink,
|
|
686
|
+
cancelPostmortemCleanup: () => {},
|
|
683
687
|
redact,
|
|
684
688
|
verbosity,
|
|
685
689
|
sessionTag: tag,
|
|
@@ -687,6 +691,15 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
687
691
|
pendingInbound: new Set<number>(),
|
|
688
692
|
};
|
|
689
693
|
runtimes.set(id, runtime);
|
|
694
|
+
// A native terminal close (SIGHUP), SIGTERM, Ctrl+C exit, or fatal error
|
|
695
|
+
// skips AgentSession.dispose(), so the `session_shutdown` extension event
|
|
696
|
+
// never fires and the daemon-side topic would be orphaned. postmortem
|
|
697
|
+
// awaits registered cleanups on those paths, so send the graceful
|
|
698
|
+
// `session_closed` frame from there too. stopSession() cancels this
|
|
699
|
+
// registration on every other teardown path, so it never double-fires.
|
|
700
|
+
runtime.cancelPostmortemCleanup = postmortem.register(`notifications-session-closed:${id}`, async () => {
|
|
701
|
+
await stopSession(id);
|
|
702
|
+
});
|
|
690
703
|
logger.info(`notifications: serving session ${id} at ${endpoint.url} (unattended=${unattended})`);
|
|
691
704
|
|
|
692
705
|
if (settingsAvailable && settings && isGloballyConfigured(cfg)) {
|
|
@@ -848,9 +861,15 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
848
861
|
// Re-bind the interactive ask answer source: the ask tool resolves the
|
|
849
862
|
// source by the current session id, which just changed.
|
|
850
863
|
try {
|
|
864
|
+
rt.cancelPostmortemCleanup();
|
|
851
865
|
rt.disposeAnswerSource();
|
|
852
866
|
rt.disposeFileSink();
|
|
853
867
|
} catch {}
|
|
868
|
+
// Follow the id change so a later process teardown closes the re-keyed
|
|
869
|
+
// session (the old closure captured the retired id).
|
|
870
|
+
rt.cancelPostmortemCleanup = postmortem.register(`notifications-session-closed:${newId}`, async () => {
|
|
871
|
+
await stopSession(newId);
|
|
872
|
+
});
|
|
854
873
|
rt.disposeAnswerSource = registerInteractiveAnswerSource(
|
|
855
874
|
newId,
|
|
856
875
|
rt.server,
|
|
@@ -238,6 +238,7 @@ export function daemonResumeSession(env: NodeJS.ProcessEnv = process.env, opts:
|
|
|
238
238
|
// restart, so an unknown or ambiguous prefix fails closed instead of
|
|
239
239
|
// blindly spawning `gjc --resume <prefix>` against a non-authoritative id.
|
|
240
240
|
let resumeId = target.sessionIdOrPrefix;
|
|
241
|
+
let resumeCwd = target.path;
|
|
241
242
|
if (opts.sessionsRoot) {
|
|
242
243
|
const saved = listRecentSessions({ sessionsRoot: opts.sessionsRoot, limit: 1000 });
|
|
243
244
|
const prefixed = saved.filter(
|
|
@@ -249,11 +250,19 @@ export function daemonResumeSession(env: NodeJS.ProcessEnv = process.env, opts:
|
|
|
249
250
|
if (resolved.length > 1) {
|
|
250
251
|
return { ambiguous: resolved.map(s => ({ sessionId: s.sessionId, path: s.path })) };
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
+
const selected = resolved[0]!;
|
|
254
|
+
resumeId = selected.sessionId;
|
|
255
|
+
resumeCwd = selected.path;
|
|
256
|
+
}
|
|
257
|
+
const resolvedResumeCwd = resumeCwd ? path.resolve(resumeCwd) : undefined;
|
|
258
|
+
const resumeCwdStat = resolvedResumeCwd ? fs.statSync(resolvedResumeCwd, { throwIfNoEntry: false }) : undefined;
|
|
259
|
+
if (!resumeCwdStat?.isDirectory()) {
|
|
260
|
+
throw new Error(`gjc_lifecycle_resume_cwd_unavailable: ${resolvedResumeCwd ?? "(missing)"}`);
|
|
253
261
|
}
|
|
254
262
|
const tmux = resolveGjcTmuxCommand(env);
|
|
255
263
|
const name = tmuxSessionNameFor(resumeId);
|
|
256
|
-
const
|
|
264
|
+
const cwdPrefix = resolvedResumeCwd ? `cd ${shellQuote(resolvedResumeCwd)} && ` : "";
|
|
265
|
+
const command = `${cwdPrefix}exec env GJC_TMUX_LAUNCHED=1 GJC_NOTIFICATIONS=1 gjc --resume ${shellQuote(resumeId)}`;
|
|
257
266
|
const r = Bun.spawnSync([tmux, "new-session", "-d", "-s", name, "sh", "-c", command], {
|
|
258
267
|
stdout: "pipe",
|
|
259
268
|
stderr: "pipe",
|
|
@@ -261,7 +270,7 @@ export function daemonResumeSession(env: NodeJS.ProcessEnv = process.env, opts:
|
|
|
261
270
|
});
|
|
262
271
|
if (r.exitCode !== 0) throw new Error(r.stderr.toString().trim() || "gjc_lifecycle_resume_failed");
|
|
263
272
|
const tgt = buildGjcTmuxExactOptionTarget(name);
|
|
264
|
-
for (const cmd of buildGjcTmuxProfileCommands(tgt, env, { sessionId: resumeId })) {
|
|
273
|
+
for (const cmd of buildGjcTmuxProfileCommands(tgt, env, { sessionId: resumeId, project: resolvedResumeCwd })) {
|
|
265
274
|
Bun.spawnSync([tmux, ...cmd.args], { stdout: "pipe", stderr: "pipe", env });
|
|
266
275
|
}
|
|
267
276
|
return {
|
|
@@ -142,6 +142,9 @@ function splitTelegramPlainText(text: string, max = TELEGRAM_MESSAGE_LIMIT): str
|
|
|
142
142
|
if (out) chunks.push(out);
|
|
143
143
|
return chunks;
|
|
144
144
|
}
|
|
145
|
+
function endpointGenerationKey(url: string, token: string): string {
|
|
146
|
+
return `${url}\0${token}`;
|
|
147
|
+
}
|
|
145
148
|
|
|
146
149
|
/**
|
|
147
150
|
* Whether `err` is a transient network failure worth retrying. Telegram API
|
|
@@ -772,6 +775,7 @@ export interface TelegramDaemonOptions {
|
|
|
772
775
|
interface SessionSocket {
|
|
773
776
|
sessionId: string;
|
|
774
777
|
token: string;
|
|
778
|
+
endpointKey: string;
|
|
775
779
|
ws: WebSocket;
|
|
776
780
|
pending: Map<string, { sessionId: string; actionId: string }>;
|
|
777
781
|
/** True once the server advertised the `client_ping_pong` capability. */
|
|
@@ -808,6 +812,8 @@ export class TelegramNotificationDaemon {
|
|
|
808
812
|
private readonly topicOwnerByIdentity = new Map<string, string>();
|
|
809
813
|
/** Non-identity frames held until identity creates the correct thread. */
|
|
810
814
|
private readonly pendingThreadedFrames = new Map<string, PendingThreadedFrame[]>();
|
|
815
|
+
/** Endpoint generation tombstones for sessions that already sent session_closed. */
|
|
816
|
+
private readonly closedEndpointKeys = new Map<string, string>();
|
|
811
817
|
/** True once the daemon has nudged the user to enable Threaded Mode. */
|
|
812
818
|
private threadedFallbackNoticeSent = false;
|
|
813
819
|
/** Sessions whose identity header was already sent flat (Threaded Mode off). */
|
|
@@ -1223,7 +1229,9 @@ export class TelegramNotificationDaemon {
|
|
|
1223
1229
|
matches: msg => msg.type === "session_closed",
|
|
1224
1230
|
handle: async session => {
|
|
1225
1231
|
this.busy.delete(session.sessionId);
|
|
1232
|
+
this.closedEndpointKeys.set(session.sessionId, session.endpointKey);
|
|
1226
1233
|
await this.deleteTopic(session.sessionId);
|
|
1234
|
+
this.dropSession(session, "session_closed");
|
|
1227
1235
|
},
|
|
1228
1236
|
});
|
|
1229
1237
|
}
|
|
@@ -1260,6 +1268,9 @@ export class TelegramNotificationDaemon {
|
|
|
1260
1268
|
// would chase a dead, token-bearing record forever.
|
|
1261
1269
|
const pidAlive = this.opts.pidAlive ?? defaultPidAlive;
|
|
1262
1270
|
if (endpoint.stale || (endpoint.pid !== undefined && !pidAlive(endpoint.pid))) continue;
|
|
1271
|
+
const endpointKey = endpointGenerationKey(endpoint.url, endpoint.token);
|
|
1272
|
+
if (this.closedEndpointKeys.get(sessionId) === endpointKey) continue;
|
|
1273
|
+
this.closedEndpointKeys.delete(sessionId);
|
|
1263
1274
|
this.connectSession(sessionId, endpoint.url, endpoint.token);
|
|
1264
1275
|
} catch {}
|
|
1265
1276
|
}
|
|
@@ -1269,9 +1280,12 @@ export class TelegramNotificationDaemon {
|
|
|
1269
1280
|
connectSession(sessionId: string, url: string, token: string): void {
|
|
1270
1281
|
const WS = this.opts.WebSocketImpl ?? WebSocket;
|
|
1271
1282
|
const ws = new WS(`${url}/?token=${encodeURIComponent(token)}`);
|
|
1283
|
+
const endpointKey = endpointGenerationKey(url, token);
|
|
1284
|
+
this.closedEndpointKeys.delete(sessionId);
|
|
1272
1285
|
const session: SessionSocket = {
|
|
1273
1286
|
sessionId,
|
|
1274
1287
|
token,
|
|
1288
|
+
endpointKey,
|
|
1275
1289
|
ws,
|
|
1276
1290
|
pending: new Map(),
|
|
1277
1291
|
capable: false,
|
|
@@ -61,10 +61,12 @@ Use for read-only plan critique. It approves only when execution can proceed wit
|
|
|
61
61
|
<routing>
|
|
62
62
|
- Clear, low-risk implementation request → implement directly with focused verification.
|
|
63
63
|
- Do not invoke `deep-interview`, `ralplan`, `ultragoal`, `team`, or role agents for simple clear implementation requests; direct tools and the default launch path are enough.
|
|
64
|
+
- The runtime records a `workflow-intent-diff` CustomEntry for direct-path traceability; it does not participate in LLM context and is not a reason to slow down direct execution.
|
|
64
65
|
- When a task is clear, bounded, and low-risk, the default action is to make the smallest correct change and verify it, not to interview, plan, open a durable ledger, or delegate.
|
|
65
66
|
- Small verification needs do not make a task a planning workflow. Escalate only for real ambiguity, non-trivial architecture/sequence risk, durable multi-goal tracking, or useful coordinated workers.
|
|
67
|
+
- Root-cause phase schema is active only for contradiction, regression, or high-risk transition work; otherwise keep ordinary verification and do not add root-cause ceremony.
|
|
66
68
|
- Vague requirements → use `deep-interview` before planning or execution.
|
|
67
|
-
- Clear requirements but non-trivial architecture/sequence risk → use `ralplan` and stop at pending approval.
|
|
69
|
+
- Clear requirements but non-trivial architecture/sequence risk → use `ralplan --deliberate` and stop at pending approval.
|
|
68
70
|
- Durable goal ledger needed → use `ultragoal`; if no approved plan exists, run `ralplan` first.
|
|
69
71
|
- Approved work benefits from coordinated persistent workers → use `team`.
|
|
70
72
|
- Large enough implementation work → delegate bounded slices to `executor` through the task/sub-agent tool when it improves quality or throughput.
|
|
@@ -139,6 +141,18 @@ Use tools whenever they materially improve correctness, completeness, or groundi
|
|
|
139
141
|
</inventory>
|
|
140
142
|
{{/if}}
|
|
141
143
|
|
|
144
|
+
{{#if toolDiscoveryActive}}
|
|
145
|
+
<tool-discovery>
|
|
146
|
+
Only essential tools are loaded up front; other tools are hidden to save context and are available on demand. Call `{{toolRefs.search_tool_bm25}}` with a short natural-language query to find and activate the tool you need, then call it. Prefer discovering a purpose-built tool over forcing the task onto a resident one.
|
|
147
|
+
{{#if discoverableTools.length}}
|
|
148
|
+
Discoverable tools:
|
|
149
|
+
{{#each discoverableTools}}
|
|
150
|
+
- `{{name}}`{{#if summary}} — {{summary}}{{/if}}
|
|
151
|
+
{{/each}}
|
|
152
|
+
{{/if}}
|
|
153
|
+
</tool-discovery>
|
|
154
|
+
{{/if}}
|
|
155
|
+
|
|
142
156
|
<inputs>
|
|
143
157
|
- Keep tool inputs concise where possible.
|
|
144
158
|
- For `path` or path-like fields, prefer relative paths.
|
|
@@ -237,6 +237,34 @@ export async function listMCPServers(filePath: string): Promise<string[]> {
|
|
|
237
237
|
return Object.keys(config.mcpServers ?? {});
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Set a server's autoload flag (connect automatically at session startup).
|
|
242
|
+
*
|
|
243
|
+
* Autoload defaults to true, so turning it on removes the key to keep the
|
|
244
|
+
* config file free of redundant fields; turning it off writes `autoload: false`.
|
|
245
|
+
*
|
|
246
|
+
* @throws Error if the server doesn't exist
|
|
247
|
+
*/
|
|
248
|
+
export async function setServerAutoload(filePath: string, name: string, autoload: boolean): Promise<void> {
|
|
249
|
+
const existing = await readMCPConfigFile(filePath);
|
|
250
|
+
const server = existing.mcpServers?.[name];
|
|
251
|
+
if (!server) {
|
|
252
|
+
throw new Error(`Server "${name}" not found in ${filePath}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { autoload: _removed, ...rest } = server;
|
|
256
|
+
const updatedServer = (autoload ? rest : { ...rest, autoload: false }) as MCPServerConfig;
|
|
257
|
+
const updated: MCPConfigFile = {
|
|
258
|
+
...existing,
|
|
259
|
+
mcpServers: {
|
|
260
|
+
...existing.mcpServers,
|
|
261
|
+
[name]: updatedServer,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await writeMCPConfigFile(filePath, updated);
|
|
266
|
+
}
|
|
267
|
+
|
|
240
268
|
/**
|
|
241
269
|
* Read the disabled servers list from a config file.
|
|
242
270
|
*/
|
|
@@ -20,6 +20,8 @@ export interface LoadMCPConfigsOptions {
|
|
|
20
20
|
filterExa?: boolean;
|
|
21
21
|
/** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
|
|
22
22
|
filterBrowser?: boolean;
|
|
23
|
+
/** Only include servers eligible for startup connection, i.e. autoload !== false (default: false) */
|
|
24
|
+
autoloadOnly?: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/** Result of loading MCP configs */
|
|
@@ -40,6 +42,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
40
42
|
const transport = server.transport ?? (server.command ? "stdio" : server.url ? "http" : "stdio");
|
|
41
43
|
const shared = {
|
|
42
44
|
enabled: server.enabled,
|
|
45
|
+
autoload: server.autoload,
|
|
43
46
|
timeout: server.timeout,
|
|
44
47
|
auth: server.auth,
|
|
45
48
|
oauth: server.oauth,
|
|
@@ -96,6 +99,7 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
|
|
|
96
99
|
const enableProjectConfig = options?.enableProjectConfig ?? true;
|
|
97
100
|
const filterExa = options?.filterExa ?? true;
|
|
98
101
|
const filterBrowser = options?.filterBrowser ?? false;
|
|
102
|
+
const autoloadOnly = options?.autoloadOnly ?? false;
|
|
99
103
|
|
|
100
104
|
// Load MCP servers via capability system
|
|
101
105
|
const result = await loadCapability<MCPServer>(mcpCapability.id, { cwd });
|
|
@@ -115,6 +119,9 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
|
|
|
115
119
|
if (config.enabled === false || disabledServers.has(server.name)) {
|
|
116
120
|
continue;
|
|
117
121
|
}
|
|
122
|
+
if (autoloadOnly && config.autoload === false) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
118
125
|
configs[server.name] = config;
|
|
119
126
|
sources[server.name] = server._source;
|
|
120
127
|
}
|
|
@@ -34,6 +34,8 @@ export interface MCPToolsLoadOptions {
|
|
|
34
34
|
filterExa?: boolean;
|
|
35
35
|
/** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
|
|
36
36
|
filterBrowser?: boolean;
|
|
37
|
+
/** Only connect servers with autoload !== false (default: false) */
|
|
38
|
+
autoloadOnly?: boolean;
|
|
37
39
|
/** SQLite storage for MCP tool cache (null disables cache) */
|
|
38
40
|
cacheStorage?: AgentStorage | null;
|
|
39
41
|
/** Auth storage used to resolve OAuth credentials before initial MCP connect */
|
|
@@ -72,6 +74,7 @@ export async function discoverAndLoadMCPTools(cwd: string, options?: MCPToolsLoa
|
|
|
72
74
|
enableProjectConfig: options?.enableProjectConfig,
|
|
73
75
|
filterExa: options?.filterExa,
|
|
74
76
|
filterBrowser: options?.filterBrowser,
|
|
77
|
+
autoloadOnly: options?.autoloadOnly,
|
|
75
78
|
});
|
|
76
79
|
} catch (error) {
|
|
77
80
|
// If discovery fails entirely, return empty result
|
|
@@ -147,6 +147,8 @@ export interface MCPDiscoverOptions {
|
|
|
147
147
|
filterExa?: boolean;
|
|
148
148
|
/** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
|
|
149
149
|
filterBrowser?: boolean;
|
|
150
|
+
/** Only connect servers with autoload !== false (default: false) */
|
|
151
|
+
autoloadOnly?: boolean;
|
|
150
152
|
/** Called when starting to connect to servers */
|
|
151
153
|
onConnecting?: (serverNames: string[]) => void;
|
|
152
154
|
}
|
|
@@ -325,6 +327,7 @@ export class MCPManager {
|
|
|
325
327
|
enableProjectConfig: options?.enableProjectConfig,
|
|
326
328
|
filterExa: options?.filterExa,
|
|
327
329
|
filterBrowser: options?.filterBrowser,
|
|
330
|
+
autoloadOnly: options?.autoloadOnly,
|
|
328
331
|
});
|
|
329
332
|
const result = await this.connectServers(configs, sources, options?.onConnecting);
|
|
330
333
|
result.exaApiKeys = exaApiKeys;
|