@gajae-code/coding-agent 0.18.8 → 0.18.9

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 (113) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/types/async/endpoint-id.d.ts +16 -0
  3. package/dist/types/async/support.d.ts +0 -14
  4. package/dist/types/commands/mcp-serve.d.ts +4 -0
  5. package/dist/types/discovery/mcp-json.d.ts +1 -0
  6. package/dist/types/eval/py/kernel.d.ts +18 -0
  7. package/dist/types/export/html/html-export-destination.d.ts +2 -0
  8. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
  9. package/dist/types/extensibility/skill-management.d.ts +1 -0
  10. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
  11. package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
  12. package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
  13. package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
  14. package/dist/types/lsp/config.d.ts +8 -5
  15. package/dist/types/runtime-mcp/manager.d.ts +2 -1
  16. package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
  17. package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -1
  18. package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
  19. package/dist/types/sdk/broker/broker.d.ts +6 -1
  20. package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
  21. package/dist/types/sdk/broker/ensure.d.ts +14 -1
  22. package/dist/types/session/session-storage.d.ts +7 -0
  23. package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
  24. package/dist/types/task/isolation-dir.d.ts +26 -0
  25. package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
  26. package/dist/types/tools/browser/cdp-url.d.ts +2 -0
  27. package/dist/types/tools/github-side-effect.d.ts +22 -0
  28. package/dist/types/tools/monitor-mutation.d.ts +9 -0
  29. package/package.json +7 -7
  30. package/src/async/endpoint-id.ts +26 -0
  31. package/src/async/job-manager.ts +19 -10
  32. package/src/async/support.ts +0 -25
  33. package/src/capability/index.ts +14 -16
  34. package/src/cli/doctor/repairs.ts +4 -1
  35. package/src/cli/doctor-supervisor.ts +3 -0
  36. package/src/cli/public-command-registry.ts +28 -6
  37. package/src/commands/mcp-serve.ts +7 -0
  38. package/src/commands/sdk.ts +453 -415
  39. package/src/config/file-lock.ts +9 -11
  40. package/src/config/model-profile-activation.ts +5 -5
  41. package/src/coordinator-mcp/server.ts +2 -0
  42. package/src/customization/inventory.ts +1 -13
  43. package/src/dap/config.ts +2 -2
  44. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
  45. package/src/discovery/builtin.ts +3 -1
  46. package/src/discovery/helpers.ts +12 -9
  47. package/src/discovery/mcp-json.ts +20 -12
  48. package/src/eval/py/executor.ts +459 -77
  49. package/src/eval/py/kernel.ts +79 -13
  50. package/src/eval/py/tool-bridge.ts +4 -0
  51. package/src/export/html/html-export-destination.ts +16 -0
  52. package/src/export/html/index.ts +2 -6
  53. package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
  54. package/src/extensibility/extensions/runner.ts +11 -1
  55. package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
  56. package/src/extensibility/gjc-plugins/runtime-adapters.ts +27 -15
  57. package/src/extensibility/skill-management.ts +3 -0
  58. package/src/gjc-runtime/launch-worktree.ts +56 -5
  59. package/src/gjc-runtime/psmux-detect.ts +28 -4
  60. package/src/gjc-runtime/session-state-sidecar.ts +3 -2
  61. package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
  62. package/src/gjc-runtime/state-writer.ts +22 -8
  63. package/src/gjc-runtime/tmux-common.ts +8 -5
  64. package/src/internal-urls/docs-index.generated.ts +7 -7
  65. package/src/lsp/config.ts +12 -63
  66. package/src/memories/index.ts +88 -7
  67. package/src/modes/components/hook-editor.ts +1 -1
  68. package/src/modes/components/hook-selector.ts +1 -1
  69. package/src/modes/controllers/input-controller.ts +2 -2
  70. package/src/modes/controllers/runtime-mcp-command-controller.ts +21 -13
  71. package/src/modes/controllers/selector-controller.ts +3 -7
  72. package/src/modes/controllers/todo-command-controller.ts +1 -1
  73. package/src/modes/interactive-mode.ts +1 -1
  74. package/src/runtime-mcp/client.ts +4 -2
  75. package/src/runtime-mcp/json-rpc.ts +2 -1
  76. package/src/runtime-mcp/manager.ts +29 -11
  77. package/src/runtime-mcp/mcp-redirect.ts +73 -0
  78. package/src/runtime-mcp/oauth-flow.ts +11 -4
  79. package/src/runtime-mcp/oauth-public-url.ts +8 -2
  80. package/src/runtime-mcp/pool.ts +6 -0
  81. package/src/runtime-mcp/project-host-values.ts +46 -0
  82. package/src/runtime-mcp/transports/http.ts +2 -1
  83. package/src/sdk/acp/adapter.ts +2 -0
  84. package/src/sdk/broker/broker.ts +6 -0
  85. package/src/sdk/broker/doctor-restart.ts +4 -0
  86. package/src/sdk/broker/ensure.ts +29 -2
  87. package/src/sdk/host/session-runtime.test.ts +58 -9
  88. package/src/sdk/host/session-runtime.ts +65 -35
  89. package/src/sdk/router/session-router.ts +7 -6
  90. package/src/sdk/session.ts +2 -7
  91. package/src/session/agent-session.ts +59 -17
  92. package/src/session/artifacts.ts +3 -2
  93. package/src/session/internal/managed-session-scope.ts +403 -81
  94. package/src/session/internal/managed-session-storage.ts +125 -17
  95. package/src/session/session-manager.ts +12 -0
  96. package/src/session/session-storage.ts +30 -1
  97. package/src/skill-state/active-state.ts +13 -0
  98. package/src/skill-state/workflow-mutation-guard.ts +102 -10
  99. package/src/slash-commands/builtin-registry.ts +1 -1
  100. package/src/slash-commands/helpers/mcp.ts +8 -4
  101. package/src/task/isolation-dir.ts +143 -0
  102. package/src/task/worktree.ts +16 -7
  103. package/src/tools/ast-edit-write-path.ts +68 -0
  104. package/src/tools/ast-edit.ts +7 -2
  105. package/src/tools/browser/cdp-url.ts +18 -0
  106. package/src/tools/browser.ts +8 -1
  107. package/src/tools/gh.ts +10 -0
  108. package/src/tools/github-side-effect.ts +112 -0
  109. package/src/tools/monitor-mutation.ts +27 -0
  110. package/src/tools/monitor.ts +7 -0
  111. package/src/tools/python.ts +7 -6
  112. package/src/utils/external-editor.ts +3 -2
  113. package/src/web/insane/url-guard.ts +2 -1
@@ -1368,7 +1368,6 @@ function ownerGenerationIsDead(owner: FileLockOwnerToken): boolean {
1368
1368
 
1369
1369
  /** Outcome of a guarded lock-dir removal attempt (`removeFileLockDirForGc`). */
1370
1370
  export type FileLockGcRemoval = "removed" | "owner_changed" | "missing" | "cleanup_failed";
1371
-
1372
1371
  type LockStaleSnapshot =
1373
1372
  | { stale: false }
1374
1373
  | { stale: true; owner: FileLockOwnerToken; identity: GenericFileLockDirIdentity };
@@ -1809,12 +1808,9 @@ async function staleLockSnapshot(
1809
1808
  throw error;
1810
1809
  }
1811
1810
  if (!info) {
1812
- // A directory without a valid owner record is either a contender between
1813
- // native directory ownership and metadata publication, or malformed state
1814
- // with no PID/incarnation/host proof of any process generation. Neither
1815
- // case carries liveness evidence, so elapsed time and byte stability must
1816
- // never make this namespace reclaimable: only an independently committed
1817
- // owner record proving its process generation dead authorizes removal.
1811
+ // An ownerless directory has no generation or liveness proof. Age and
1812
+ // emptiness cannot distinguish interrupted cleanup from a live legacy holder
1813
+ // or a paused acquisition, so every platform fails closed.
1818
1814
  return { stale: false };
1819
1815
  }
1820
1816
 
@@ -1857,8 +1853,6 @@ async function staleLockSnapshot(
1857
1853
  return { stale: false };
1858
1854
  }
1859
1855
 
1860
- type StaleLockRemovalAttempt = { removed: true } | { removed: false; failure?: FileLockStaleRemovalFailure };
1861
-
1862
1856
  type RecordedStaleRemovalFailure = {
1863
1857
  owner: FileLockOwnerToken;
1864
1858
  identity: GenericFileLockDirIdentity;
@@ -1902,10 +1896,12 @@ function manualLockCleanupCommand(lockPath: string): string {
1902
1896
  return `rm -rf -- '${quotedPath}'`;
1903
1897
  }
1904
1898
 
1899
+ type FileLockRemovalAttempt = { removed: true } | { removed: false; failure?: FileLockStaleRemovalFailure };
1900
+
1905
1901
  async function removeStaleLockForAcquire(
1906
1902
  lockPath: string,
1907
1903
  snapshot: LockStaleSnapshot,
1908
- ): Promise<StaleLockRemovalAttempt> {
1904
+ ): Promise<FileLockRemovalAttempt> {
1909
1905
  if (!snapshot.stale) return { removed: false };
1910
1906
  try {
1911
1907
  const outcome = await removeFileLockDirForGc(lockPath, snapshot.owner, snapshot.identity);
@@ -2132,7 +2128,9 @@ async function tryAcquireLock(
2132
2128
  }
2133
2129
  }
2134
2130
  if (!publishedSuccessfully) {
2135
- if (published.reason === "destination_exists") return null;
2131
+ if (published.reason === "destination_exists") {
2132
+ return null;
2133
+ }
2136
2134
  const failure = new Error(
2137
2135
  `Failed to publish file lock: ${published.code ?? published.reason ?? "unknown"}.`,
2138
2136
  ) as NodeJS.ErrnoException;
@@ -1820,6 +1820,10 @@ export async function applyPreparedModelProfileActivation(
1820
1820
  } catch (error) {
1821
1821
  const activationWasCancelled = error instanceof ModelProfileActivationSupersededError;
1822
1822
  const selectionSuperseded = options.isCurrent?.() === false;
1823
+ const shouldRestoreFallbackRuntimeState =
1824
+ prepared.previousDefaultFallbackRuntimeState !== undefined &&
1825
+ (!selectionSuperseded ||
1826
+ sameSerializedValue(prepared.session.getDefaultFallbackRuntimeState?.(), activatedFallbackRuntimeState));
1823
1827
  const rollbackErrors: Array<{ stage: string; error: unknown }> = [];
1824
1828
  const restore = (stage: string, action: () => void): void => {
1825
1829
  try {
@@ -1945,11 +1949,7 @@ export async function applyPreparedModelProfileActivation(
1945
1949
  ),
1946
1950
  );
1947
1951
  }
1948
- if (
1949
- prepared.previousDefaultFallbackRuntimeState &&
1950
- (!selectionSuperseded ||
1951
- sameSerializedValue(prepared.session.getDefaultFallbackRuntimeState?.(), activatedFallbackRuntimeState))
1952
- ) {
1952
+ if (shouldRestoreFallbackRuntimeState) {
1953
1953
  restore("restore fallback runtime", () =>
1954
1954
  prepared.session.restoreDefaultFallbackRuntimeState?.(prepared.previousDefaultFallbackRuntimeState!),
1955
1955
  );
@@ -210,6 +210,8 @@ type CoordinatorBrokerStage = "ensure" | "read" | "connect" | "request" | "close
210
210
  function toCoordinatorBrokerError(stage: CoordinatorBrokerStage, error: unknown): SdkClientError {
211
211
  if (stage === "request" && error instanceof SdkClientError) return error;
212
212
  if (stage === "ensure") {
213
+ // Attach-only mode refuses with the public unavailability code; keep it.
214
+ if (error instanceof SdkClientError && error.code === "broker_unavailable") return error;
213
215
  if (error instanceof AggregateError)
214
216
  return new SdkClientError(
215
217
  "broker_cleanup_unverified",
@@ -74,18 +74,6 @@ function redactServerForDisplay(server: Record<string, unknown>): Record<string,
74
74
  // Skills
75
75
  // ---------------------------------------------------------------------------
76
76
 
77
- async function readImportMarker(skillPath: string): Promise<string | null> {
78
- try {
79
- const content = await fs.readFile(skillPath, "utf-8");
80
- const { frontmatter } = parseFrontmatter(content, { level: "off" });
81
- // parseFrontmatter camelCases keys: x-gjc-imported-from → xGjcImportedFrom.
82
- const marker = frontmatter[IMPORTED_FROM_FRONTMATTER_KEY] ?? frontmatter.xGjcImportedFrom;
83
- return typeof marker === "string" ? marker : null;
84
- } catch {
85
- return null;
86
- }
87
- }
88
-
89
77
  function importedProductLabel(marker: string): string {
90
78
  return marker === "claude-code" ? "Claude Code" : marker === "codex" ? "Codex" : marker;
91
79
  }
@@ -102,7 +90,7 @@ async function loadSkillRows(options: LoadCustomizationInventoryOptions, _warnin
102
90
  for (const record of records) {
103
91
  managedPaths.add(path.resolve(record.path));
104
92
  const scope: GjcScope = record.scope === "project" ? "project" : "global";
105
- const marker = await readImportMarker(record.path);
93
+ const marker = record.importedFrom ?? null;
106
94
  let status: InventoryStatus;
107
95
  const diagnostics: string[] = [];
108
96
  if (options.policy?.enabled === false) {
package/src/dap/config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { isRecord } from "@gajae-code/utils";
3
- import { hasRootMarkers, resolveCommand } from "../lsp/config";
3
+ import { hasRootMarkers, resolveTrustedCommand } from "../lsp/config";
4
4
  import DEFAULTS from "./defaults.json" with { type: "json" };
5
5
  import type { DapAdapterConfig, DapResolvedAdapter } from "./types";
6
6
 
@@ -51,7 +51,7 @@ export function getAdapterConfigs(): Record<string, DapAdapterConfig> {
51
51
  export function resolveAdapter(adapterName: string, cwd: string): DapResolvedAdapter | null {
52
52
  const config = DEFAULT_ADAPTERS[adapterName];
53
53
  if (!config) return null;
54
- const resolvedCommand = resolveCommand(config.command, cwd);
54
+ const resolvedCommand = resolveTrustedCommand(config.command, cwd);
55
55
  if (!resolvedCommand) return null;
56
56
  return {
57
57
  name: adapterName,
@@ -7,6 +7,7 @@ import { Effort } from '@gajae-code/ai/model-thinking';
7
7
  import type { OAuthCredentials, OAuthLoginCallbacks } from '@gajae-code/ai/utils/oauth/types';
8
8
  import { loginXai, refreshXaiToken, XAI_OAUTH_SCOPE } from '@gajae-code/ai/utils/oauth/xai';
9
9
  import type { ExtensionAPI, ProviderConfig } from '@gajae-code/coding-agent';
10
+ import { tagProviderRequestHandler } from '../../../../../../extensibility/extensions/function-hooks-internal';
10
11
  import {
11
12
  type GrokCliModelConfig,
12
13
  getMaxReasoningEffort,
@@ -102,13 +103,16 @@ export default function registerGrokCli(api: ExtensionAPI) {
102
103
  }
103
104
  });
104
105
 
105
- api.on('before_provider_request', (event, ctx) => {
106
- if (ctx.model?.provider !== 'grok-build') return;
106
+ api.on(
107
+ 'before_provider_request',
108
+ tagProviderRequestHandler('grok-build', (event, ctx) => {
109
+ if (ctx.model?.provider !== 'grok-build') return;
107
110
 
108
- const modelId = ctx.model?.id ?? '';
109
- const sessionId = ctx.sessionManager?.getSessionId();
110
- return sanitizePayload(event.payload as Record<string, unknown>, modelId, sessionId, ctx.cwd);
111
- });
111
+ const modelId = ctx.model?.id ?? '';
112
+ const sessionId = ctx.sessionManager?.getSessionId();
113
+ return sanitizePayload(event.payload as Record<string, unknown>, modelId, sessionId, ctx.cwd);
114
+ }),
115
+ );
112
116
 
113
117
  registerUsageCommand(api);
114
118
  }
@@ -138,7 +138,9 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
138
138
  const data = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
139
139
  if (!data?.mcpServers) return result;
140
140
 
141
- const expanded = expandEnvVarsDeep(data.mcpServers);
141
+ // Project mcp.json is repo content. Skip secret-named ${VAR} the same way
142
+ // project ssh.json does; user scope still expands those names.
143
+ const expanded = expandEnvVarsDeep(data.mcpServers, undefined, level === "project");
142
144
  for (const [serverName, config] of Object.entries(expanded)) {
143
145
  const serverConfig = config as Record<string, unknown>;
144
146
 
@@ -878,7 +878,7 @@ export async function scanSkillsFromDir(
878
878
  await handle.close();
879
879
  }
880
880
  };
881
- const loadSkill = async (candidatePath: string) => {
881
+ const loadSkill = async (candidatePath: string, expectedCanonicalPath?: string) => {
882
882
  try {
883
883
  const linkIdentities = (
884
884
  await Promise.all([
@@ -889,6 +889,13 @@ export async function scanSkillsFromDir(
889
889
  await SkillDiscoveryTestHooks.afterSkillLinksCaptured?.(candidatePath);
890
890
  const skillPath = await fs.promises.realpath(candidatePath);
891
891
  await SkillDiscoveryTestHooks.afterSkillPathResolved?.(candidatePath);
892
+ if (
893
+ expectedCanonicalPath &&
894
+ normalizePathForComparison(skillPath) !== normalizePathForComparison(expectedCanonicalPath)
895
+ ) {
896
+ warnings.push(`Refusing changed skill path during isolated-home scan: ${candidatePath}`);
897
+ return;
898
+ }
892
899
  const allowOutsideRoot = options.allowExternalUserSkillSymlinks && level === "user";
893
900
  if (!isWithinRoot(skillPath) && !allowOutsideRoot) {
894
901
  const remedy =
@@ -971,14 +978,10 @@ export async function scanSkillsFromDir(
971
978
  for (const entry of entries) {
972
979
  if (entry.name.startsWith(".")) continue;
973
980
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
974
- const skillPath = await canonicalizePathWithinHome(
975
- _ctx,
976
- path.join(scanDir, entry.name, "SKILL.md"),
977
- containmentRoot,
978
- scope,
979
- );
980
- if (!skillPath) continue;
981
- work.push(loadSkill(skillPath));
981
+ const candidatePath = path.join(scanDir, entry.name, "SKILL.md");
982
+ const canonicalSkillPath = await canonicalizePathWithinHome(_ctx, candidatePath, containmentRoot, scope);
983
+ if (!canonicalSkillPath) continue;
984
+ work.push(loadSkill(candidatePath, _ctx.isolatedHome ? canonicalSkillPath : undefined));
982
985
  }
983
986
  await Promise.all(work);
984
987
 
@@ -154,7 +154,12 @@ function validateExactMCPConfig(config: unknown): {
154
154
  /**
155
155
  * Transform raw MCP config to canonical MCPServer format.
156
156
  */
157
- function transformMCPConfig(config: MCPConfigFile, source: SourceMeta, quiet = false): MCPServer[] {
157
+ function transformMCPConfig(
158
+ config: MCPConfigFile,
159
+ source: SourceMeta,
160
+ quiet = false,
161
+ skipSensitiveEnvNames = false,
162
+ ): MCPServer[] {
158
163
  const servers: MCPServer[] = [];
159
164
 
160
165
  if (config.mcpServers) {
@@ -229,15 +234,17 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta, quiet = f
229
234
  _source: source,
230
235
  };
231
236
 
232
- // Expand environment variables
233
- if (server.command) server.command = expandEnvVarsDeep(server.command);
234
- if (server.args) server.args = expandEnvVarsDeep(server.args);
235
- if (server.env) server.env = expandEnvVarsDeep(server.env);
236
- if (server.cwd) server.cwd = expandEnvVarsDeep(server.cwd);
237
- if (server.url) server.url = expandEnvVarsDeep(server.url);
238
- if (server.headers) server.headers = expandEnvVarsDeep(server.headers);
239
- if (server.auth) server.auth = expandEnvVarsDeep(server.auth);
240
- if (server.oauth) server.oauth = expandEnvVarsDeep(server.oauth);
237
+ // Repo-root mcp.json is project content. Exact `--mcp-config` passes
238
+ // skipSensitiveEnvNames false so a trusted file can still reference secrets.
239
+ const expand = <T>(value: T): T => expandEnvVarsDeep(value, undefined, skipSensitiveEnvNames);
240
+ if (server.command) server.command = expand(server.command);
241
+ if (server.args) server.args = expand(server.args);
242
+ if (server.env) server.env = expand(server.env);
243
+ if (server.cwd) server.cwd = expand(server.cwd);
244
+ if (server.url) server.url = expand(server.url);
245
+ if (server.headers) server.headers = expand(server.headers);
246
+ if (server.auth) server.auth = expand(server.auth);
247
+ if (server.oauth) server.oauth = expand(server.oauth);
241
248
  servers.push(server);
242
249
  }
243
250
  }
@@ -446,7 +453,7 @@ async function readExactMCPConfigFile(filePath: string): Promise<string | null>
446
453
  export async function loadMCPJsonFile(
447
454
  filePath: string,
448
455
  level: "user" | "project",
449
- options?: { quiet?: boolean; useCache?: boolean; readOptions?: ReadFileOptions },
456
+ options?: { quiet?: boolean; useCache?: boolean; readOptions?: ReadFileOptions; skipSensitiveEnvNames?: boolean },
450
457
  ): Promise<MCPJsonLoadResult> {
451
458
  const warnings: string[] = [];
452
459
  const items: MCPServer[] = [];
@@ -503,7 +510,7 @@ export async function loadMCPJsonFile(
503
510
  validConfig = config as MCPConfigFile;
504
511
  }
505
512
  const source = createSourceMeta(PROVIDER_ID, sourcePath, level);
506
- items.push(...transformMCPConfig(validConfig, source, options?.quiet));
513
+ items.push(...transformMCPConfig(validConfig, source, options?.quiet, options?.skipSensitiveEnvNames === true));
507
514
 
508
515
  return {
509
516
  items,
@@ -521,6 +528,7 @@ async function load(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
521
528
  filenames.map(filename =>
522
529
  loadMCPJsonFile(path.join(ctx.cwd, filename), "project", {
523
530
  readOptions: getReadOptions(ctx, "project"),
531
+ skipSensitiveEnvNames: true,
524
532
  }),
525
533
  ),
526
534
  );