@gajae-code/coding-agent 0.16.3 → 0.16.6

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 (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -158,69 +158,98 @@ export interface StdioPolicyContext {
158
158
  pluginRoot: string;
159
159
  }
160
160
 
161
- // Node/Bun flags that can execute or load code outside the bundled script.
162
- const DANGEROUS_LAUNCHER_FLAGS = [
163
- "-e",
164
- "--eval",
165
- "-p",
166
- "--print",
167
- "-r",
168
- "--require",
169
- "--import",
170
- "--loader",
171
- "--experimental-loader",
172
- "--input-type",
173
- ];
161
+ export interface StdioInvocationTarget {
162
+ kind: "launcher";
163
+ launcher: "node" | "bun";
164
+ entrypoint: string;
165
+ executablePath: string;
166
+ ownedRelativePath: string;
167
+ ownedRelativePaths: string[];
168
+ cwd: string;
169
+ }
174
170
 
175
- /** stdio launcher/path confinement policy. */
176
- export function assertStdioAllowed(entry: GjcPluginMcpManifestEntry, ctx: StdioPolicyContext): void {
171
+ function isWithinOrEqual(root: string, candidate: string): boolean {
172
+ return candidate === root || pathIsWithin(root, candidate);
173
+ }
174
+
175
+ function isCrossPlatformAbsolute(value: string): boolean {
176
+ return path.posix.isAbsolute(value) || path.win32.isAbsolute(value) || /^[A-Za-z]:/.test(value);
177
+ }
178
+
179
+ function resolveManifestRelative(base: string, value: string, label: string): string {
180
+ if (isCrossPlatformAbsolute(value)) fail(`${label} must be relative`);
181
+ const portable = path.posix.normalize(value.replaceAll("\\", "/"));
182
+ return path.resolve(base, ...portable.split("/"));
183
+ }
184
+
185
+ function isFileLikeArgument(value: string): boolean {
186
+ return value.startsWith(".") || value.includes("/") || value.includes("\\") || isCrossPlatformAbsolute(value);
187
+ }
188
+
189
+ /**
190
+ * Resolve the exact executable file selected by the supported stdio grammar.
191
+ * Bare Node/Bun launchers accept a direct bundled entrypoint as argv[0]; no
192
+ * manifest-controlled runtime options or subcommands precede it. Path-qualified
193
+ * commands are unsupported because they bypass the verified module wrapper.
194
+ */
195
+ export function classifyStdioInvocation(
196
+ entry: GjcPluginMcpManifestEntry,
197
+ ctx: StdioPolicyContext,
198
+ ): StdioInvocationTarget {
177
199
  const command = entry.command ?? "";
178
200
  if (!command) fail(`MCP "${entry.name}": stdio requires a command`);
179
201
  const root = path.resolve(ctx.pluginRoot);
180
- const base = path.basename(command);
181
- const isBareLauncher = !command.includes("/") && ALLOWED_STDIO_LAUNCHERS.has(base);
182
- const isRootConfinedExecutable = command.includes("/") && pathIsWithin(root, path.resolve(root, command));
183
- // Absolute or relative paths must stay inside the plugin root; bare launchers
184
- // must be in the allowlist. An absolute /bin/node is rejected (outside root).
185
- if (!isBareLauncher && !isRootConfinedExecutable) {
186
- fail(`MCP "${entry.name}": stdio command not allowed: ${command}`);
187
- }
188
- const usesNodeLauncher = isBareLauncher || ALLOWED_STDIO_LAUNCHERS.has(base);
189
- const args = entry.args ?? [];
190
- // Reject code-eval/loader flags for node/bun launchers.
191
- if (usesNodeLauncher) {
192
- for (const arg of args) {
193
- const flag = arg.split("=")[0];
194
- if (DANGEROUS_LAUNCHER_FLAGS.includes(flag)) {
195
- fail(`MCP "${entry.name}": stdio launcher flag not allowed: ${arg}`);
196
- }
197
- }
198
- // Require a root-confined script as the first non-flag argument.
199
- const firstScript = args.find(a => !a.startsWith("-"));
200
- if (!firstScript) {
201
- fail(`MCP "${entry.name}": node/bun stdio launcher requires a bundled script argument`);
202
- }
203
- if (!pathIsWithin(root, path.resolve(root, firstScript))) {
204
- fail(`MCP "${entry.name}": stdio script escapes plugin root: ${firstScript}`);
205
- }
206
- }
207
- // cwd must resolve within the plugin root.
208
- const cwd = entry.cwd ? path.resolve(root, entry.cwd) : root;
209
- if (!pathIsWithin(root, cwd) && cwd !== root) {
202
+ const cwd = entry.cwd ? resolveManifestRelative(root, entry.cwd, `MCP "${entry.name}": stdio cwd`) : root;
203
+ if (!isWithinOrEqual(root, cwd)) {
210
204
  fail(`MCP "${entry.name}": stdio cwd escapes plugin root: ${entry.cwd}`);
211
205
  }
212
- // File-like args must resolve within the plugin root; reject env-expansion.
206
+ const args = entry.args ?? [];
213
207
  for (const arg of args) {
214
208
  if (/\$\{?[A-Za-z_]/.test(arg) || arg.includes("`") || arg.includes("$(")) {
215
209
  fail(`MCP "${entry.name}": stdio arg expansion not allowed: ${arg}`);
216
210
  }
217
- if (arg.startsWith("-")) continue;
218
- if (!arg.startsWith(".") && !arg.includes("/")) continue;
219
- const resolvedArg = path.resolve(root, arg);
220
- if (!pathIsWithin(root, resolvedArg)) {
221
- fail(`MCP "${entry.name}": stdio arg escapes plugin root: ${arg}`);
222
- }
223
211
  }
212
+
213
+ if (!ALLOWED_STDIO_LAUNCHERS.has(command)) {
214
+ fail(`MCP "${entry.name}": stdio command not allowed (expected bare node or bun): ${command}`);
215
+ }
216
+ const entrypoint = args[0];
217
+ if (!entrypoint) {
218
+ fail(`MCP "${entry.name}": node/bun stdio launcher requires a bundled script argument`);
219
+ }
220
+ if (entrypoint.startsWith("-")) {
221
+ fail(`MCP "${entry.name}": stdio launcher options before the bundled entrypoint are not allowed`);
222
+ }
223
+ const executablePath = resolveManifestRelative(cwd, entrypoint, `MCP "${entry.name}": stdio launcher entrypoint`);
224
+ if (!pathIsWithin(root, executablePath)) {
225
+ fail(`MCP "${entry.name}": stdio script escapes plugin root: ${entrypoint}`);
226
+ }
227
+ const extension = path.extname(executablePath).toLowerCase();
228
+ const allowedExtensions = command === "node" ? new Set([".mjs"]) : new Set([".mjs", ".ts", ".jsx", ".tsx"]);
229
+ if (!allowedExtensions.has(extension)) {
230
+ fail(`MCP "${entry.name}": unsupported ${command} entrypoint extension: ${extension || "<none>"}`);
231
+ }
232
+ const ownedRelativePaths = [path.relative(root, executablePath)];
233
+ for (const arg of args.slice(1)) {
234
+ if (arg.startsWith("-") || !isFileLikeArgument(arg)) continue;
235
+ const resolvedArg = resolveManifestRelative(cwd, arg, `MCP "${entry.name}": stdio arg`);
236
+ if (!pathIsWithin(root, resolvedArg)) fail(`MCP "${entry.name}": stdio arg escapes plugin root: ${arg}`);
237
+ ownedRelativePaths.push(path.relative(root, resolvedArg));
238
+ }
239
+ return {
240
+ kind: "launcher",
241
+ launcher: command as "node" | "bun",
242
+ entrypoint,
243
+ executablePath,
244
+ ownedRelativePath: ownedRelativePaths[0] as string,
245
+ ownedRelativePaths,
246
+ cwd,
247
+ };
248
+ }
249
+
250
+ /** stdio launcher/path confinement policy. */
251
+ export function assertStdioAllowed(entry: GjcPluginMcpManifestEntry, ctx: StdioPolicyContext): void {
252
+ classifyStdioInvocation(entry, ctx);
224
253
  }
225
254
 
226
255
  /**
@@ -11,6 +11,19 @@ export function gjcPluginProjectRoot(cwd: string): string {
11
11
  return path.join(cwd, ".gjc", "gjc-plugins");
12
12
  }
13
13
 
14
+ export function gjcPluginSafeDirectoryName(name: string): string {
15
+ const segment = name.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "");
16
+ if (!segment || segment === "." || segment === "..") {
17
+ throw new GjcPluginLoadError("invalid_manifest", `GJC plugin name is not a safe directory segment: ${name}`);
18
+ }
19
+ return segment;
20
+ }
21
+
22
+ export function gjcPluginInstallRoot(scope: "user" | "project", cwd: string, name: string): string {
23
+ const root = scope === "user" ? gjcPluginUserRoot() : gjcPluginProjectRoot(cwd);
24
+ return path.join(root, gjcPluginSafeDirectoryName(name));
25
+ }
26
+
14
27
  function isEnoent(error: unknown): boolean {
15
28
  return (error as NodeJS.ErrnoException).code === "ENOENT";
16
29
  }
@@ -27,6 +27,117 @@ function isEnoent(error: unknown): boolean {
27
27
  return (error as NodeJS.ErrnoException)?.code === "ENOENT";
28
28
  }
29
29
 
30
+ function registryRecord(value: unknown, field: string): Record<string, unknown> {
31
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
32
+ throw new GjcPluginLoadError("invalid_manifest", `Invalid GJC plugin registry field ${field}`);
33
+ }
34
+ return value as Record<string, unknown>;
35
+ }
36
+
37
+ function rejectRegistryKeys(value: Record<string, unknown>, allowed: readonly string[], field: string): void {
38
+ const allowedSet = new Set(allowed);
39
+ for (const key of Object.keys(value)) {
40
+ if (!allowedSet.has(key)) {
41
+ throw new GjcPluginLoadError("invalid_manifest", `Unknown GJC plugin registry field ${field}.${key}`);
42
+ }
43
+ }
44
+ }
45
+
46
+ function validateRegistryEntryShape(value: unknown, field: string): void {
47
+ const entry = registryRecord(value, field);
48
+ rejectRegistryKeys(
49
+ entry,
50
+ [
51
+ "name",
52
+ "version",
53
+ "scope",
54
+ "enabled",
55
+ "pluginRoot",
56
+ "manifestPath",
57
+ "manifestHash",
58
+ "source",
59
+ "installedAt",
60
+ "updatedAt",
61
+ "copiedFiles",
62
+ "surfaces",
63
+ "disabledSurfaceIds",
64
+ "quarantine",
65
+ "migration",
66
+ ],
67
+ field,
68
+ );
69
+ const source = registryRecord(entry.source, `${field}.source`);
70
+ rejectRegistryKeys(source, ["kind", "uri", "ref", "sha", "resolvedAt"], `${field}.source`);
71
+ const surfaces = registryRecord(entry.surfaces, `${field}.surfaces`);
72
+ rejectRegistryKeys(
73
+ surfaces,
74
+ ["subskills", "tools", "hooks", "mcps", "systemAppendices", "agentAppendices"],
75
+ `${field}.surfaces`,
76
+ );
77
+ const surfaceKeys: Record<string, readonly string[]> = {
78
+ subskills: [
79
+ "extensionId",
80
+ "name",
81
+ "description",
82
+ "parent",
83
+ "phase",
84
+ "activationArg",
85
+ "relativePath",
86
+ "sha256",
87
+ "toolRefs",
88
+ ],
89
+ tools: [
90
+ "extensionId",
91
+ "name",
92
+ "relativePath",
93
+ "sha256",
94
+ "description",
95
+ "schema",
96
+ "schemaHash",
97
+ "implementationHash",
98
+ "presentationHash",
99
+ "metadataVersion",
100
+ ],
101
+ hooks: [
102
+ "extensionId",
103
+ "name",
104
+ "event",
105
+ "target",
106
+ "phase",
107
+ "relativePath",
108
+ "sha256",
109
+ "implementationHash",
110
+ "capabilities",
111
+ "networkDestinations",
112
+ "filesystemRoots",
113
+ "capabilityHash",
114
+ "functionHook",
115
+ ],
116
+ mcps: ["extensionId", "name", "transport", "configHash", "config"],
117
+ systemAppendices: ["extensionId", "name", "relativePath", "content", "contentHash", "bytes"],
118
+ agentAppendices: ["extensionId", "name", "relativePath", "content", "contentHash", "bytes", "agent"],
119
+ };
120
+ for (const [surfaceName, allowed] of Object.entries(surfaceKeys)) {
121
+ const items = surfaces[surfaceName];
122
+ if (!Array.isArray(items))
123
+ throw new GjcPluginLoadError("invalid_manifest", `Invalid ${field}.surfaces.${surfaceName}`);
124
+ for (let index = 0; index < items.length; index += 1) {
125
+ const item = registryRecord(items[index], `${field}.surfaces.${surfaceName}[${index}]`);
126
+ rejectRegistryKeys(item, allowed, `${field}.surfaces.${surfaceName}[${index}]`);
127
+ }
128
+ }
129
+ if (!Array.isArray(entry.copiedFiles) || !Array.isArray(entry.disabledSurfaceIds)) {
130
+ throw new GjcPluginLoadError("invalid_manifest", `Invalid GJC plugin registry arrays at ${field}`);
131
+ }
132
+ if (entry.quarantine !== undefined && !Array.isArray(entry.quarantine)) {
133
+ throw new GjcPluginLoadError("invalid_manifest", `Invalid ${field}.quarantine`);
134
+ }
135
+ if (entry.migration !== undefined) {
136
+ const migration = registryRecord(entry.migration, `${field}.migration`);
137
+ rejectRegistryKeys(migration, ["status", "metadataVersion", "migratedAt", "failure"], `${field}.migration`);
138
+ }
139
+ }
140
+
30
141
  /**
31
142
  * Deterministic ordering: scope (user before project) -> normalized name ->
32
143
  * resolved plugin root. Collisions are errors elsewhere; order only controls
@@ -69,8 +180,9 @@ async function readRegistryRaw(scope: GjcPluginScope, cwd: string): Promise<GjcP
69
180
  );
70
181
  if (
71
182
  !Array.isArray(registry.plugins) ||
72
- registry.plugins.some(plugin => {
183
+ registry.plugins.some((plugin, index) => {
73
184
  if (!plugin || typeof plugin !== "object") return true;
185
+ validateRegistryEntryShape(plugin, `plugins[${index}]`);
74
186
  const entry = plugin as GjcPluginRegistryEntry;
75
187
  return (
76
188
  entry.scope !== scope ||