@gajae-code/coding-agent 0.18.6 → 0.18.8

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 (302) hide show
  1. package/CHANGELOG.md +318 -1
  2. package/dist/types/async/job-manager.d.ts +1 -1
  3. package/dist/types/capability/mcp.d.ts +2 -0
  4. package/dist/types/capability/skill.d.ts +2 -0
  5. package/dist/types/capability/types.d.ts +6 -0
  6. package/dist/types/cli/public-command-errors.d.ts +3 -1
  7. package/dist/types/cli/setup-cli.d.ts +1 -1
  8. package/dist/types/commands/deep-interview.d.ts +3 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  10. package/dist/types/config/file-lock.d.ts +1 -0
  11. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  12. package/dist/types/config/model-profile-activation.d.ts +11 -3
  13. package/dist/types/config/model-registry.d.ts +4 -9
  14. package/dist/types/config/settings.d.ts +4 -0
  15. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  16. package/dist/types/cursor-delete-path.d.ts +1 -0
  17. package/dist/types/cursor-mcp-name.d.ts +8 -0
  18. package/dist/types/customization/inventory.d.ts +2 -0
  19. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  22. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  23. package/dist/types/discovery/helpers.d.ts +8 -3
  24. package/dist/types/export/html/css-color.d.ts +2 -0
  25. package/dist/types/export/html/template.generated.d.ts +1 -1
  26. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  27. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  28. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  29. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  31. package/dist/types/extensibility/skill-management.d.ts +3 -1
  32. package/dist/types/extensibility/skills.d.ts +2 -0
  33. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  34. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  35. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  36. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  37. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  38. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  39. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  40. package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
  41. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  42. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  43. package/dist/types/lsp/workspace-path.d.ts +1 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  46. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  47. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  48. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  49. package/dist/types/modes/interactive-mode.d.ts +1 -0
  50. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  51. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  52. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  53. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  54. package/dist/types/progress/progress-contract.d.ts +154 -0
  55. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  56. package/dist/types/progress/project-progress.d.ts +141 -0
  57. package/dist/types/progress/render-progress.d.ts +28 -0
  58. package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
  59. package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
  60. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  61. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  62. package/dist/types/runtime-mcp/types.d.ts +6 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  64. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  65. package/dist/types/sdk/broker/broker.d.ts +18 -1
  66. package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
  67. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  68. package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
  69. package/dist/types/sdk/bus/index.d.ts +12 -1
  70. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  71. package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/host/host.d.ts +6 -0
  74. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  75. package/dist/types/sdk/index.d.ts +1 -0
  76. package/dist/types/sdk/router/session-router.d.ts +9 -0
  77. package/dist/types/sdk/session-list.d.ts +1 -1
  78. package/dist/types/sdk/session.d.ts +8 -0
  79. package/dist/types/session/agent-session.d.ts +38 -9
  80. package/dist/types/session/artifacts.d.ts +2 -2
  81. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  82. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  83. package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
  84. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  85. package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
  86. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
  87. package/dist/types/session/session-manager.d.ts +31 -5
  88. package/dist/types/session/session-retirement.d.ts +71 -0
  89. package/dist/types/session/session-storage.d.ts +62 -48
  90. package/dist/types/session/session-transcript-header.d.ts +2 -0
  91. package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
  92. package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
  93. package/dist/types/session/task-artifact-owner.d.ts +10 -0
  94. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  95. package/dist/types/setup/credential-import.d.ts +6 -4
  96. package/dist/types/skill-state/active-state.d.ts +38 -0
  97. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  98. package/dist/types/task/executor.d.ts +3 -0
  99. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  100. package/dist/types/tools/index.d.ts +3 -0
  101. package/dist/types/tools/python.d.ts +9 -1
  102. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  103. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  104. package/dist/types/tools/todo-write.d.ts +6 -0
  105. package/dist/types/utils/tools-checksum.d.ts +7 -0
  106. package/package.json +7 -7
  107. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  108. package/scripts/resident-memory-bench.ts +174 -20
  109. package/scripts/verify-managed-empty-stop.ts +28 -0
  110. package/src/async/job-manager.ts +228 -122
  111. package/src/capability/index.ts +18 -0
  112. package/src/capability/mcp.ts +2 -0
  113. package/src/capability/skill.ts +2 -0
  114. package/src/capability/types.ts +6 -0
  115. package/src/cli/auth-broker-cli.ts +1 -1
  116. package/src/cli/config-cli.ts +8 -18
  117. package/src/cli/customize-doctor.ts +15 -2
  118. package/src/cli/public-command-errors.ts +21 -1
  119. package/src/cli/setup-cli.ts +4 -4
  120. package/src/cli/skills-cli.ts +5 -0
  121. package/src/cli-main.ts +7 -8
  122. package/src/commands/deep-interview.ts +47 -8
  123. package/src/commands/sdk.ts +140 -29
  124. package/src/config/autorouting-tier-map.ts +117 -7
  125. package/src/config/file-lock.ts +307 -68
  126. package/src/config/mcp-schema.json +4 -0
  127. package/src/config/model-bindings-applier.ts +20 -0
  128. package/src/config/model-profile-activation.ts +240 -46
  129. package/src/config/model-registry.ts +47 -153
  130. package/src/config/settings.ts +44 -4
  131. package/src/config/skill-settings-defaults.ts +7 -0
  132. package/src/coordinator-mcp/policy.ts +3 -2
  133. package/src/cursor-delete-path.ts +10 -0
  134. package/src/cursor-mcp-name.ts +15 -0
  135. package/src/cursor.ts +13 -2
  136. package/src/customization/inventory.ts +3 -0
  137. package/src/debug/report-bundle-parent.ts +22 -0
  138. package/src/debug/report-bundle.ts +3 -0
  139. package/src/debug/system-info.ts +13 -1
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  141. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  142. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  143. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  144. package/src/defaults/gjc-defaults.test.ts +18 -1
  145. package/src/defaults/gjc-defaults.ts +3 -0
  146. package/src/defaults/gjc-skills.generated.ts +58 -36
  147. package/src/discovery/agents.ts +6 -1
  148. package/src/discovery/builtin.ts +10 -0
  149. package/src/discovery/helpers.ts +168 -23
  150. package/src/discovery/mcp-json.ts +6 -0
  151. package/src/discovery/ssh.ts +9 -4
  152. package/src/discovery/substitute-plugin-root.ts +3 -1
  153. package/src/edit/file-read-cache.ts +1 -1
  154. package/src/eval/py/executor.ts +380 -120
  155. package/src/export/html/css-color.ts +9 -0
  156. package/src/export/html/index.ts +5 -4
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/extensibility/extensions/runner.ts +4 -0
  159. package/src/extensibility/extensions/types.ts +7 -0
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
  161. package/src/extensibility/plugins/manager.ts +3 -2
  162. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  163. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  164. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  165. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  166. package/src/extensibility/skill-management.ts +6 -2
  167. package/src/extensibility/skills.ts +6 -1
  168. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  169. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  170. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  171. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  172. package/src/gjc-runtime/gc-runtime.ts +206 -8
  173. package/src/gjc-runtime/launch-worktree.ts +14 -4
  174. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  175. package/src/gjc-runtime/state-argv.ts +2 -0
  176. package/src/gjc-runtime/state-migrations.ts +91 -29
  177. package/src/gjc-runtime/state-runtime.ts +3716 -135
  178. package/src/gjc-runtime/state-writer.ts +83 -8
  179. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  180. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  181. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  182. package/src/harness-control-plane/operate.ts +12 -4
  183. package/src/harness-control-plane/owner.ts +5 -1
  184. package/src/hashline/parser.ts +48 -9
  185. package/src/hashline/recovery.ts +13 -4
  186. package/src/hindsight/config.ts +9 -3
  187. package/src/internal-urls/docs-index.generated.ts +19 -18
  188. package/src/internal-urls/skill-path.ts +19 -0
  189. package/src/internal-urls/skill-protocol.ts +2 -0
  190. package/src/lsp/edits.ts +6 -0
  191. package/src/lsp/workspace-path.ts +51 -0
  192. package/src/main.ts +195 -31
  193. package/src/modes/acp/acp-agent.ts +163 -7
  194. package/src/modes/components/custom-editor.ts +14 -0
  195. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  196. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  197. package/src/modes/components/extensions/state-manager.ts +15 -5
  198. package/src/modes/components/model-selector.ts +1 -4
  199. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  200. package/src/modes/components/status-line/segments.ts +4 -0
  201. package/src/modes/controllers/event-controller.ts +11 -4
  202. package/src/modes/controllers/input-controller.ts +74 -29
  203. package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
  204. package/src/modes/controllers/selector-controller.ts +28 -15
  205. package/src/modes/interactive-mode.ts +4 -0
  206. package/src/modes/runtime-init.ts +4 -0
  207. package/src/modes/utils/abort-message.ts +3 -1
  208. package/src/modes/utils/interrupt-ownership.ts +12 -0
  209. package/src/modes/utils/keybinding-matchers.ts +4 -0
  210. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  211. package/src/progress/collect-project-progress.ts +153 -0
  212. package/src/progress/progress-contract.ts +172 -0
  213. package/src/progress/progress-snapshot.ts +140 -0
  214. package/src/progress/project-progress.ts +446 -0
  215. package/src/progress/render-progress.ts +209 -0
  216. package/src/prompts/system/master-mode.md +1 -0
  217. package/src/prompts/tools/skill-discovery.md +1 -1
  218. package/src/runtime-mcp/client.ts +13 -4
  219. package/src/runtime-mcp/config-writer.ts +10 -6
  220. package/src/runtime-mcp/config.ts +1 -0
  221. package/src/runtime-mcp/elicitation.ts +142 -0
  222. package/src/runtime-mcp/oauth-flow.ts +5 -0
  223. package/src/runtime-mcp/oauth-public-url.ts +8 -0
  224. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  225. package/src/runtime-mcp/smithery-registry.ts +22 -3
  226. package/src/runtime-mcp/transports/http.ts +2 -2
  227. package/src/runtime-mcp/types.ts +6 -0
  228. package/src/sdk/acp/adapter.ts +93 -8
  229. package/src/sdk/broker/broker-exit.ts +52 -23
  230. package/src/sdk/broker/broker.ts +83 -7
  231. package/src/sdk/broker/ensure.ts +2 -0
  232. package/src/sdk/broker/lifecycle.ts +820 -73
  233. package/src/sdk/broker/session-index.ts +191 -81
  234. package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
  235. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  236. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
  238. package/src/sdk/bus/index.ts +241 -46
  239. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  240. package/src/sdk/bus/telegram-daemon.ts +4 -2
  241. package/src/sdk/cli/session-cli.ts +17 -3
  242. package/src/sdk/client/client.ts +28 -8
  243. package/src/sdk/host/host.ts +6 -0
  244. package/src/sdk/host/query/handlers.ts +9 -1
  245. package/src/sdk/host/session-runtime.test.ts +2805 -411
  246. package/src/sdk/host/session-runtime.ts +428 -111
  247. package/src/sdk/host/surface-policy.ts +1 -0
  248. package/src/sdk/index.ts +8 -0
  249. package/src/sdk/lifecycle-session.ts +3 -1
  250. package/src/sdk/mcp/server.ts +15 -3
  251. package/src/sdk/prompt-deadline-manager.ts +15 -0
  252. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  253. package/src/sdk/protocol/operation-registry.ts +9 -2
  254. package/src/sdk/router/session-router.ts +94 -14
  255. package/src/sdk/session-list.ts +1 -0
  256. package/src/sdk/session.ts +81 -38
  257. package/src/session/agent-session.ts +674 -224
  258. package/src/session/artifacts.ts +174 -70
  259. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  260. package/src/session/internal/managed-session-scope.ts +3457 -462
  261. package/src/session/internal/managed-session-storage.ts +482 -61
  262. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  263. package/src/session/internal/task-artifact-owner-access.ts +245 -0
  264. package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
  265. package/src/session/session-manager.ts +939 -133
  266. package/src/session/session-retirement.ts +714 -0
  267. package/src/session/session-storage.ts +418 -174
  268. package/src/session/session-transcript-header.ts +17 -0
  269. package/src/session/task-artifact-owner-codec.ts +641 -0
  270. package/src/session/task-artifact-owner-retirement.ts +477 -0
  271. package/src/session/task-artifact-owner.ts +289 -0
  272. package/src/setup/credential-auto-import.ts +12 -3
  273. package/src/setup/credential-import.ts +176 -5
  274. package/src/setup/paseo/announce.ts +6 -1
  275. package/src/skill-state/active-state.ts +132 -2
  276. package/src/slash-commands/builtin-registry.ts +39 -9
  277. package/src/slash-commands/helpers/progress-report.ts +23 -0
  278. package/src/stt/setup.ts +3 -5
  279. package/src/task/commands.ts +3 -1
  280. package/src/task/executor.ts +4 -0
  281. package/src/task/index.ts +6 -0
  282. package/src/tools/ask.ts +205 -6
  283. package/src/tools/ast-edit.ts +4 -0
  284. package/src/tools/bash-allowed-prefixes.ts +15 -0
  285. package/src/tools/bash-skill-urls.ts +56 -3
  286. package/src/tools/bash.ts +48 -79
  287. package/src/tools/descriptors.ts +5 -1
  288. package/src/tools/eval.ts +56 -36
  289. package/src/tools/image-gen.ts +1 -1
  290. package/src/tools/index.ts +3 -0
  291. package/src/tools/python.ts +187 -66
  292. package/src/tools/recipe/index.ts +9 -0
  293. package/src/tools/recipe/recipe-guard.ts +19 -0
  294. package/src/tools/restricted-cwd.ts +10 -0
  295. package/src/tools/skill-discovery.ts +5 -0
  296. package/src/tools/skill.ts +28 -1
  297. package/src/tools/todo-write.ts +10 -1
  298. package/src/tools/tool-catalog.generated.ts +1 -1
  299. package/src/tools/write.ts +1 -0
  300. package/src/utils/tools-checksum.ts +34 -0
  301. package/src/utils/tools-manager.ts +24 -6
  302. package/src/web/search/providers/codex.ts +3 -2
@@ -13,6 +13,7 @@ import {
13
13
  type Context,
14
14
  codexContextOverrideKey,
15
15
  createModelManager,
16
+ createTrustedStrippedModelClone,
16
17
  Effort,
17
18
  enrichModelThinking,
18
19
  getBundledModels,
@@ -49,6 +50,8 @@ import {
49
50
  readBoundedModelsJson,
50
51
  resolveLoopbackOpenAIBaseUrl,
51
52
  } from "@gajae-code/ai/utils/discovery/openai-compatible";
53
+ // Internal-only: registerFinalizedModelClone is not part of the public @gajae-code/ai surface
54
+ import { registerFinalizedModelClone } from "@gajae-code/ai/utils/trusted-model-clone";
52
55
 
53
56
  // Sentinels for local-only OAuth tokens — declared inline to avoid loading provider
54
57
  // modules at startup. Must match the provider OAuth modules.
@@ -1771,9 +1774,6 @@ export class ModelRegistry {
1771
1774
  {
1772
1775
  fresh: boolean;
1773
1776
  modelIds: ReadonlySet<string>;
1774
- profileModelIds?: ReadonlySet<string>;
1775
- profileFresh?: boolean;
1776
- profileEndpoint?: string;
1777
1777
  authGeneration: string;
1778
1778
  endpoint: string;
1779
1779
  }
@@ -2746,20 +2746,7 @@ export class ModelRegistry {
2746
2746
  }));
2747
2747
  }
2748
2748
  #stripModelBaseUrlQueries(models: readonly Model<Api>[]): Model<Api>[] {
2749
- return models.map(model => {
2750
- if (!model.baseUrl) return model;
2751
- try {
2752
- const parsed = new URL(model.baseUrl);
2753
- parsed.username = "";
2754
- parsed.password = "";
2755
- parsed.search = "";
2756
- parsed.hash = "";
2757
- return { ...model, baseUrl: parsed.toString().replace(/\/$/, "") };
2758
- } catch {
2759
- const { baseUrl: _baseUrl, ...withoutBaseUrl } = model;
2760
- return withoutBaseUrl as Model<Api>;
2761
- }
2762
- });
2749
+ return models.map(model => createTrustedStrippedModelClone(model));
2763
2750
  }
2764
2751
  #stripUrlUserinfo(url: string | undefined): string | undefined {
2765
2752
  if (!url) return url;
@@ -3288,6 +3275,11 @@ export class ModelRegistry {
3288
3275
  result.provider,
3289
3276
  this.#providerEvidenceApiKeys.get(result.provider),
3290
3277
  ) &&
3278
+ result.configurationGeneration ===
3279
+ this.authStorage.getProviderConfigurationGeneration(
3280
+ result.provider,
3281
+ this.#authStorageConfigOwner,
3282
+ ) &&
3291
3283
  result.endpoint ===
3292
3284
  this.#normalizeDiscoveryEvidenceEndpoint(
3293
3285
  this.#effectiveDiscoveryProviderConfig(providerConfig).baseUrl ?? "",
@@ -3309,7 +3301,10 @@ export class ModelRegistry {
3309
3301
  this.#providerEvidenceApiKeys.get(result.provider),
3310
3302
  ) ||
3311
3303
  result.configurationGeneration !==
3312
- this.authStorage.getProviderConfigurationGeneration(result.provider) ||
3304
+ this.authStorage.getProviderConfigurationGeneration(
3305
+ result.provider,
3306
+ this.#authStorageConfigOwner,
3307
+ ) ||
3313
3308
  result.endpoint !==
3314
3309
  this.#normalizeDiscoveryEvidenceEndpoint(
3315
3310
  this.#effectiveDiscoveryProviderConfig(providerConfig).baseUrl ?? "",
@@ -3334,9 +3329,6 @@ export class ModelRegistry {
3334
3329
  | {
3335
3330
  fresh: boolean;
3336
3331
  modelIds: ReadonlySet<string>;
3337
- profileModelIds?: ReadonlySet<string>;
3338
- profileFresh?: boolean;
3339
- profileEndpoint?: string;
3340
3332
  authGeneration: string;
3341
3333
  endpoint: string;
3342
3334
  }
@@ -3481,12 +3473,9 @@ export class ModelRegistry {
3481
3473
  .map(([provider, evidence]) => [
3482
3474
  provider,
3483
3475
  evidence.fresh,
3484
- evidence.profileFresh,
3485
3476
  evidence.authGeneration,
3486
3477
  evidence.endpoint,
3487
- evidence.profileEndpoint,
3488
3478
  [...evidence.modelIds].sort(),
3489
- evidence.profileModelIds === undefined ? undefined : [...evidence.profileModelIds].sort(),
3490
3479
  ]),
3491
3480
  configured: [...this.#configuredDiscoveryEvidence.entries()]
3492
3481
  .sort(([left], [right]) => left.localeCompare(right))
@@ -3540,7 +3529,10 @@ export class ModelRegistry {
3540
3529
  ? this.authStorage.has(provider) ||
3541
3530
  this.authStorage.hasAuth(provider, undefined, { owner: this.#authStorageConfigOwner })
3542
3531
  : !this.#isCredentiallessProvider(provider);
3543
- let preflightAuthConfigurationGeneration = this.authStorage.getProviderConfigurationGeneration(provider);
3532
+ let preflightAuthConfigurationGeneration = this.authStorage.getProviderConfigurationGeneration(
3533
+ provider,
3534
+ this.#authStorageConfigOwner,
3535
+ );
3544
3536
  let preflightOAuthRefreshGeneration = this.authStorage.getProviderOAuthRefreshGeneration(provider);
3545
3537
  if (shouldPreflightAuth) {
3546
3538
  if (optionalAuth && isCurrentPreflight()) this.#credentiallessAuthFallbackProviders.delete(provider);
@@ -3555,7 +3547,10 @@ export class ModelRegistry {
3555
3547
  credentialSessionId,
3556
3548
  }),
3557
3549
  );
3558
- const currentAuthConfigurationGeneration = this.authStorage.getProviderConfigurationGeneration(provider);
3550
+ const currentAuthConfigurationGeneration = this.authStorage.getProviderConfigurationGeneration(
3551
+ provider,
3552
+ this.#authStorageConfigOwner,
3553
+ );
3559
3554
  if (preflightAuthConfigurationGeneration !== currentAuthConfigurationGeneration) {
3560
3555
  const currentOAuthRefreshGeneration = this.authStorage.getProviderOAuthRefreshGeneration(provider);
3561
3556
  if (
@@ -3633,6 +3628,9 @@ export class ModelRegistry {
3633
3628
  const isCurrentEndpoint = () =>
3634
3629
  endpoint ===
3635
3630
  this.#normalizeDiscoveryEvidenceEndpoint(this.#effectiveDiscoveryProviderConfig(providerConfig).baseUrl ?? "");
3631
+ const isCurrentAuthConfiguration = () =>
3632
+ preflightAuthConfigurationGeneration ===
3633
+ this.authStorage.getProviderConfigurationGeneration(provider, this.#authStorageConfigOwner);
3636
3634
  const isCurrentProviderRefresh = () =>
3637
3635
  providerRefresh === undefined ||
3638
3636
  ("providerId" in providerRefresh
@@ -3704,7 +3702,7 @@ export class ModelRegistry {
3704
3702
  },
3705
3703
  getEvidenceGeneration: provider => this.#getProviderEvidenceGeneration(provider.provider, preflightApiKey),
3706
3704
  cacheDynamicModelProvenance: cacheLookupProvenance,
3707
- canPublishCache: () => isCurrentEndpoint() && isCurrentProviderRefresh(),
3705
+ canPublishCache: () => isCurrentAuthConfiguration() && isCurrentEndpoint() && isCurrentProviderRefresh(),
3708
3706
  });
3709
3707
  const authGeneration =
3710
3708
  mergeInput.authGeneration ??
@@ -3712,6 +3710,7 @@ export class ModelRegistry {
3712
3710
  const current =
3713
3711
  mergeInput.current &&
3714
3712
  authGeneration === this.#getProviderEvidenceGeneration(effectiveProviderConfig.provider, preflightApiKey) &&
3713
+ isCurrentAuthConfiguration() &&
3715
3714
  isCurrentEndpoint();
3716
3715
  if (!current) {
3717
3716
  return {
@@ -3899,9 +3898,15 @@ export class ModelRegistry {
3899
3898
  // The token is only needed if the dynamic fetch fires (cache miss),
3900
3899
  // and failures there are handled gracefully.
3901
3900
  const peekKey = async (descriptor: { providerId: string }) => {
3902
- const configurationGeneration = this.authStorage.getProviderConfigurationGeneration(descriptor.providerId);
3901
+ const configurationGeneration = this.authStorage.getProviderConfigurationGeneration(
3902
+ descriptor.providerId,
3903
+ this.#authStorageConfigOwner,
3904
+ );
3903
3905
  const apiKey = await this.#peekApiKeyForProvider(descriptor.providerId, { credentialSessionId });
3904
- if (configurationGeneration !== this.authStorage.getProviderConfigurationGeneration(descriptor.providerId)) {
3906
+ if (
3907
+ configurationGeneration !==
3908
+ this.authStorage.getProviderConfigurationGeneration(descriptor.providerId, this.#authStorageConfigOwner)
3909
+ ) {
3905
3910
  return { apiKey: undefined, authGeneration: undefined };
3906
3911
  }
3907
3912
  return {
@@ -4071,13 +4076,6 @@ export class ModelRegistry {
4071
4076
  this.#descriptorDiscoveryEvidence.set(options.providerId, {
4072
4077
  fresh: result.fetched,
4073
4078
  modelIds: new Set(models.map(model => model.id)),
4074
- ...(result.dynamicModelIds === undefined
4075
- ? {}
4076
- : {
4077
- profileModelIds: new Set(result.dynamicModelIds),
4078
- profileFresh: !result.stale,
4079
- profileEndpoint: endpoint,
4080
- }),
4081
4079
  authGeneration,
4082
4080
  endpoint: this.#normalizeDiscoveryEvidenceEndpoint(models[0]?.baseUrl ?? endpoint),
4083
4081
  });
@@ -5005,6 +5003,14 @@ export class ModelRegistry {
5005
5003
  }
5006
5004
  const generated = this.#generatedAuthHeaders.get(models[index]!);
5007
5005
  if (generated) this.#generatedAuthHeaders.set(result[index]!, generated);
5006
+ // Register finalized clone as trusted if the original is trusted
5007
+ // registerFinalizedModelClone builds a fresh trusted object from the original's snapshot
5008
+ if (result[index]) {
5009
+ const registeredClone = registerFinalizedModelClone(models[index]!, result[index]);
5010
+ if (registeredClone) {
5011
+ result[index] = registeredClone;
5012
+ }
5013
+ }
5008
5014
  }
5009
5015
  return result;
5010
5016
  }
@@ -5648,9 +5654,10 @@ export class ModelRegistry {
5648
5654
 
5649
5655
  /**
5650
5656
  * Get selectable models with auth configured.
5651
- * This is a fast check that doesn't refresh OAuth tokens. A current,
5652
- * authoritative live catalog also limits each provider to its enrolled ids;
5653
- * bundled entries remain the fallback until that evidence exists.
5657
+ * This is a fast check that doesn't refresh OAuth tokens. The bundled catalog
5658
+ * is authoritative for visibility: live discovery only enriches entries and
5659
+ * adds unknown ids, and never removes a bundled model. A provider that cannot
5660
+ * serve a model reports its own error when the model is used.
5654
5661
  */
5655
5662
  getAvailable(): Model<Api>[] {
5656
5663
  this.#synchronizeEnvironmentCredentials();
@@ -5664,82 +5671,12 @@ export class ModelRegistry {
5664
5671
  ) {
5665
5672
  return this.#availableModelsCache;
5666
5673
  }
5667
- const authoritativeDiscoveryIds = new Map<string, ReadonlySet<string> | undefined>();
5668
- const bundledIdsByProvider = new Map<string, Set<string>>();
5669
- this.#availableModelsCache = this.#models.filter(model => {
5670
- if (!this.#isModelAvailable(model, disabledProviders)) return false;
5671
-
5672
- let liveIds = authoritativeDiscoveryIds.get(model.provider);
5673
- if (!authoritativeDiscoveryIds.has(model.provider)) {
5674
- liveIds = this.#getAuthoritativeDiscoveredModelIds(model.provider);
5675
- authoritativeDiscoveryIds.set(model.provider, liveIds);
5676
- }
5677
- // Undefined means discovery has not run successfully for the current
5678
- // provider context. Keep the bundled catalog as the explicit fallback.
5679
- if (liveIds === undefined) return true;
5680
- if (this.#hasCustomModelOverlay(model.provider, model.id)) return true;
5681
-
5682
- const activity = this.#providerActivity.get(model.provider);
5683
- if (!activity?.staticModelIds.has(model.id)) return liveIds.has(model.id);
5684
-
5685
- let bundledIds = bundledIdsByProvider.get(model.provider);
5686
- if (!bundledIds) {
5687
- bundledIds = new Set(
5688
- (getBundledModels(model.provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[]).map(
5689
- candidate => candidate.id,
5690
- ),
5691
- );
5692
- bundledIdsByProvider.set(model.provider, bundledIds);
5693
- }
5694
- return !bundledIds.has(model.id) || liveIds.has(model.id);
5695
- });
5674
+ this.#availableModelsCache = this.#models.filter(model => this.#isModelAvailable(model, disabledProviders));
5696
5675
  this.#availableModelsDisabledProviders = disabledProviderKey;
5697
5676
  this.#availableModelsEnvFingerprint = envFingerprint;
5698
5677
  return this.#availableModelsCache;
5699
5678
  }
5700
5679
 
5701
- /**
5702
- * Return the current live model ids only when discovery produced an
5703
- * authoritative catalog for the provider and endpoint. An unavailable or
5704
- * failed discovery returns undefined so callers retain the bundled fallback.
5705
- */
5706
- #getAuthoritativeDiscoveredModelIds(provider: string): ReadonlySet<string> | undefined {
5707
- const descriptorEvidence = this.#descriptorDiscoveryEvidence.get(provider);
5708
- if (descriptorEvidence?.profileFresh && descriptorEvidence.profileModelIds !== undefined) {
5709
- try {
5710
- if (
5711
- descriptorEvidence.authGeneration === this.#getProviderEvidenceGeneration(provider) &&
5712
- descriptorEvidence.profileEndpoint ===
5713
- this.#normalizeDiscoveryEvidenceEndpoint(this.#getProviderBaseUrlForDiscovery(provider) ?? "")
5714
- ) {
5715
- return descriptorEvidence.profileModelIds;
5716
- }
5717
- } catch {
5718
- // A provider context that can no longer be verified must use its fallback.
5719
- }
5720
- }
5721
-
5722
- const configuredEvidence = this.#configuredDiscoveryEvidence.get(provider);
5723
- if (!configuredEvidence) return undefined;
5724
- const discoveryState = this.#discoveryManager.getState(provider);
5725
- if (
5726
- discoveryState?.status !== "ok" &&
5727
- discoveryState?.status !== "cached" &&
5728
- discoveryState?.status !== "empty"
5729
- ) {
5730
- return undefined;
5731
- }
5732
- try {
5733
- return configuredEvidence.authGeneration === this.#getProviderEvidenceGeneration(provider) &&
5734
- configuredEvidence.endpoint ===
5735
- this.#normalizeDiscoveryEvidenceEndpoint(this.#getProviderBaseUrlForDiscovery(provider) ?? "")
5736
- ? configuredEvidence.modelIds
5737
- : undefined;
5738
- } catch {
5739
- return undefined;
5740
- }
5741
- }
5742
-
5743
5680
  #synchronizeEnvironmentCredentials(): void {
5744
5681
  // Runtime registrations own a provider while their credential is present.
5745
5682
  // Refresh them first so a missing runtime env key can hand ownership back to
@@ -5753,49 +5690,6 @@ export class ModelRegistry {
5753
5690
  }
5754
5691
  }
5755
5692
 
5756
- /**
5757
- * Get authenticated models, excluding bundled entries that a fresh provider
5758
- * catalog has positively shown to be unavailable. Bundled entries remain
5759
- * usable until live catalog evidence exists so offline startup is unchanged.
5760
- */
5761
- getAvailableForProfileActivation(): Model<Api>[] {
5762
- const bundledIdsByProvider = new Map<string, Set<string>>();
5763
- // Evidence staleness depends only on the provider, and resolving it scans the
5764
- // catalog for the provider base URL. Decide it once per provider per call so a
5765
- // multi-thousand-model catalog is not rescanned for every model.
5766
- const staleEvidenceByProvider = new Map<string, boolean>();
5767
- return this.getAvailable().filter(model => {
5768
- const evidence = this.#descriptorDiscoveryEvidence.get(model.provider);
5769
- if (!evidence?.profileFresh || evidence.profileModelIds === undefined) return true;
5770
- if (this.#hasCustomModelOverlay(model.provider, model.id)) return true;
5771
- let bundledModelIds = bundledIdsByProvider.get(model.provider);
5772
- if (!bundledModelIds) {
5773
- bundledModelIds = new Set(
5774
- (getBundledModels(model.provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[]).map(
5775
- candidate => candidate.id,
5776
- ),
5777
- );
5778
- bundledIdsByProvider.set(model.provider, bundledModelIds);
5779
- }
5780
- if (!bundledModelIds.has(model.id)) return true;
5781
- let staleEvidence = staleEvidenceByProvider.get(model.provider);
5782
- if (staleEvidence === undefined) {
5783
- staleEvidence =
5784
- evidence.authGeneration !== this.#getProviderEvidenceGeneration(model.provider) ||
5785
- evidence.profileEndpoint !==
5786
- this.#normalizeDiscoveryEvidenceEndpoint(this.#getProviderBaseUrlForDiscovery(model.provider) ?? "");
5787
- staleEvidenceByProvider.set(model.provider, staleEvidence);
5788
- }
5789
- if (staleEvidence) return true;
5790
- return evidence.profileModelIds.has(model.id);
5791
- });
5792
- }
5793
-
5794
- #hasCustomModelOverlay(provider: string, id: string): boolean {
5795
- const matches = (overlay: CustomModelOverlay) => overlay.provider === provider && overlay.id === id;
5796
- return this.#customModelOverlays.some(matches) || this.#runtimeModelOverlays.some(matches);
5797
- }
5798
-
5799
5693
  #hasFreshOrStaticModelEvidence(model: Model<Api>): boolean {
5800
5694
  const evidence = this.#providerActivity.get(model.provider);
5801
5695
  if (
@@ -1104,6 +1104,41 @@ export class Settings implements NotificationSettingsReader {
1104
1104
  if (saveError !== undefined) throw saveError;
1105
1105
  }
1106
1106
 
1107
+ /** Copy execution settings without reloading, persisting, or sharing mutable layers. */
1108
+ snapshot(): Settings {
1109
+ const cloned = new Settings({ cwd: this.#cwd, agentDir: this.#agentDir, inMemory: true });
1110
+ cloned.#global = structuredClone(this.#global);
1111
+ cloned.#project = structuredClone(this.#project);
1112
+ cloned.#overrides = structuredClone(this.#overrides);
1113
+ cloned.#merged = structuredClone(this.#merged);
1114
+ cloned.#schemaReport = structuredClone(this.#schemaReport);
1115
+ cloned.#autoroutingEffective = structuredClone(this.#autoroutingEffective);
1116
+ cloned.#autoroutingLocalIssues = structuredClone(this.#autoroutingLocalIssues);
1117
+ cloned.#schemaMigrationPending = this.#schemaMigrationPending;
1118
+ cloned.#futureSchemaVersion = this.#futureSchemaVersion;
1119
+ cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
1120
+ cloned.#hasRecoveredConfigSyntax = this.#hasRecoveredConfigSyntax;
1121
+ cloned.#hasInvalidNotificationGlobal = this.#hasInvalidNotificationGlobal;
1122
+ cloned.#notificationValidationGeneration = this.#notificationValidationGeneration;
1123
+ cloned.#rawNotificationConfig = structuredClone(this.#rawNotificationConfig);
1124
+ cloned.#durableRawNotificationConfig = structuredClone(this.#durableRawNotificationConfig);
1125
+ cloned.#durableNotificationFingerprint = this.#durableNotificationFingerprint;
1126
+ return cloned;
1127
+ }
1128
+
1129
+ /** Resolve a retained execution's project layer without persistence or global hooks. */
1130
+ async snapshotForCwd(cwd: string): Promise<Settings> {
1131
+ const cloned = this.snapshot();
1132
+ if (path.resolve(cwd) === path.resolve(this.#cwd)) return cloned;
1133
+ cloned.#cwd = path.resolve(cwd);
1134
+ cloned.#readonly = true;
1135
+ cloned.#project = {};
1136
+ cloned.#rebuildMerged();
1137
+ cloned.#project = await cloned.#loadProjectSettings(true);
1138
+ await cloned.#normalizeAfterLoad(false);
1139
+ return cloned;
1140
+ }
1141
+
1107
1142
  async cloneForCwd(cwd: string): Promise<Settings> {
1108
1143
  // A clone shares the same config queue. Settle an already-reserved local
1109
1144
  // debounce before the clone can enqueue a durable selector, preventing it
@@ -1572,9 +1607,14 @@ export class Settings implements NotificationSettingsReader {
1572
1607
  return reconciled.settings;
1573
1608
  }
1574
1609
 
1575
- async #loadProjectSettings(): Promise<RawSettings> {
1610
+ async #loadProjectSettings(bypassCache = false): Promise<RawSettings> {
1576
1611
  try {
1577
- const result = await loadCapability(settingsCapability.id, { cwd: this.#cwd, settings: this });
1612
+ const result = await loadCapability(settingsCapability.id, {
1613
+ cwd: this.#cwd,
1614
+ agentDir: this.#agentDir,
1615
+ settings: this,
1616
+ bypassCache,
1617
+ });
1578
1618
  let merged: RawSettings = {};
1579
1619
  for (const item of result.items as SettingsCapabilityItem[]) {
1580
1620
  if (item.level !== "project") continue;
@@ -1637,7 +1677,7 @@ export class Settings implements NotificationSettingsReader {
1637
1677
  return settings;
1638
1678
  }
1639
1679
 
1640
- async #normalizeAfterLoad(): Promise<void> {
1680
+ async #normalizeAfterLoad(publishHooks = true): Promise<void> {
1641
1681
  this.#sanitizeModelSelectorRecords();
1642
1682
  this.#rebuildMerged();
1643
1683
  if (!this.#futureSchemaVersion) {
@@ -1653,7 +1693,7 @@ export class Settings implements NotificationSettingsReader {
1653
1693
  await this.flush();
1654
1694
  this.#sanitizeModelSelectorRecords();
1655
1695
  this.#rebuildMerged();
1656
- this.#fireAllHooks();
1696
+ if (publishHooks) this.#fireAllHooks();
1657
1697
  }
1658
1698
 
1659
1699
  #sanitizeModelSelectorRecords(): void {
@@ -51,6 +51,13 @@ export function resolveSkillScopeTrust(
51
51
  return DEFAULT_SKILL_SCOPE_TRUST;
52
52
  }
53
53
 
54
+ /** Resolve permission to read a user's external skill symlinks from user-owned config only. */
55
+ export function resolveGlobalUserSkillLinkTrust(
56
+ settings: Pick<SkillDiscoverySettings, "trustUserSkills" | "enablePiUser">,
57
+ ): boolean {
58
+ return resolveSkillScopeTrust(settings, "user");
59
+ }
60
+
54
61
  export const DEFAULT_SKILL_DISCOVERY_SETTINGS: SkillDiscoverySettings = {
55
62
  enabled: true,
56
63
  enableSkillCommands: true,
@@ -122,8 +122,9 @@ function parseRootList(value: string | undefined): string[] {
122
122
  }
123
123
 
124
124
  function resolveManagedWorktreeRoot(root: string, configured: string | undefined): string {
125
- const template = (configured?.trim() || "{repo}/.worktrees").replace(/^~(?=\/|$)/, os.homedir());
126
- const resolved = template.replaceAll("{repo}", path.basename(root));
125
+ // Function replacers so a `$` in the home or repository path is kept verbatim.
126
+ const template = (configured?.trim() || "{repo}/.worktrees").replace(/^~(?=\/|$)/, () => os.homedir());
127
+ const resolved = template.replaceAll("{repo}", () => path.basename(root));
127
128
  return path.resolve(path.isAbsolute(resolved) ? resolved : path.join(path.dirname(root), resolved));
128
129
  }
129
130
 
@@ -0,0 +1,10 @@
1
+ import * as fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export async function assertDeleteInsideWorkspace(cwd: string, filePath: string): Promise<void> {
5
+ const root = await fs.realpath(cwd);
6
+ const target = await fs.realpath(filePath);
7
+ if (target !== root && !target.startsWith(`${root}${path.sep}`)) {
8
+ throw new Error(`Delete escapes the workspace: ${filePath}`);
9
+ }
10
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Tools Cursor already runs on a dedicated exec channel.
3
+ * Keep this aligned with CURSOR_NATIVE_TOOL_NAMES in packages/ai/src/providers/cursor.ts.
4
+ */
5
+ const CURSOR_DEDICATED_CHANNEL_TOOLS = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp", "todo_write"]);
6
+
7
+ /**
8
+ * Name to dispatch from a Cursor MCP call.
9
+ * Dedicated-channel tools are refused here. Advertised session tools such as eval stay allowed.
10
+ */
11
+ export function cursorMcpDispatchName(call: { toolName?: string; name?: string }): string | null {
12
+ const toolName = call.toolName || call.name || "";
13
+ if (toolName.length === 0 || CURSOR_DEDICATED_CHANNEL_TOOLS.has(toolName)) return null;
14
+ return toolName;
15
+ }
package/src/cursor.ts CHANGED
@@ -23,6 +23,8 @@ import {
23
23
  piTimeout,
24
24
  } from "@gajae-code/ai/providers/cursor/exec-modern";
25
25
  import { sanitizeText } from "@gajae-code/utils";
26
+ import { assertDeleteInsideWorkspace } from "./cursor-delete-path";
27
+ import { cursorMcpDispatchName } from "./cursor-mcp-name";
26
28
  import { resolveToCwd } from "./tools/path-utils";
27
29
 
28
30
  /**
@@ -184,6 +186,7 @@ async function executeDelete(
184
186
  throw new Error(`Path is not a file: ${pathArg}`);
185
187
  }
186
188
 
189
+ await assertDeleteInsideWorkspace(options.cwd, absolutePath);
187
190
  fs.rmSync(absolutePath);
188
191
 
189
192
  const sizeText = fileStat.size ? ` (${fileStat.size} bytes)` : "";
@@ -646,11 +649,19 @@ export class CursorExecHandlers implements ICursorExecHandlers {
646
649
 
647
650
  async mcp(call: CursorMcpCall, signal?: AbortSignal, markNonAbortable?: () => void) {
648
651
  const options = this.#optionsForCall();
649
- const toolName = call.toolName || call.name;
652
+ const requestedName = call.toolName || call.name || "";
653
+ const toolName = cursorMcpDispatchName(call);
650
654
  const toolCallId = decodeToolCallId(call.toolCallId);
655
+ const availableTools = Array.from(options.tools.keys()).filter(
656
+ name => cursorMcpDispatchName({ toolName: name }) !== null,
657
+ );
658
+ if (!toolName) {
659
+ const message = formatMcpToolErrorMessage(requestedName, availableTools);
660
+ const result = buildToolErrorResult(message);
661
+ return createToolResultMessage(toolCallId, requestedName || "mcp", result, true);
662
+ }
651
663
  const tool = options.tools.get(toolName);
652
664
  if (!tool) {
653
- const availableTools = Array.from(options.tools.keys()).filter(name => name.startsWith("mcp__"));
654
665
  const message = formatMcpToolErrorMessage(toolName, availableTools);
655
666
  const result = buildToolErrorResult(message);
656
667
  return createToolResultMessage(toolCallId, toolName, result, true);
@@ -35,6 +35,8 @@ export interface LoadCustomizationInventoryOptions {
35
35
  home?: string;
36
36
  /** Skill management policy snapshot (trust, include/ignore, disabledExtensions). */
37
37
  policy?: SkillManagementPolicy;
38
+ /** User-owned permission to follow external user skill symlinks. */
39
+ allowExternalUserSkillSymlinks?: boolean;
38
40
  /** Disabled extension ids (`skill:<name>` / `mcp:<name>`) from settings. */
39
41
  disabledExtensions?: string[];
40
42
  }
@@ -94,6 +96,7 @@ async function loadSkillRows(options: LoadCustomizationInventoryOptions, _warnin
94
96
  cwd: options.cwd,
95
97
  home: options.home,
96
98
  policy: options.policy,
99
+ allowExternalUserSkillSymlinks: options.allowExternalUserSkillSymlinks,
97
100
  });
98
101
  const managedPaths = new Set<string>();
99
102
  for (const record of records) {
@@ -0,0 +1,22 @@
1
+ /** True when a session header names this parent file or its basename. */
2
+ export function sessionBelongsToParent(headerLine: string, parentBasename: string): boolean {
3
+ let parentSession: unknown;
4
+ try {
5
+ parentSession = (JSON.parse(headerLine) as { parentSession?: unknown }).parentSession;
6
+ } catch {
7
+ return false;
8
+ }
9
+ if (typeof parentSession !== "string" || parentSession.length === 0) return false;
10
+ const normalized = parentSession.replaceAll("\\", "/");
11
+ const base =
12
+ normalized
13
+ .split("/")
14
+ .pop()
15
+ ?.replace(/\.jsonl$/, "") ?? "";
16
+ return (
17
+ normalized === parentBasename ||
18
+ base === parentBasename ||
19
+ normalized.endsWith(`/${parentBasename}`) ||
20
+ normalized.endsWith(`/${parentBasename}.jsonl`)
21
+ );
22
+ }
@@ -17,6 +17,7 @@ import {
17
17
  resolveEquivalentPath,
18
18
  } from "@gajae-code/utils";
19
19
  import type { CpuProfile, HeapSnapshot } from "./profiler";
20
+ import { sessionBelongsToParent } from "./report-bundle-parent";
20
21
  import { collectSystemInfo, sanitizeEnv } from "./system-info";
21
22
 
22
23
  /** Maximum number of log lines to load into memory at once. */
@@ -283,6 +284,8 @@ async function addSubagentSessions(
283
284
  const archivePath = `subagents/${filename}`;
284
285
  try {
285
286
  const content = await Bun.file(filePath).text();
287
+ const headerLine = content.split("\n", 1)[0] ?? "";
288
+ if (!sessionBelongsToParent(headerLine, parentBasename)) continue;
286
289
  data[archivePath] = content;
287
290
  files.push(archivePath);
288
291
 
@@ -99,7 +99,19 @@ export function formatSystemInfo(info: SystemInfo): string {
99
99
 
100
100
  /** Sanitize environment variables by redacting sensitive values */
101
101
  export function sanitizeEnv(env: Record<string, string | undefined>): Record<string, string> {
102
- const SENSITIVE_PATTERNS = [/key/i, /secret/i, /token/i, /pass/i, /auth/i, /credential/i, /api/i, /private/i];
102
+ const SENSITIVE_PATTERNS = [
103
+ /key/i,
104
+ /secret/i,
105
+ /token/i,
106
+ /pass/i,
107
+ /auth/i,
108
+ /credential/i,
109
+ /api/i,
110
+ /private/i,
111
+ /cookie/i,
112
+ /dsn/i,
113
+ /url$/i,
114
+ ];
103
115
 
104
116
  const result: Record<string, string> = {};
105
117
  for (const [k, v] of Object.entries(env)) {