@gajae-code/coding-agent 0.7.10 → 0.8.0
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 +58 -0
- package/README.md +2 -2
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/config/keybindings.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +25 -2
- package/dist/types/defaults/gjc-defaults.d.ts +7 -0
- 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/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/skill-message.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/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +5 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/types.d.ts +2 -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/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +8 -5
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/utils/cmux-workspace.d.ts +30 -1
- package/dist/types/web/search/index.d.ts +3 -1
- package/dist/types/web/search/provider.d.ts +9 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- 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/cli/update-cli.ts +24 -0
- package/src/cli/web-search-cli.ts +2 -5
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/config/keybindings.ts +14 -5
- package/src/config/mcp-schema.json +5 -1
- package/src/config/model-profiles.ts +16 -0
- package/src/config/settings-schema.ts +18 -2
- package/src/coordinator-mcp/server.ts +15 -3
- package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
- package/src/defaults/gjc-defaults.ts +16 -0
- 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/extensions/runner.ts +2 -1
- package/src/gjc-runtime/launch-tmux.ts +164 -42
- package/src/gjc-runtime/ralplan-runtime.ts +22 -6
- package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/gjc-runtime/session-state-sidecar.ts +167 -24
- package/src/gjc-runtime/state-runtime.ts +8 -5
- package/src/gjc-runtime/state-writer.ts +5 -5
- package/src/gjc-runtime/team-runtime.ts +56 -51
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/main.ts +2 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/components/agent-dashboard.ts +1 -34
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/model-selector.ts +64 -4
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +13 -2
- package/src/modes/components/skill-message.ts +18 -10
- 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 +23 -0
- package/src/modes/controllers/event-controller.ts +4 -0
- package/src/modes/controllers/input-controller.ts +192 -39
- package/src/modes/controllers/selector-controller.ts +25 -28
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +23 -1
- 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/slash-command-visibility.ts +9 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/index.ts +20 -1
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/telegram-daemon.ts +4 -4
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +15 -1
- package/src/prompts/tools/bash.md +1 -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 +7 -6
- package/src/session/agent-session.ts +81 -18
- package/src/session/session-manager.ts +58 -18
- package/src/slash-commands/builtin-registry.ts +30 -10
- package/src/slash-commands/types.ts +2 -0
- 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 +39 -14
- package/src/tools/index.ts +8 -1
- package/src/utils/cmux-workspace.ts +134 -7
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +118 -45
- package/src/web/search/provider.ts +65 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.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
|
@@ -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) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const BEGINNER_HIDDEN_EXTENSION_COMMANDS = new Set(["grok-build-usage"]);
|
|
2
|
+
|
|
3
|
+
export function shouldShowExtensionCommand(name: string, activeProvider: string | undefined): boolean {
|
|
4
|
+
if (!BEGINNER_HIDDEN_EXTENSION_COMMANDS.has(name)) return true;
|
|
5
|
+
if (name === "grok-build-usage") {
|
|
6
|
+
return activeProvider === "grok-build" || Boolean(process.env.GROK_CLI_OAUTH_TOKEN);
|
|
7
|
+
}
|
|
8
|
+
return true;
|
|
9
|
+
}
|
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;
|
|
@@ -209,6 +210,7 @@ export interface InteractiveModeContext {
|
|
|
209
210
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
210
211
|
handleChangelogCommand(showFull?: boolean): Promise<void>;
|
|
211
212
|
handleHotkeysCommand(): void;
|
|
213
|
+
handleHelpCommand(): void;
|
|
212
214
|
handleToolsCommand(): void;
|
|
213
215
|
handleContextCommand(): void;
|
|
214
216
|
handleDumpCommand(): void;
|
|
@@ -38,6 +38,8 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
38
38
|
`| \`${appKey(bindings, "app.clear")}\` | Clear editor (first) / exit (second) |`,
|
|
39
39
|
`| \`${appKey(bindings, "app.exit")}\` | Exit (when editor is empty) |`,
|
|
40
40
|
`| \`${appKey(bindings, "app.suspend")}\` | Suspend to background |`,
|
|
41
|
+
`| \`${appKey(bindings, "app.commandPalette.open")}\` | Open command palette |`,
|
|
42
|
+
`| \`${appKey(bindings, "app.session.new")}\` | Start a fresh session |`,
|
|
41
43
|
`| \`${appKey(bindings, "app.thinking.cycle")}\` | Cycle thinking level |`,
|
|
42
44
|
`| \`${appKey(bindings, "app.model.cycleForward")}\` | Cycle configured model roles |`,
|
|
43
45
|
`| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle configured model roles temporarily |`,
|
|
@@ -51,8 +53,9 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
51
53
|
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
|
52
54
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
|
|
53
55
|
`| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
|
|
54
|
-
"|
|
|
55
|
-
"|
|
|
56
|
+
"| `?` | Show help / shortcuts |",
|
|
57
|
+
"| `#` | Prompt actions (command-palette style actions) |",
|
|
58
|
+
"| `/` | Slash commands (try `/help` or `/new`) |",
|
|
56
59
|
"| `!` | Run bash command |",
|
|
57
60
|
"| `!!` | Run bash command (excluded from context) |",
|
|
58
61
|
"| `$` | Run Python in shared kernel |",
|
|
@@ -2,6 +2,8 @@ import type { AgentMessage } from "@gajae-code/agent-core";
|
|
|
2
2
|
import type { AssistantMessage, ImageContent, Message } from "@gajae-code/ai";
|
|
3
3
|
import { type Component, Spacer, Text, TruncatedText } from "@gajae-code/tui";
|
|
4
4
|
import { settings } from "../../config/settings";
|
|
5
|
+
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/gjc-plugins";
|
|
6
|
+
import { buildSkillPromptMessage, parseSkillInvocations } from "../../extensibility/skills";
|
|
5
7
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
6
8
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
7
9
|
import { BranchSummaryMessageComponent } from "../../modes/components/branch-summary-message";
|
|
@@ -610,9 +612,80 @@ export class UiHelpers {
|
|
|
610
612
|
this.ctx.updatePendingMessagesDisplay();
|
|
611
613
|
this.ctx.showStatus("Queued message for after compaction");
|
|
612
614
|
}
|
|
615
|
+
#hasSkillInvocations(text: string): boolean {
|
|
616
|
+
return parseSkillInvocations(text, this.ctx.skillCommands ?? new Map()).length > 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#isCompactionCommandMessage(text: string): boolean {
|
|
620
|
+
return this.#hasSkillInvocations(text) || this.isKnownSlashCommand(text);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async #deliverQueuedSkillMessage(message: CompactionQueuedMessage): Promise<boolean> {
|
|
624
|
+
const invocations = parseSkillInvocations(message.text, this.ctx.skillCommands ?? new Map());
|
|
625
|
+
if (invocations.length === 0) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (let index = 0; index < invocations.length; index += 1) {
|
|
630
|
+
const invocation = invocations[index];
|
|
631
|
+
if (!invocation) continue;
|
|
632
|
+
|
|
633
|
+
const activationResult = await resolveSubskillActivationForSkillInvocation({
|
|
634
|
+
cwd: this.ctx.sessionManager.getCwd(),
|
|
635
|
+
sessionId: this.ctx.session.sessionId,
|
|
636
|
+
skillName: invocation.skill.name,
|
|
637
|
+
args: invocation.args,
|
|
638
|
+
});
|
|
639
|
+
const built = await buildSkillPromptMessage(invocation.skill, activationResult.cleanedArgs, {
|
|
640
|
+
subskillActivation: activationResult.activation,
|
|
641
|
+
subskillActivationSet: activationResult.activeSubskillsToPersist,
|
|
642
|
+
cwd: this.ctx.sessionManager.getCwd(),
|
|
643
|
+
sessionId: this.ctx.session.sessionId,
|
|
644
|
+
});
|
|
645
|
+
const details: SkillPromptDetails = built.details;
|
|
646
|
+
const displayText = `/${invocation.commandName}${activationResult.cleanedArgs ? ` ${activationResult.cleanedArgs}` : ""}`;
|
|
647
|
+
|
|
648
|
+
if (this.ctx.session.isStreaming) {
|
|
649
|
+
const tag = this.ctx.session.enqueueCustomMessageDisplay(displayText, message.mode);
|
|
650
|
+
details.__pendingDisplayTag = tag;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const isLast = index === invocations.length - 1;
|
|
654
|
+
if (!this.ctx.session.isStreaming && !isLast) {
|
|
655
|
+
await this.ctx.session.sendCustomMessage({
|
|
656
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
657
|
+
content: built.message,
|
|
658
|
+
display: true,
|
|
659
|
+
details,
|
|
660
|
+
attribution: "user",
|
|
661
|
+
});
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
613
664
|
|
|
665
|
+
await this.ctx.session.promptCustomMessage(
|
|
666
|
+
{
|
|
667
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
668
|
+
content: built.message,
|
|
669
|
+
display: true,
|
|
670
|
+
details,
|
|
671
|
+
attribution: "user",
|
|
672
|
+
},
|
|
673
|
+
{ streamingBehavior: message.mode },
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (this.ctx.session.isStreaming) {
|
|
678
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
679
|
+
this.ctx.ui.requestRender();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
614
684
|
async #deliverQueuedMessage(message: CompactionQueuedMessage): Promise<void> {
|
|
615
|
-
if (this
|
|
685
|
+
if (await this.#deliverQueuedSkillMessage(message)) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (this.isKnownSlashCommand(message.text)) {
|
|
616
689
|
await this.ctx.session.prompt(message.text);
|
|
617
690
|
return;
|
|
618
691
|
}
|
|
@@ -671,14 +744,14 @@ export class UiHelpers {
|
|
|
671
744
|
|
|
672
745
|
let firstPromptIndex = -1;
|
|
673
746
|
for (let i = 0; i < queuedMessages.length; i++) {
|
|
674
|
-
if (!this
|
|
747
|
+
if (!this.#isCompactionCommandMessage(queuedMessages[i].text)) {
|
|
675
748
|
firstPromptIndex = i;
|
|
676
749
|
break;
|
|
677
750
|
}
|
|
678
751
|
}
|
|
679
752
|
if (firstPromptIndex === -1) {
|
|
680
753
|
for (const message of queuedMessages) {
|
|
681
|
-
await this
|
|
754
|
+
await this.#deliverQueuedMessage(message);
|
|
682
755
|
}
|
|
683
756
|
return;
|
|
684
757
|
}
|
|
@@ -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 {
|
|
@@ -13,7 +13,7 @@ import { parseInThreadConfigCommand } from "./config-commands";
|
|
|
13
13
|
import { daemonPaths } from "./daemon-paths";
|
|
14
14
|
import {
|
|
15
15
|
buildCompactChoiceGrid,
|
|
16
|
-
|
|
16
|
+
code,
|
|
17
17
|
splitTelegramHtml,
|
|
18
18
|
TELEGRAM_MESSAGE_LIMIT,
|
|
19
19
|
TELEGRAM_PARSE_MODE,
|
|
@@ -1117,10 +1117,10 @@ export class TelegramNotificationDaemon {
|
|
|
1117
1117
|
limit: 10,
|
|
1118
1118
|
includeInternal: false,
|
|
1119
1119
|
});
|
|
1120
|
-
const
|
|
1121
|
-
? recent.map(e => `• ${e.sessionId}${e.path ? ` (${e.path})` : ""}`).join("\n")
|
|
1120
|
+
const body = recent.length
|
|
1121
|
+
? recent.map(e => `• ${code(e.sessionId)}${e.path ? ` (${code(e.path)})` : ""}`).join("\n")
|
|
1122
1122
|
: "No recent sessions.";
|
|
1123
|
-
await replyHtml(
|
|
1123
|
+
await replyHtml(body);
|
|
1124
1124
|
return true;
|
|
1125
1125
|
}
|
|
1126
1126
|
|
|
@@ -25,7 +25,7 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
25
25
|
|
|
26
26
|
<constraints>
|
|
27
27
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
28
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown
|
|
28
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
29
29
|
- Never approve code or plans you have not grounded in inspected files.
|
|
30
30
|
- Never give generic advice detached from this codebase.
|
|
31
31
|
- Never approve CRITICAL or HIGH severity issues.
|
|
@@ -63,6 +63,9 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
|
|
|
63
63
|
## Summary
|
|
64
64
|
2-3 sentences with result and main recommendation.
|
|
65
65
|
|
|
66
|
+
## Claims
|
|
67
|
+
Evidence-backed claims being reviewed or introduced.
|
|
68
|
+
|
|
66
69
|
## Analysis
|
|
67
70
|
Evidence-backed findings.
|
|
68
71
|
|
|
@@ -81,12 +84,12 @@ Prioritized concrete actions.
|
|
|
81
84
|
## Code Review Recommendation
|
|
82
85
|
`APPROVE` / `COMMENT` / `REQUEST CHANGES`
|
|
83
86
|
|
|
84
|
-
##
|
|
87
|
+
## Tradeoffs
|
|
85
88
|
Table or bullets comparing viable options when relevant.
|
|
86
89
|
|
|
87
|
-
Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown
|
|
90
|
+
Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
88
91
|
|
|
89
|
-
gjc ralplan --write --stage architect --stage_n <N> --artifact
|
|
92
|
+
gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
90
93
|
|
|
91
94
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (Architectural Status + Code Review Recommendation). Never paste the full review body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
92
95
|
</output_contract>
|
|
@@ -17,7 +17,7 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: do not write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown
|
|
20
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
21
|
- A lone file path is valid input; read and evaluate it.
|
|
22
22
|
- Reject YAML-only plans as invalid plan format when a human-readable plan is required.
|
|
23
23
|
- Do not invent problems; report no issues found when the plan passes.
|
|
@@ -42,11 +42,19 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
42
42
|
</success_criteria>
|
|
43
43
|
|
|
44
44
|
<output_contract>
|
|
45
|
+
## Verdict
|
|
45
46
|
**[OKAY / ITERATE / REJECT]**
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
## Claim Checks
|
|
49
|
+
Concise evidence-backed explanation of verified claims.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
## Missing Evidence
|
|
52
|
+
Definitely missing or unverified evidence, or `None`.
|
|
53
|
+
|
|
54
|
+
## Approval Boundary
|
|
55
|
+
What execution may proceed with, and what remains outside approval.
|
|
56
|
+
|
|
57
|
+
## Summary
|
|
50
58
|
- Clarity
|
|
51
59
|
- Verifiability
|
|
52
60
|
- Completeness
|
|
@@ -55,11 +63,12 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
55
63
|
- Alternatives Depth
|
|
56
64
|
- Risk/Verification Rigor
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
## Required Changes
|
|
67
|
+
If not OKAY, list concrete required fixes; otherwise write `None`.
|
|
59
68
|
|
|
60
|
-
Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown
|
|
69
|
+
Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
61
70
|
|
|
62
|
-
gjc ralplan --write --stage critic --stage_n <N> --artifact
|
|
71
|
+
gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
63
72
|
|
|
64
73
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (OKAY / ITERATE / REJECT). Never paste the full evaluation body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
65
74
|
</output_contract>
|
|
@@ -17,7 +17,7 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
|
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown
|
|
20
|
+
- Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
21
|
- Persist durable plans only through `gjc ralplan --write`. Never write plan files to `/tmp`, the repository, or any other path, and never rely on a file the caller must read back. The CLI is your only persistence channel.
|
|
22
22
|
- Inspect the repository before asking about code facts.
|
|
23
23
|
- Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences that repository inspection cannot resolve.
|
|
@@ -45,16 +45,21 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
|
|
|
45
45
|
<output_contract>
|
|
46
46
|
Build the full plan as a single markdown document containing:
|
|
47
47
|
- Summary
|
|
48
|
+
- Intent Diff
|
|
49
|
+
- Decision Drivers
|
|
50
|
+
- Options
|
|
48
51
|
- In scope / out of scope
|
|
49
52
|
- File-level changes
|
|
50
53
|
- Sequencing and dependencies
|
|
51
54
|
- Acceptance criteria
|
|
52
55
|
- Verification
|
|
56
|
+
- Escalation/Risk Gate
|
|
57
|
+
- Verification Plan
|
|
53
58
|
- Risks and mitigations
|
|
54
59
|
|
|
55
|
-
Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan
|
|
60
|
+
Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
|
|
56
61
|
|
|
57
|
-
gjc ralplan --write --stage planner --stage_n <N> --artifact
|
|
62
|
+
gjc ralplan --write --stage planner --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
58
63
|
|
|
59
64
|
Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back into your response — the caller reads the persisted artifact when it needs the full text.
|
|
60
65
|
</output_contract>
|
|
@@ -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.
|
|
@@ -33,7 +33,7 @@ This session's bash tool is restricted. It only accepts commands beginning with:
|
|
|
33
33
|
{{#each restrictedAllowedPrefixes}}
|
|
34
34
|
- `{{this}}`
|
|
35
35
|
{{/each}}
|
|
36
|
-
Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; per-command env
|
|
36
|
+
Use it only for sanctioned GJC workflow CLI persistence or state read/write/contract operations; the only per-command env override allowed is `GJC_RALPLAN_ARTIFACT` when paired with `gjc ralplan --write ... --artifact-env GJC_RALPLAN_ARTIFACT`, and all other shell command shapes are blocked before execution.
|
|
37
37
|
{{/when}}
|
|
38
38
|
</restricted-bash-mode>
|
|
39
39
|
{{/if}}
|
|
@@ -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;
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -61,6 +61,12 @@ export interface MCPAuthConfig {
|
|
|
61
61
|
interface MCPServerConfigBase {
|
|
62
62
|
/** Whether this server is enabled (default: true) */
|
|
63
63
|
enabled?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether to connect this server automatically at session startup (default: true).
|
|
66
|
+
* When false the server stays configured-but-disconnected until connected
|
|
67
|
+
* explicitly (e.g. `/mcp connect <name>`).
|
|
68
|
+
*/
|
|
69
|
+
autoload?: boolean;
|
|
64
70
|
/** Connection timeout in milliseconds (default: 30000) */
|
|
65
71
|
timeout?: number;
|
|
66
72
|
/** Authentication configuration (optional) */
|