@gajae-code/coding-agent 0.7.1 → 0.7.3

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/types/cli/mcp-cli.d.ts +25 -0
  3. package/dist/types/cli/notify-cli.d.ts +2 -0
  4. package/dist/types/cli.d.ts +6 -0
  5. package/dist/types/commands/mcp.d.ts +70 -0
  6. package/dist/types/config/keybindings.d.ts +2 -2
  7. package/dist/types/config/settings-schema.d.ts +39 -2
  8. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  9. package/dist/types/extensibility/shared-events.d.ts +1 -0
  10. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +1 -1
  11. package/dist/types/lsp/types.d.ts +2 -0
  12. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  13. package/dist/types/modes/components/model-selector.d.ts +2 -0
  14. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  15. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  16. package/dist/types/notifications/attachment-registry.d.ts +17 -0
  17. package/dist/types/notifications/chat-adapters.d.ts +9 -0
  18. package/dist/types/notifications/config.d.ts +9 -1
  19. package/dist/types/notifications/engine.d.ts +59 -0
  20. package/dist/types/notifications/managed-daemon.d.ts +48 -0
  21. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  22. package/dist/types/notifications/telegram-daemon.d.ts +73 -16
  23. package/dist/types/notifications/threaded-inbound.d.ts +19 -0
  24. package/dist/types/notifications/threaded-render.d.ts +6 -1
  25. package/dist/types/notifications/topic-registry.d.ts +2 -0
  26. package/dist/types/session/agent-session.d.ts +2 -0
  27. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  28. package/dist/types/tools/fetch.d.ts +23 -0
  29. package/dist/types/tools/index.d.ts +1 -0
  30. package/dist/types/tools/telegram-send.d.ts +32 -0
  31. package/dist/types/web/insane/bridge.d.ts +103 -0
  32. package/dist/types/web/insane/url-guard.d.ts +25 -0
  33. package/dist/types/web/scrapers/types.d.ts +5 -0
  34. package/dist/types/web/scrapers/utils.d.ts +7 -1
  35. package/dist/types/web/search/provider.d.ts +18 -1
  36. package/dist/types/web/search/providers/insane.d.ts +53 -0
  37. package/dist/types/web/search/providers/text-citations.d.ts +23 -0
  38. package/dist/types/web/search/types.d.ts +12 -4
  39. package/package.json +10 -8
  40. package/scripts/verify-insane-vendor.ts +132 -0
  41. package/src/cli/args.ts +1 -1
  42. package/src/cli/fast-help.ts +1 -1
  43. package/src/cli/mcp-cli.ts +272 -0
  44. package/src/cli/notify-cli.ts +152 -5
  45. package/src/cli.ts +6 -2
  46. package/src/commands/mcp.ts +117 -0
  47. package/src/commands/team.ts +1 -1
  48. package/src/config/keybindings.ts +2 -2
  49. package/src/config/settings-schema.ts +30 -1
  50. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  51. package/src/defaults/gjc/skills/deep-interview/SKILL.md +4 -3
  52. package/src/defaults/gjc/skills/ralplan/SKILL.md +11 -4
  53. package/src/defaults/gjc/skills/team/SKILL.md +3 -2
  54. package/src/extensibility/extensions/runner.ts +1 -0
  55. package/src/extensibility/shared-events.ts +1 -0
  56. package/src/gjc-runtime/launch-tmux.ts +17 -3
  57. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  58. package/src/gjc-runtime/ralplan-runtime.ts +2 -2
  59. package/src/gjc-runtime/tmux-common.ts +3 -1
  60. package/src/gjc-runtime/ultragoal-guard.ts +25 -8
  61. package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
  62. package/src/gjc-runtime/workflow-manifest.ts +7 -2
  63. package/src/hooks/skill-state.ts +57 -0
  64. package/src/internal-urls/docs-index.generated.ts +14 -11
  65. package/src/lsp/config.ts +16 -3
  66. package/src/lsp/defaults.json +7 -0
  67. package/src/lsp/types.ts +2 -0
  68. package/src/modes/bridge/bridge-mode.ts +11 -0
  69. package/src/modes/components/custom-editor.ts +2 -0
  70. package/src/modes/components/footer.ts +2 -3
  71. package/src/modes/components/model-selector.ts +12 -0
  72. package/src/modes/components/status-line/git-utils.ts +25 -0
  73. package/src/modes/components/status-line.ts +10 -11
  74. package/src/modes/components/welcome.ts +2 -3
  75. package/src/modes/controllers/event-controller.ts +15 -0
  76. package/src/modes/controllers/selector-controller.ts +3 -0
  77. package/src/modes/interactive-mode.ts +48 -3
  78. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  79. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  80. package/src/modes/theme/defaults/index.ts +2 -0
  81. package/src/modes/utils/context-usage.ts +2 -2
  82. package/src/notifications/attachment-registry.ts +23 -0
  83. package/src/notifications/chat-adapters.ts +147 -0
  84. package/src/notifications/config.ts +23 -2
  85. package/src/notifications/engine.ts +100 -0
  86. package/src/notifications/index.ts +180 -38
  87. package/src/notifications/managed-daemon.ts +163 -0
  88. package/src/notifications/operator-runtime.ts +171 -0
  89. package/src/notifications/telegram-daemon.ts +553 -236
  90. package/src/notifications/threaded-inbound.ts +60 -4
  91. package/src/notifications/threaded-render.ts +20 -2
  92. package/src/notifications/topic-registry.ts +5 -0
  93. package/src/session/agent-session.ts +82 -51
  94. package/src/slash-commands/helpers/parse.ts +2 -1
  95. package/src/tools/bash.ts +9 -0
  96. package/src/tools/composer-bash-policy.ts +96 -0
  97. package/src/tools/fetch.ts +94 -1
  98. package/src/tools/index.ts +3 -0
  99. package/src/tools/telegram-send.ts +137 -0
  100. package/src/web/insane/bridge.ts +350 -0
  101. package/src/web/insane/url-guard.ts +159 -0
  102. package/src/web/scrapers/types.ts +143 -45
  103. package/src/web/scrapers/utils.ts +70 -19
  104. package/src/web/search/provider.ts +77 -18
  105. package/src/web/search/providers/anthropic.ts +70 -3
  106. package/src/web/search/providers/codex.ts +1 -119
  107. package/src/web/search/providers/gemini.ts +99 -0
  108. package/src/web/search/providers/insane.ts +551 -0
  109. package/src/web/search/providers/openai-compatible.ts +66 -32
  110. package/src/web/search/providers/text-citations.ts +111 -0
  111. package/src/web/search/types.ts +13 -2
  112. package/vendor/insane-search/LICENSE +21 -0
  113. package/vendor/insane-search/MANIFEST.json +24 -0
  114. package/vendor/insane-search/engine/__init__.py +23 -0
  115. package/vendor/insane-search/engine/__main__.py +128 -0
  116. package/vendor/insane-search/engine/bias_check.py +183 -0
  117. package/vendor/insane-search/engine/executor.py +254 -0
  118. package/vendor/insane-search/engine/fetch_chain.py +725 -0
  119. package/vendor/insane-search/engine/learning.py +175 -0
  120. package/vendor/insane-search/engine/phase0.py +214 -0
  121. package/vendor/insane-search/engine/safety.py +91 -0
  122. package/vendor/insane-search/engine/templates/package.json +11 -0
  123. package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
  124. package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
  125. package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
  126. package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
  127. package/vendor/insane-search/engine/tests/test_u1.py +200 -0
  128. package/vendor/insane-search/engine/tests/test_u4.py +131 -0
  129. package/vendor/insane-search/engine/tests/test_u5.py +163 -0
  130. package/vendor/insane-search/engine/tests/test_u7.py +124 -0
  131. package/vendor/insane-search/engine/transport.py +211 -0
  132. package/vendor/insane-search/engine/url_transforms.py +98 -0
  133. package/vendor/insane-search/engine/validators.py +331 -0
  134. package/vendor/insane-search/engine/waf_detector.py +214 -0
  135. package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
@@ -29,6 +29,8 @@ export interface NotifyCommandDeps {
29
29
  pollIntervalMs?: number;
30
30
  setupChatId?: string;
31
31
  setupRedact?: boolean;
32
+ setupInteractive?: boolean;
33
+ threadedModePrompt?: (message: string) => Promise<string>;
32
34
  }
33
35
 
34
36
  interface TelegramApiResponse<T> {
@@ -47,6 +49,18 @@ interface TelegramUpdate {
47
49
  };
48
50
  }
49
51
 
52
+ interface TelegramUser {
53
+ id: number;
54
+ is_bot?: boolean;
55
+ first_name?: string;
56
+ username?: string;
57
+ has_topics_enabled?: boolean;
58
+ allows_users_to_create_topics?: boolean;
59
+ }
60
+
61
+ type ThreadedModeState = "enabled" | "disabled" | "unknown";
62
+ type ThreadedModeFinalLabel = "verified" | "unverified" | "unknown";
63
+
50
64
  const DEFAULT_API_BASE = "https://api.telegram.org";
51
65
  const DEFAULT_POLL_TIMEOUT_MS = 60_000;
52
66
  const DEFAULT_POLL_INTERVAL_MS = 1_000;
@@ -120,7 +134,11 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
120
134
  throw new Error("Telegram bot token is required.");
121
135
  }
122
136
 
123
- await callTelegram(fetchImpl, apiBase, token, "getMe", {});
137
+ const user = await getMe(fetchImpl, apiBase, token);
138
+ const threadedState = await verifyThreadedMode(fetchImpl, apiBase, token, user, {
139
+ interactive: resolveSetupInteractive(deps),
140
+ prompt: deps.threadedModePrompt ?? promptForThreadedMode,
141
+ });
124
142
  process.stdout.write(
125
143
  "Token validated. Message your bot now from the private Telegram chat to pair notifications.\n",
126
144
  );
@@ -145,7 +163,9 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
145
163
  if (deps.setupRedact) settings.set("notifications.redact", true);
146
164
  await settings.flush();
147
165
 
148
- process.stdout.write(`Notifications enabled. botToken=${maskToken(token)} chatId=${chatId}\n`);
166
+ process.stdout.write(
167
+ `Notifications enabled. botToken=${maskToken(token)} chatId=${chatId} threaded=${threadedLabel(threadedState)}\n`,
168
+ );
149
169
  }
150
170
 
151
171
  async function promptForToken(): Promise<string> {
@@ -160,14 +180,135 @@ async function promptForToken(): Promise<string> {
160
180
  }
161
181
  }
162
182
 
183
+ const THREADED_ENABLED_SUCCESS =
184
+ "Telegram Threaded Mode capability verified for this bot. GJC will request a private-chat topic per session; if Telegram ever refuses topic creation, notifications fall back to this flat chat with a one-time nudge.\n";
185
+
186
+ const THREADED_MISSING_WARNING =
187
+ "Warning: Telegram getMe did not include has_topics_enabled, so GJC cannot verify private-chat Threaded Mode capability for this bot. Setup will continue; update Telegram/Bot API support or re-run setup if per-session topics fail.\n";
188
+
189
+ const THREADED_NONINTERACTIVE_WARNING =
190
+ "Warning: Telegram Threaded Mode capability is OFF for this bot. Setup will be saved because this run is non-interactive, but per-session Telegram delivery may fail closed until the bot owner enables Threaded Mode in @BotFather. GJC cannot enable it through the Bot API.\n";
191
+
192
+ const THREADED_DISABLED_GUIDANCE =
193
+ "Telegram Threaded Mode is OFF for this bot. GJC needs Telegram private-chat topics so each session can use its own thread.\n" +
194
+ "GJC cannot enable this through the Bot API. Open @BotFather, select this bot, enable Threaded Mode / forum topics for private chats, then return here.\n" +
195
+ "Telegram may require an additional Stars purchase fee for private-chat topics.\n";
196
+
197
+ const THREADED_DISABLED_PROMPT =
198
+ "Press Enter after enabling Threaded Mode, or type skip to finish setup with a warning: ";
199
+
200
+ const THREADED_STILL_OFF = "Telegram still reports Threaded Mode OFF for this bot.\n";
201
+
202
+ const THREADED_RETRY_PROMPT = "Press Enter to check again, or type skip to finish setup with a warning: ";
203
+
204
+ const THREADED_SKIP_WARNING =
205
+ "Warning: continuing without verified Telegram Threaded Mode capability. Setup will be saved, but per-session Telegram delivery may fail closed until Threaded Mode is enabled in BotFather.\n";
206
+
207
+ const THREADED_INVALID_INPUT = "Type Enter to retry or skip to continue with a warning.\n";
208
+
209
+ const THREADED_RETRY_INPUTS = new Set(["", "y", "yes", "r", "retry"]);
210
+ const THREADED_SKIP_INPUTS = new Set(["s", "skip", "n", "no"]);
211
+
212
+ function isTelegramUser(value: unknown): value is TelegramUser {
213
+ return Boolean(value) && typeof value === "object" && typeof (value as { id?: unknown }).id === "number";
214
+ }
215
+
216
+ async function getMe(fetchImpl: typeof fetch, apiBase: string, token: string): Promise<TelegramUser> {
217
+ const user = await callTelegram<unknown>(fetchImpl, apiBase, token, "getMe", {});
218
+ if (!isTelegramUser(user)) {
219
+ throw new Error("Telegram getMe returned invalid Telegram response: missing valid User result.");
220
+ }
221
+ return user;
222
+ }
223
+
224
+ function threadedModeState(user: TelegramUser): ThreadedModeState {
225
+ if (user.has_topics_enabled === true) return "enabled";
226
+ if (user.has_topics_enabled === false) return "disabled";
227
+ return "unknown";
228
+ }
229
+
230
+ function threadedLabel(state: ThreadedModeState): ThreadedModeFinalLabel {
231
+ if (state === "enabled") return "verified";
232
+ if (state === "disabled") return "unverified";
233
+ return "unknown";
234
+ }
235
+
236
+ function resolveSetupInteractive(deps: NotifyCommandDeps): boolean {
237
+ if (deps.setupInteractive !== undefined) return deps.setupInteractive;
238
+ return Boolean(process.stdin.isTTY) && !deps.setupChatId?.trim();
239
+ }
240
+
241
+ async function promptForThreadedMode(message: string): Promise<string> {
242
+ if (!process.stdin.isTTY) return "skip";
243
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
244
+ try {
245
+ return (await rl.question(message)).trim();
246
+ } finally {
247
+ rl.close();
248
+ }
249
+ }
250
+
251
+ async function verifyThreadedMode(
252
+ fetchImpl: typeof fetch,
253
+ apiBase: string,
254
+ token: string,
255
+ initialUser: TelegramUser,
256
+ opts: { interactive: boolean; prompt: (message: string) => Promise<string> },
257
+ ): Promise<ThreadedModeState> {
258
+ const classify = (user: TelegramUser): ThreadedModeState | undefined => {
259
+ const state = threadedModeState(user);
260
+ if (state === "enabled") {
261
+ process.stdout.write(THREADED_ENABLED_SUCCESS);
262
+ return "enabled";
263
+ }
264
+ if (state === "unknown") {
265
+ process.stdout.write(THREADED_MISSING_WARNING);
266
+ return "unknown";
267
+ }
268
+ return undefined;
269
+ };
270
+
271
+ const initial = classify(initialUser);
272
+ if (initial) return initial;
273
+
274
+ if (!opts.interactive) {
275
+ process.stdout.write(THREADED_NONINTERACTIVE_WARNING);
276
+ return "disabled";
277
+ }
278
+
279
+ process.stdout.write(THREADED_DISABLED_GUIDANCE);
280
+ let firstPrompt = true;
281
+ for (;;) {
282
+ const answer = (await opts.prompt(firstPrompt ? THREADED_DISABLED_PROMPT : THREADED_RETRY_PROMPT))
283
+ .trim()
284
+ .toLowerCase();
285
+ firstPrompt = false;
286
+ if (THREADED_SKIP_INPUTS.has(answer)) {
287
+ process.stdout.write(THREADED_SKIP_WARNING);
288
+ return "disabled";
289
+ }
290
+ if (!THREADED_RETRY_INPUTS.has(answer)) {
291
+ process.stdout.write(THREADED_INVALID_INPUT);
292
+ continue;
293
+ }
294
+ const resolved = classify(await getMe(fetchImpl, apiBase, token));
295
+ if (resolved) return resolved;
296
+ process.stdout.write(THREADED_STILL_OFF);
297
+ }
298
+ }
299
+
163
300
  async function runStatus(deps: NotifyCommandDeps): Promise<void> {
164
301
  const settings = await getSettings(deps);
165
302
  const cfg = getNotificationConfig(settings);
166
303
  process.stdout.write(
167
304
  `${chalk.bold("Notifications")}\n` +
168
305
  ` enabled: ${cfg.enabled}\n` +
169
- ` botToken: ${maskToken(cfg.botToken)}\n` +
170
- ` chatId: ${cfg.chatId ?? "(unset)"}\n` +
306
+ ` telegram.botToken: ${maskToken(cfg.botToken)}\n` +
307
+ ` telegram.chatId: ${cfg.chatId ?? "(unset)"}\n` +
308
+ ` discord.botToken: ${maskToken(cfg.discord.botToken)}\n` +
309
+ ` discord.channelId: ${cfg.discord.channelId ?? "(unset)"}\n` +
310
+ ` slack.botToken: ${maskToken(cfg.slack.botToken)}\n` +
311
+ ` slack.channelId: ${cfg.slack.channelId ?? "(unset)"}\n` +
171
312
  ` redact: ${cfg.redact}\n`,
172
313
  );
173
314
  }
@@ -263,12 +404,18 @@ ${chalk.bold("Usage:")}
263
404
  ${APP_NAME} notify status
264
405
 
265
406
  ${chalk.bold("Subcommands:")}
266
- setup Pair a Telegram bot token with a private chat
407
+ setup Pair a Telegram bot token with a private chat and verify Threaded Mode capability
267
408
  status Show notification configuration without secrets
268
409
 
269
410
  ${chalk.bold("Examples:")}
270
411
  ${APP_NAME} notify setup
271
412
  ${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
272
413
  ${APP_NAME} notify status
414
+
415
+ ${chalk.bold("Threaded Mode:")}
416
+ GJC uses Telegram private-chat topics for per-session threads. Setup verifies the bot
417
+ capability via getMe.has_topics_enabled. If it is off, enable Threaded Mode in @BotFather;
418
+ bots cannot toggle it through the Bot API. If Telegram refuses topic creation at runtime,
419
+ GJC delivers flat to the paired private chat and nudges you to enable Threaded Mode.
273
420
  `);
274
421
  }
package/src/cli.ts CHANGED
@@ -22,7 +22,7 @@ process.title = APP_NAME;
22
22
  const rootHelpFlags = ["--help", "-h", "help"];
23
23
  const versionFlags = ["--version", "-v"];
24
24
 
25
- const commands: CommandEntry[] = [
25
+ export const commands: CommandEntry[] = [
26
26
  { name: "codex-native-hook", load: () => import("./commands/codex-native-hook").then(m => m.default) },
27
27
  { name: "state", load: () => import("./commands/state").then(m => m.default) },
28
28
  { name: "setup", load: () => import("./commands/setup").then(m => m.default) },
@@ -39,6 +39,7 @@ const commands: CommandEntry[] = [
39
39
  { name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
40
40
  { name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
41
41
  { name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
42
+ { name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
42
43
  {
43
44
  name: "contribute-pr",
44
45
  aliases: ["contribution-prep"],
@@ -48,6 +49,7 @@ const commands: CommandEntry[] = [
48
49
  { name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
49
50
  { name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
50
51
  { name: "update", load: () => import("./commands/update").then(m => m.default) },
52
+ { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
51
53
  { name: "launch", load: () => import("./commands/launch").then(m => m.default) },
52
54
  ];
53
55
 
@@ -220,4 +222,6 @@ export async function runCli(argv: string[]): Promise<void> {
220
222
  return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
221
223
  }
222
224
 
223
- await runCli(process.argv.slice(2));
225
+ if (import.meta.main) {
226
+ await runCli(process.argv.slice(2));
227
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Direct MCP server registration for standalone GJC.
3
+ */
4
+ import { Args, Command, Flags } from "@gajae-code/utils/cli";
5
+ import { type MCPAction, type MCPCommandArgs, runMCPCommand } from "../cli/mcp-cli";
6
+
7
+ const ACTIONS: MCPAction[] = ["add", "list", "remove"];
8
+
9
+ export default class MCP extends Command {
10
+ static description = "Register standalone MCP servers explicitly in GJC config";
11
+ static delegateHelp = true;
12
+
13
+ static examples = [
14
+ "gjc mcp add context7 npx -y @upstash/context7-mcp",
15
+ "gjc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN",
16
+ "gjc mcp list --json",
17
+ "gjc mcp remove context7",
18
+ ];
19
+
20
+ static args = {
21
+ action: Args.string({ description: "MCP action", required: false, options: ACTIONS }),
22
+ name: Args.string({ description: "Server name", required: false }),
23
+ commandArgs: Args.string({
24
+ description: "Command/URL and trailing args for add",
25
+ required: false,
26
+ multiple: true,
27
+ }),
28
+ };
29
+
30
+ static flags = {
31
+ project: Flags.boolean({ description: "Write/read project scope (./.gjc/mcp.json) instead of user scope" }),
32
+ force: Flags.boolean({ description: "Overwrite an existing server during add", default: false }),
33
+ json: Flags.boolean({
34
+ char: "j",
35
+ description: "Emit machine-readable JSON with sensitive values redacted",
36
+ default: false,
37
+ }),
38
+ type: Flags.string({ description: "Server transport type", options: ["stdio", "http", "sse"] }),
39
+ command: Flags.string({ description: "Stdio server command for add" }),
40
+ url: Flags.string({ description: "HTTP/SSE server URL for add" }),
41
+ arg: Flags.string({ description: "Argument passed to a stdio server (repeatable)", multiple: true }),
42
+ env: Flags.string({
43
+ description: "Environment variable for stdio server as KEY=VALUE (repeatable)",
44
+ multiple: true,
45
+ }),
46
+ header: Flags.string({
47
+ description: "HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)",
48
+ multiple: true,
49
+ }),
50
+ cwd: Flags.string({ description: "Working directory for stdio server" }),
51
+ timeout: Flags.integer({ description: "Connection timeout in milliseconds" }),
52
+ };
53
+
54
+ async run(): Promise<void> {
55
+ if (this.argv.includes("--help") || this.argv.includes("-h")) {
56
+ this.printHelp();
57
+ return;
58
+ }
59
+
60
+ const { args, flags } = await this.parse(MCP);
61
+ const action = (args.action ?? "list") as MCPAction;
62
+ const cmd: MCPCommandArgs = {
63
+ action,
64
+ name: args.name,
65
+ commandArgs: args.commandArgs,
66
+ flags: {
67
+ project: flags.project,
68
+ force: flags.force,
69
+ json: flags.json,
70
+ type: flags.type as MCPCommandArgs["flags"]["type"],
71
+ command: flags.command,
72
+ url: flags.url,
73
+ arg: flags.arg,
74
+ env: flags.env,
75
+ header: flags.header,
76
+ cwd: flags.cwd,
77
+ timeout: flags.timeout,
78
+ },
79
+ };
80
+ await runMCPCommand(cmd);
81
+ }
82
+
83
+ private printHelp(): void {
84
+ process.stdout.write(`Register standalone MCP servers explicitly in GJC config
85
+
86
+ USAGE
87
+ $ gjc mcp [add|list|remove] [NAME] [COMMAND_OR_URL] [ARGS...] [FLAGS]
88
+
89
+ COMMANDS
90
+ add Add an explicit user-provided MCP server definition
91
+ list List registered servers with env/header/auth values redacted
92
+ remove Remove a registered server and print the removed definition redacted
93
+
94
+ FLAGS
95
+ --project Use project scope (./.gjc/mcp.json) instead of user scope
96
+ --force Overwrite an existing server during add
97
+ -j, --json Emit machine-readable JSON with sensitive values redacted
98
+ --type=<value> stdio | http | sse (default: stdio, or http when --url is set)
99
+ --command=<value> Stdio server command for add
100
+ --url=<value> HTTP/SSE server URL for add
101
+ --arg=<value> Stdio server argument (repeatable)
102
+ --env=<value> Stdio env var as KEY=VALUE (repeatable; redacted in output)
103
+ --header=<value> HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)
104
+ --cwd=<value> Working directory for stdio server
105
+ --timeout=<int> Connection timeout in milliseconds
106
+
107
+ EXAMPLES
108
+ $ gjc mcp add context7 npx -y @upstash/context7-mcp
109
+ $ gjc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN
110
+ $ gjc mcp list --json
111
+ $ gjc mcp remove context7
112
+
113
+ SECURITY
114
+ This command writes only the server definition supplied on this invocation. It does not import or inherit Claude Code, Codex, OpenCode, or other live MCP configs. Public output redacts env, header, auth, and OAuth credential values.
115
+ `);
116
+ }
117
+ }
@@ -95,7 +95,7 @@ export default class Team extends Command {
95
95
  };
96
96
 
97
97
  static examples = [
98
- "gjc --tmux # start/attach the required tmux-backed leader session first",
98
+ "gjc --tmux # start the required tmux-backed leader session first",
99
99
  'gjc team 3:executor "Implement the approved plan"',
100
100
  "gjc team status <team-name> --json",
101
101
  "gjc team monitor <team-name> --json",
@@ -117,8 +117,8 @@ export const KEYBINDINGS = {
117
117
  description: "Open external editor",
118
118
  },
119
119
  "app.message.followUp": {
120
- defaultKeys: "ctrl+enter",
121
- description: "Send follow-up message",
120
+ defaultKeys: [],
121
+ description: "Send follow-up message (no default; Ctrl+Enter inserts a newline)",
122
122
  },
123
123
  "app.message.queue": {
124
124
  defaultKeys: "alt+enter",
@@ -254,10 +254,14 @@ export const SETTINGS_SCHEMA = {
254
254
  "auth.broker.url": { type: "string", default: undefined },
255
255
  "auth.broker.token": { type: "string", default: undefined },
256
256
 
257
- // Notifications (Telegram bundled reference client)
257
+ // Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
258
258
  "notifications.enabled": { type: "boolean", default: false },
259
259
  "notifications.telegram.botToken": { type: "string", default: undefined },
260
260
  "notifications.telegram.chatId": { type: "string", default: undefined },
261
+ "notifications.discord.botToken": { type: "string", default: undefined },
262
+ "notifications.discord.channelId": { type: "string", default: undefined },
263
+ "notifications.slack.botToken": { type: "string", default: undefined },
264
+ "notifications.slack.channelId": { type: "string", default: undefined },
261
265
  "notifications.redact": { type: "boolean", default: false },
262
266
  "notifications.verbosity": {
263
267
  type: "string",
@@ -2104,6 +2108,17 @@ export const SETTINGS_SCHEMA = {
2104
2108
  ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
2105
2109
  },
2106
2110
 
2111
+ "web.insaneFallback": {
2112
+ type: "boolean",
2113
+ default: false,
2114
+ ui: {
2115
+ tab: "tools",
2116
+ label: "Insane Search Fallback",
2117
+ description:
2118
+ "Opt in to the vendored insane-search escalation for blocked public URL reads (403/WAF/JS-gated). Off by default. Requires preinstalled python3 + curl_cffi (and node + playwright/stealth for the browser phase); changes network posture by enabling TLS/browser impersonation for public pages.",
2119
+ },
2120
+ },
2121
+
2107
2122
  "github.enabled": {
2108
2123
  type: "boolean",
2109
2124
  default: false,
@@ -2731,6 +2746,7 @@ export const SETTINGS_SCHEMA = {
2731
2746
  values: [
2732
2747
  "auto",
2733
2748
  "duckduckgo",
2749
+ "insane",
2734
2750
  "exa",
2735
2751
  "brave",
2736
2752
  "jina",
@@ -2763,6 +2779,11 @@ export const SETTINGS_SCHEMA = {
2763
2779
  label: "DuckDuckGo",
2764
2780
  description: "Keyless default — no API key or OAuth required",
2765
2781
  },
2782
+ {
2783
+ value: "insane",
2784
+ label: "Insane",
2785
+ description: "Keyless safe public-route fallback inspired by upstream insane-search",
2786
+ },
2766
2787
  { value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set" },
2767
2788
  { value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
2768
2789
  { value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
@@ -3177,6 +3198,14 @@ export interface NotificationsSettings {
3177
3198
  botToken: string | undefined;
3178
3199
  chatId: string | undefined;
3179
3200
  };
3201
+ discord: {
3202
+ botToken: string | undefined;
3203
+ channelId: string | undefined;
3204
+ };
3205
+ slack: {
3206
+ botToken: string | undefined;
3207
+ channelId: string | undefined;
3208
+ };
3180
3209
  redact: boolean;
3181
3210
  daemon: {
3182
3211
  idleTimeoutMs: number;
@@ -0,0 +1,94 @@
1
+ export type DeepInterviewPlaintextAskLeakOption = "Yes, crystallize" | "Adjust wording" | "Missing scope";
2
+
3
+ export type DeepInterviewPlaintextAskLeakResult = {
4
+ kind: "deep_interview_plaintext_ask_leak";
5
+ matchedOptions: DeepInterviewPlaintextAskLeakOption[];
6
+ signals: {
7
+ optionsHeading: true;
8
+ restateIntent: true;
9
+ deepInterviewContext: boolean;
10
+ };
11
+ };
12
+
13
+ const CANONICAL_OPTIONS: DeepInterviewPlaintextAskLeakOption[] = [
14
+ "Yes, crystallize",
15
+ "Adjust wording",
16
+ "Missing scope",
17
+ ];
18
+
19
+ const RESTATE_INTENT_PATTERNS: RegExp[] = [
20
+ /\brestate\b/i,
21
+ /\bconfirmation\b/i,
22
+ /\bconfirm(?:ation|ed|ing)?\b/i,
23
+ /\bcrystall?ize\b/i,
24
+ /\bdoes\s+this\s+(?:capture|match|reflect|summari[sz]e)\b/i,
25
+ /\bbefore\s+(?:i|we)\s+(?:write|finali[sz]e|crystall?ize)\b/i,
26
+ /\bread\s+only\s+this\s+line\b/i,
27
+ /\bturn\s+this\s+into\s+(?:the\s+)?(?:spec|final)\b/i,
28
+ /재진술/,
29
+ /다시\s*말/,
30
+ /확인/,
31
+ /확정/,
32
+ /정리/,
33
+ /요약/,
34
+ /맞(?:습니까|나요|는지)/,
35
+ ];
36
+
37
+ const DEEP_INTERVIEW_CONTEXT_PATTERNS: RegExp[] = [
38
+ /\bdeep[ -]?interview\b/i,
39
+ /\bsocratic\b/i,
40
+ /\binterview\s+round\b/i,
41
+ /\bround\s+\d+\b/i,
42
+ /\bambiguity\b/i,
43
+ /\btopology\s+confirmation\b/i,
44
+ /딥\s*인터뷰/,
45
+ /심층\s*인터뷰/,
46
+ /인터뷰\s*(?:라운드|진행|확인)/,
47
+ ];
48
+
49
+ const FINAL_ARTIFACT_PATTERNS: RegExp[] = [
50
+ /^\s*#{1,3}\s*(?:(?:final|approved|completed|완료|최종)\s+)?(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/im,
51
+ /^\s*#{1,3}\s*(?:deep[ -]?interview\s+)?(?:final|approved|completed|완료|최종)\s+(?:spec(?:ification)?|transcript)\b/im,
52
+ /\b(?:final|approved|completed)\s+(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/i,
53
+ /\binterview\s+transcript\b/i,
54
+ /\.gjc\/specs\//i,
55
+ /최종\s*(?:명세|스펙|기록)/,
56
+ ];
57
+
58
+ function hasOptionsHeading(text: string): boolean {
59
+ return /(?:^|\n)\s*options\s*:/i.test(text);
60
+ }
61
+
62
+ function hasPattern(text: string, patterns: RegExp[]): boolean {
63
+ return patterns.some(pattern => pattern.test(text));
64
+ }
65
+
66
+ function matchedCanonicalOptions(text: string): DeepInterviewPlaintextAskLeakOption[] {
67
+ const folded = text.toLocaleLowerCase("en-US");
68
+ return CANONICAL_OPTIONS.filter(option => folded.includes(option.toLocaleLowerCase("en-US")));
69
+ }
70
+
71
+ function looksLikeFinalArtifact(text: string): boolean {
72
+ return hasPattern(text, FINAL_ARTIFACT_PATTERNS);
73
+ }
74
+
75
+ export function detectDeepInterviewPlaintextAskLeak(text: string): DeepInterviewPlaintextAskLeakResult | null {
76
+ const trimmed = text.trim();
77
+ if (trimmed.length === 0) return null;
78
+ if (looksLikeFinalArtifact(trimmed)) return null;
79
+ if (!hasOptionsHeading(trimmed)) return null;
80
+
81
+ const matchedOptions = matchedCanonicalOptions(trimmed);
82
+ if (matchedOptions.length < 2) return null;
83
+ if (!hasPattern(trimmed, RESTATE_INTENT_PATTERNS)) return null;
84
+
85
+ return {
86
+ kind: "deep_interview_plaintext_ask_leak",
87
+ matchedOptions,
88
+ signals: {
89
+ optionsHeading: true,
90
+ restateIntent: true,
91
+ deepInterviewContext: hasPattern(trimmed, DEEP_INTERVIEW_CONTEXT_PATTERNS),
92
+ },
93
+ };
94
+ }
@@ -292,7 +292,7 @@ Auto-research must never add a public skill entrypoint, never be slash-command/d
292
292
 
293
293
  ### Step 2b: Ask the Question
294
294
 
295
- Use the `ask` tool with the generated question. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
295
+ Use the `ask` tool with the generated question. When a question has options, you MUST call `ask` and must not print `Question:`/`Options:` blocks as assistant prose. If you already printed a question/options block as prose, your next action is to call `ask` with the same question/options, not to wait for a typed answer. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
296
296
 
297
297
  ```
298
298
  Round {n} | Component: {target_component_name} | Targeting: {weakest_dimension} | Why now: {one_sentence_targeting_rationale} | Ambiguity: {score}%
@@ -495,7 +495,7 @@ When ambiguity ≤ threshold (or hard cap / early exit):
495
495
 
496
496
  **4a. Closure / Acceptance Guard.** Even when ambiguity ≤ threshold, do not treat the math as completion. Run an independent readiness audit from the full main-session perspective (including explore findings, established facts, and triggers the scorer may not have fully weighed). Confirm every active topology component has goal/constraint/criteria coverage, no unresolved or disputed trigger remains on a path that matters, and no low-confidence auto-answer is standing in for user-confirmed truth above the clarity cap. If a material gap exists, explicitly override the gate to the user — "The math says ready, but I am not accepting it yet because {gap}" — and ask the single highest-impact follow-up, returning to Phase 2. Record any override in `state.closure_overrides`.
497
497
 
498
- **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
498
+ **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. Because this gate has options, it MUST go through `ask`: do not print the Restate question and options as assistant prose with `Question:`/`Options:` labels. If the Restate gate was already printed that way, immediately call `ask` with the same question/options before accepting or waiting for any answer. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
499
499
 
500
500
  1. **Generate the specification** using opus model with the prompt-safe transcript. If the full interview transcript or initial context is too large, include the summary plus all concrete decisions, acceptance criteria, unresolved gaps, and ontology snapshots; never overflow the prompt with raw oversized context.
501
501
  - Apply `language.instruction` when present so user-facing prose in the spec preserves the session language; keep code identifiers, file paths, commands, JSON/settings keys, and quoted source text unchanged.
@@ -690,6 +690,7 @@ Skipping any stage is possible but reduces quality assurance:
690
690
 
691
691
  <Tool_Usage>
692
692
  - Use the `ask` tool for each interview question — provides clickable UI with contextual options
693
+ - For any option-bearing question, call `ask`; never print `Question:`/`Options:` blocks as assistant prose. If such a block was already printed, call `ask` with the same question/options as the very next action instead of waiting for a typed/prose answer
693
694
  - Preserve the GJC `ask` tool path for native interaction; do not introduce parallel structured-question transport into this skill
694
695
  - Use `read/search/find exploration or a bounded read-only planner/architect subagent` for brownfield codebase exploration (run BEFORE asking user about codebase)
695
696
  - Use opus model (temperature 0.1) for ambiguity scoring — consistency is critical
@@ -698,7 +699,7 @@ Skipping any stage is possible but reduces quality assurance:
698
699
  - Use the GJC workflow CLI to save the final spec at `.gjc/_session-{sessionid}/specs/deep-interview-{slug}.md` exactly; do not use `write`, `edit`, or `ast_edit` directly on `.gjc/` paths without force override.
699
700
  - Use public GJC workflow entrypoints to bridge to ralplan, ultragoal, or team only after explicit execution approval — never implement directly. Implementation handoff defaults to ultragoal; reserve team for when tmux-based interactive worker parallelization is genuinely required.
700
701
  - The lateral-review panel spawns read-only persona subagents (Task tool) in parallel with independent context; it is an assist layer, never an executor and never the completion authority
701
- - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each
702
+ - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each; if any of these gates has options, the assistant must call `ask` and must not print `Question:`/`Options:` blocks as assistant prose
702
703
  - Use internal fragment auto-modes only at their documented hooks: `auto-research-greenfield.md` between Step 2a and 2b for greenfield `research: true` questions, `auto-answer-uncertain.md` as Step 2b′ after `ask` resolves and before scoring, and `lateral-review-panel.md` for the Phase 3 panel personas at ambiguity-milestone transitions and before synthesizing agent-supplied answers.
703
704
  - Fragment auto-modes are loaded on demand as `kind: "skill-fragment"`; they are not public workflow skills, not slash-command/discoverable, and not `skill://` registrations.
704
705
  </Tool_Usage>
@@ -47,7 +47,7 @@ Planning artifacts and stage handoffs MUST be persisted through the ralplan CLI
47
47
  gjc ralplan --write --stage <type> --stage_n <N> --artifact "markdown file path or markdown string"
48
48
  ```
49
49
 
50
- Use stage values that match the producer or artifact kind, such as `planner`, `architect`, `critic`, `revision`, `adr`, or `final`. Increment `--stage_n` for each consensus-loop pass. The `--artifact` value may be either a markdown file path prepared outside `.gjc/` for ingestion or the markdown content string itself. The native `--write` handler persists markdown under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/stage-<NN>-<stage>.md`, maintains an `index.jsonl` audit log, and for `final` stages additionally writes a `pending-approval.md` copy. Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
50
+ Use stage values that match the producer or artifact kind, such as `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`. Increment `--stage_n` for each consensus-loop pass. The `--artifact` value may be either a markdown file path prepared outside `.gjc/` for ingestion or the markdown content string itself. The native `--write` handler persists markdown under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/stage-<NN>-<stage>.md`, maintains an `index.jsonl` audit log, and for `final` stages additionally writes a `pending-approval.md` copy. Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
51
51
 
52
52
  While ralplan is active it is a pre-approval planning phase: product-code mutation tools (`write`/`edit`/`ast_edit`) and product-mutating `bash` (e.g. `tee src/...`, redirects into the project tree) are blocked, exactly like deep-interview. Prefer passing the `--artifact` markdown **inline** (the content string) so no scratch file is needed; this is mandatory for restricted role agents (see below). Only the leader, and only when an artifact is too large to pass inline, may stage it as a file in a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree and pass that path — never write scratch files into the repo or `.gjc/`. Product code is mutated only after the plan is approved and execution begins.
53
53
 
@@ -80,9 +80,16 @@ The consensus workflow:
80
80
  d. Return to Critic evaluation
81
81
  e. Repeat this loop until Critic returns `APPROVE` or 5 iterations are reached
82
82
  f. If 5 iterations are reached without `APPROVE`, present the best version to the user
83
- 6. On Critic approval, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact "..."`, and do not directly edit `.gjc/_session-{sessionid}/plans`. *(--interactive only)* If `--interactive` is set, use the `ask` tool to present the plan with approval options (Approve execution via ultragoal (Recommended) / Approve execution via team (only when tmux-based interactive worker parallelization is required) / Compact then return for execution approval / Request changes / Reject). Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups). Otherwise, output the final plan and stop before any mutation or delegation.
84
- 7. *(--interactive only)* User chooses: Approve ultragoal execution (recommended), Approve team execution (tmux parallelization only), Request changes, or Reject
85
- 8. *(--interactive only)* On approval: invoke `/skill:ultragoal` for execution by default; invoke `/skill:team` only when the user explicitly needs tmux-based interactive worker parallelization -- never implement directly
83
+ 6. **Post-ralplan interview** (intent reconciliation gate): After Critic returns `APPROVE` and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
84
+ a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
85
+ b. **Cross-check prior context for conflicts**: glob `.gjc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
86
+ c. **Reconcile with the user**:
87
+ - *(--interactive only)* Use the `ask` tool to confirm the open assumptions and conflicts **one at a time**, weakest/highest-impact first, polishing intent. If any confirmation reveals that the plan diverges from user intent, route the consolidated correction back into the re-review loop (step 5b Planner revision) and re-run Architect + Critic before returning here. Cap at the same 5-iteration ceiling.
88
+ - *(automated mode)* Do not ask. Embed every unconfirmed assumption and every detected prior-context conflict into the final plan under an **## Intent Reconciliation** section as explicit open confirmations the user must review at the `pending approval` gate, so nothing is silently assumed.
89
+ d. Persist the reconciliation with `gjc ralplan --write --stage post-interview --stage_n <N> --artifact "..." --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
90
+ 7. On reconciliation completion, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact "..."`, and do not directly edit `.gjc/_session-{sessionid}/plans`. *(--interactive only)* If `--interactive` is set, use the `ask` tool to present the plan with approval options (Approve execution via ultragoal (Recommended) / Approve execution via team (only when tmux-based interactive worker parallelization is required) / Compact then return for execution approval / Request changes / Reject). Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section. Otherwise, output the final plan and stop before any mutation or delegation.
91
+ 8. *(--interactive only)* User chooses: Approve ultragoal execution (recommended), Approve team execution (tmux parallelization only), Request changes, or Reject
92
+ 9. *(--interactive only)* On approval: invoke `/skill:ultragoal` for execution by default; invoke `/skill:team` only when the user explicitly needs tmux-based interactive worker parallelization -- never implement directly
86
93
 
87
94
  Before invoking `/skill:team` or `/skill:ultragoal`, mark ralplan ready for handoff so the skill tool's chain guard permits the transition:
88
95
 
@@ -311,8 +311,9 @@ Worker protocol:
311
311
  Useful runtime env vars:
312
312
 
313
313
  - `GJC_TMUX_COMMAND` / `GJC_TEAM_TMUX_COMMAND`
314
- - tmux binary/command override (default `tmux`). `GJC_TMUX_COMMAND` applies to every GJC tmux flow; `GJC_TEAM_TMUX_COMMAND` is honored as an alias by the team path. Both resolve through the same resolver, so the team leader and `gjc session ...` always target the same multiplexer.
315
- - Multiplexer support boundary: GJC-managed sessions and the team leader are detected via tmux user options (`@gjc-profile`, written with `set-option` and read back with `show-options` / `list-sessions -F`). A provider must round-trip those user options to be supported. Real tmux works. Alternative multiplexers such as psmux on Windows do not reliably persist tmux user options yet, so `gjc session status` reports `gjc_tmux_session_untagged` (the session exists in the multiplexer but is not GJC-tagged) and team startup rejects the leader as `unmanaged_tmux_session`. The Windows-native psmux path is therefore not fully supported; use real tmux for GJC-managed session and team flows.
314
+ - tmux binary/name override (default `tmux`). `GJC_TMUX_COMMAND` applies to every GJC tmux flow; `GJC_TEAM_TMUX_COMMAND` is honored as an alias by the team path. Both resolve through the same resolver, so the team leader and `gjc session ...` always target the same multiplexer. These values are executable path/name overrides, not shell command lines; do not include flags such as `psmux -L <namespace>` in the env var.
315
+ - Windows psmux namespace boundary: psmux can be exposed as `psmux.exe` or as its `tmux.exe`/`pmux.exe` aliases. Its `-c <path>` cwd/start-directory flags do not isolate the server namespace; psmux uses the tmux-compatible global `-L <namespace>` flag for isolated server instances. GJC does not currently expose structured runtime `-L` support, because launch, `gjc session`, and `gjc team` must all carry the same namespace prefix together.
316
+ - Multiplexer support boundary: GJC-managed sessions and the team leader are detected via tmux user options (`@gjc-profile`, written with `set-option` and read back with `show-options` / `list-sessions -F`). A provider must round-trip those user options to be supported. Real tmux works. Alternative multiplexers such as psmux on Windows do not reliably persist tmux user options yet, so `gjc session status` reports `gjc_tmux_session_untagged` (the session exists in the multiplexer but is not GJC-tagged) and team startup rejects the leader as `unmanaged_tmux_session`. psmux namespace isolation is separate from this ownership-tag support boundary; `-L` prevents cross-namespace server collision, but it does not make GJC-managed session and team flows supported while user options fail to round-trip. Use real tmux for GJC-managed session and team flows.
316
317
  - `GJC_TEAM_WORKER_COMMAND`
317
318
  - worker command override (default resolves to active GJC entrypoint or `gjc`)
318
319
  - `GJC_TEAM_STATE_ROOT`
@@ -348,6 +348,7 @@ export class ExtensionRunner {
348
348
  "ctrl+g",
349
349
  "shift+tab",
350
350
  "shift+ctrl+p",
351
+ "ctrl+enter",
351
352
  "alt+enter",
352
353
  "escape",
353
354
  "enter",