@gajae-code/coding-agent 0.18.8 → 0.18.10

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 +100 -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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,106 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.18.10] - 2026-10-12
6
+
7
+ ## [0.18.9] - 2026-10-11
8
+
9
+ ### Added
10
+
11
+ - Added attach-only SDK client mode: with `GJC_SDK_BROKER_AUTOSTART=0` or `--attach-only` (on `gjc sdk session`, `gjc sdk search`, `gjc sdk spawn` and `gjc mcp-serve`), every SDK client entry point (session CLI, search, spawn, SDK and coordinator MCP servers, ACP, session hosts) attaches only to a live, reusable broker and fails with `broker_unavailable` instead of spawning, retiring, or restarting one, so a supervisor-owned broker has a single owner. `gjc doctor` broker restart refuses with `attach_only_refused`, and the SDK broker restart script refuses before closing hosts or stopping the broker.
12
+
13
+ - Added `gjc sdk broker run [--agent-dir <dir>]`, a stable foreground broker command for supervisors (systemd, launchd). It never detaches, stops gracefully on SIGTERM or SIGINT and exits with the signal status, exits 1 instead of serving when another live broker already owns the agent directory (never retiring it), and exits 1 if it loses its discovery root while serving.
14
+
15
+ ### Fixed
16
+
17
+ - Prevented deferred startup profile recovery from replacing newer model or role selections during credential refresh or settings flush; rejected model selections no longer cancel recovery, and canceled recovery restores session-owned alias and fallback state (#6380).
18
+
19
+ - Match Windows session paths by filesystem identity without rewriting canonical paths or conflating distinct case-sensitive files. Managed-root containment canonicalizes aliases above the root and rejects symlink components below it.
20
+ - Reject managed descendant symlinks before canonicalizing paths, preventing a replaced scope directory from redirecting access to a sibling session.
21
+ - Keep native-backed endpoint key derivation out of the broadly imported async barrel so idle startup does not load path identity bindings.
22
+
23
+ - The Bash tool now counts characters instead of UTF-8 bytes for `${#var}`, so negative substring offsets such as `${var: -2}` select the right characters in non-ASCII values (#6544).
24
+ - A negative substring length now marks where the slice ends, as in bash (`${v:2:-2}` on `abcdefgh` is `cdef`), and an end before the offset or a negative length on an array fails with `substring expression < 0` instead of slicing the wrong range.
25
+
26
+ - `ast_edit` apply refuses auto-generated files when that edit guard is on, the same as write and vim.
27
+
28
+ - `ast_edit` apply checks the real write path and the original preview path, so a directory symlink such as `src` → `.gjc` cannot bypass the `.gjc/**` block, and a `.gjc` directory that points at another in-workspace directory stays blocked.
29
+
30
+ - Enroll async-job delivery and loop completion futures before callbacks and change notifications so drains and bounded disposal cannot mistake in-flight work for settled delivery.
31
+
32
+ - Contain rejections from `async` agent-session event subscribers, so a failed subscriber write (for example the `managed_append_identity_mismatch` fence after another process resumed the same session) is logged instead of terminating the interactive process with an unhandled rejection.
33
+
34
+ - `app.cdp_url` is refused unless its host is loopback.
35
+
36
+ - Debug adapter lookup uses the same trusted `$PATH` rule as LSP auto-detection, so a repository `bin/`, `node_modules/.bin`, or virtualenv binary is no longer spawned as a debugger.
37
+
38
+ - Managed-session descriptor preflight and bounded range reads now preserve their original metadata, pathname, or range error when descriptor close also fails; close-only errors after a successful read still propagate.
39
+
40
+ - Reusing a detached launch worktree now refuses to check out a new source HEAD when the worktree commit is not an ancestor of that HEAD, so committed work is not silently dropped.
41
+
42
+ - Explicit session directories create fork and branch transcripts and unsent drafts with the same native owner-only security as the primary transcript.
43
+
44
+ - A project `.env` declaration of `VISUAL` or `EDITOR` no longer selects the external editor that GJC executes on your draft; values inherited from the launching shell or GJC/user-owned env files are still honored.
45
+
46
+ - Temp directory cleanup on Windows: sessions now properly release managed sidecar cache file handles during `closeStrict()`, preventing "directory in use" errors when deleting temporary session directories in tests. The root cause was unconditional resource retention when close succeeded; the fix ensures file handles are released even when resident blob cleanup is skipped (#6566).
47
+
48
+ - Side-effect `github` operations (`pr_create`, `pr_checkout`, `pr_push`) now go through the session approval gate and the planning-phase mutation guard, while read-only operations stay available.
49
+
50
+ - HTML session export creates and replaces its output file as owner-only (`0600`).
51
+
52
+ - Keep bundled Grok Build request hooks scoped to their provider so unrelated SDK sessions retain replay-safe empty-response fallback. Cover the production SDK observer, accepted-only lifecycle, managed transcript persistence, and billed empty stops without changing deadline reconciliation or admitting rejected attempts.
53
+
54
+ - Preserve clean managed-session fallback admission when the SDK observes the run start, while keeping user extension execution replay-unsafe. Successful empty stops with nonzero usage no longer trigger legacy proxy-overflow retries in session-owned turns.
55
+
56
+ - Plugin MCP policy denies the shared CGNAT range 100.64.0.0/10, matching the public URL address check.
57
+
58
+ - Remote MCP HTTP no longer follows a redirect onto another origin, so custom credential headers, the MCP session id, and the JSON-RPC body stay on the configured server.
59
+
60
+ - A modern MCP `roots/list` input request uses the connection's request handler, so a tools-only session does not answer with the project URI.
61
+
62
+ - Project MCP config no longer runs shell substitutions or copies secret-named environment variables into server environment and headers.
63
+
64
+ - Local memory consolidation (phase 2) no longer runs out of room under a fixed 8192-token response cap that, on adaptive-thinking and reasoning-effort models, also had to hold the reasoning; on large memories the rewritten `MEMORY.md`, summary and skills were cut off, failed as `phase2 JSON parse failure`, and `MEMORY.md` / `memory_summary.md` silently stopped updating. The response budget is now the model's output limit capped at 64k, clamped so input, response and any provider-added thinking budget fit the context window (inputs are shrunk on small-context models rather than dropping below the old 8192 budget), and phase 2 requests low reasoning effort like phase 1. A length-truncated response is recorded as `phase2 output truncated …` and a response with no text as `phase2 returned no text (stopReason=…, … output tokens, … thinking blocks)` instead of a JSON parse failure. A job that already exhausted its retries runs again once new sessions advance its input.
65
+
66
+ - The workflow mutation guard no longer treats an out-of-band mode-state phase as permission to edit product code while the active planning skill is still sealed.
67
+
68
+ - `monitor` commands are checked by the same planning-phase mutation guard as `bash` before a background job starts.
69
+
70
+ - The MCP OAuth client_id probe does not GET a non-public authorization URL: the request is pinned to the validated address and the flow abort signal reaches that DNS lookup. The shared address check rejects 192.88.99.0/24, including the non-global 6a44 relay 192.88.99.2.
71
+
72
+ - OAuth dynamic registration metadata is fetched only from a public URL, and that fetch does not follow redirects.
73
+
74
+ - Plugin MCP launchers no longer trust startup `node` executables located under temporary roots, even when the GJC checkout is also under `/tmp`; transient first-use digest failures can be retried against the original startup identity.
75
+
76
+ - The Python tool bridge checks that its registration is still current and not aborted after the request body arrives.
77
+
78
+ - Register spawned Python kernels before startup callbacks and retain failed or unconfirmed cleanup resources for an explicit retry instead of losing or replacing them.
79
+ - Separate cancelled requests from shared kernel initialization while preserving captured cleanup joins and removing failed provisional acquisitions.
80
+ - Preserve direct-start recovery handles and execution/cleanup error precedence, revalidate cancelled retry acquisitions, and keep active fallback owners through failed explicit preflight.
81
+
82
+ - Enroll the original Python tool invocation before external execution tracking, so reentrant clear and registered cleanup join its transcript and result finalization without clearing an independent generation.
83
+
84
+ - Remove temporary deadline-recovery stderr diagnostics so SDK prompt bookkeeping stays within centralized logging and does not expose correlation identifiers through debug prints.
85
+
86
+ - A retained managed session directory replaced by a symlink is now refused as a changed root binding, and the newly retained child store is closed.
87
+
88
+ - Bound shared GC count/collect inventories across each read-only receipt read, discovery call and protocol inspection, including unrelated entries and repeated passes. Preserve genuine leased publication quotas, authority fences and descriptor admission; report capacity refusal rather than omitting targets. This does not establish full GC traversal/read/hash admission or writer quiescence.
89
+
90
+ - The coordinator sidecar ignores a signing-key URL or key that the project dotenv declares, so a repository `.env` cannot supply the signing key.
91
+
92
+ - Task isolation directories are unique per attempt, so one session can no longer delete another session's live worktree when both reuse the same task id.
93
+
94
+ - A project `.env` declaration of `GJC_TMUX_COMMAND`, including a dynamic one, no longer selects the tmux binary; the default `tmux` path is used instead, while an operator value the project does not declare is kept.
95
+
96
+ - Preserve trusted user-skill symlink provenance in isolated-home discovery, honor the effective scope trust policy, and reuse scanner-validated import metadata in customization inventory.
97
+
98
+ - Fixed Windows CI task cache key test cleanup failures (EBUSY/EPERM) by adding exponential backoff retry logic to temporary directory removal. File handles on Windows may remain open during deletion, causing removal to fail; retrying with increasing delays resolves the locking issue.
99
+
100
+ - A project `.env` declaration of `GJC_WORKTREE_DIR`, including a dynamic one, no longer selects the launch worktree bucket; the default `{repo}/.worktrees` path is used instead, while an operator value the project does not declare is kept.
101
+
102
+ - Zero-usage empty stops from context-overflow proxies trigger context promotion and overflow recovery again; only successful empty stops with nonzero usage stay terminal in session-owned turns.
103
+ - Total-token-only empty stops (from OpenAI-compatible endpoints returning only `usage.total_tokens`) are now correctly classified as nonzero usage and remain terminal, preventing unnecessary promotion or retry.
104
+
5
105
  ## [0.18.8] - 2026-10-08
6
106
 
7
107
  ### Added
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Single source of truth for the AsyncJobManager endpoint key.
3
+ *
4
+ * A session with an explicit provider session id is keyed by (provider id,
5
+ * transcript path); everything else is keyed by its logical session id. The
6
+ * transcript component uses a stable path key so a path alias — symlink,
7
+ * casing difference on a case-insensitive Windows directory, or `..` segment —
8
+ * cannot register the manager under one key at construction and look it up
9
+ * under another across a session-identity transition, stranding ownership.
10
+ * The key remains stable when a transcript is created or atomically replaced,
11
+ * and case-sensitive Windows paths remain distinct.
12
+ *
13
+ * This module is deliberately not re-exported from the broadly imported async
14
+ * barrel: importing the path identity helper loads native bindings.
15
+ */
16
+ export declare function asyncJobEndpointId(providerSessionId: string | undefined, sessionId: string, sessionFile: string | undefined): string;
@@ -1,16 +1,2 @@
1
1
  import type { Settings } from "../config/settings";
2
2
  export declare function isBackgroundJobSupportEnabled(settings: Pick<Settings, "get">): boolean;
3
- /**
4
- * Single source of truth for the AsyncJobManager endpoint key.
5
- *
6
- * A session with an explicit provider session id is keyed by (provider id,
7
- * transcript path); everything else is keyed by its logical session id. The
8
- * transcript component is canonicalized so a path alias — symlink, `..`
9
- * segment, or unresolved relative path — cannot register the manager under one
10
- * key at construction and look it up under another across a session-identity
11
- * transition, stranding ownership.
12
- *
13
- * Both the SDK constructor and AgentSession's transition rekeying must derive
14
- * the key here; duplicating the shape lets the two drift apart silently.
15
- */
16
- export declare function asyncJobEndpointId(providerSessionId: string | undefined, sessionId: string, sessionFile: string | undefined): string;
@@ -52,6 +52,10 @@ export default class McpServe extends Command {
52
52
  description: string;
53
53
  default: boolean;
54
54
  };
55
+ "attach-only": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
56
+ description: string;
57
+ default: boolean;
58
+ };
55
59
  };
56
60
  run(): Promise<void>;
57
61
  }
@@ -11,4 +11,5 @@ export declare function loadMCPJsonFile(filePath: string, level: "user" | "proje
11
11
  quiet?: boolean;
12
12
  useCache?: boolean;
13
13
  readOptions?: ReadFileOptions;
14
+ skipSensitiveEnvNames?: boolean;
14
15
  }): Promise<MCPJsonLoadResult>;
@@ -42,6 +42,24 @@ interface KernelStartOptions extends KernelLifecycleOptions {
42
42
  env?: Record<string, string | undefined>;
43
43
  runtimeOptions?: PythonRuntimeOptions;
44
44
  settings?: Settings;
45
+ /** @internal Synchronously registers the actual spawned kernel before startup callbacks run. */
46
+ onKernelCreated?: (kernel: PythonKernel) => void;
47
+ /** @internal Routes partial-start cleanup through the executor's retained ownership. */
48
+ onStartupFailure?: (kernel: PythonKernel) => Promise<void>;
49
+ }
50
+ export type PythonKernelStartupCleanupFailure = {
51
+ kind: "unconfirmed";
52
+ result: KernelShutdownResult;
53
+ } | {
54
+ kind: "rejected";
55
+ error: unknown;
56
+ };
57
+ /** Startup failed after a real runner was created and physical cleanup failed. */
58
+ export declare class PythonKernelStartError extends Error {
59
+ readonly kernel: PythonKernel;
60
+ readonly startupError: unknown;
61
+ readonly cleanupFailure: PythonKernelStartupCleanupFailure;
62
+ constructor(kernel: PythonKernel, startupError: unknown, cleanupFailure: PythonKernelStartupCleanupFailure);
45
63
  }
46
64
  interface KernelShutdownOptions {
47
65
  signal?: AbortSignal;
@@ -0,0 +1,2 @@
1
+ /** Owner-only HTML export. A replaced file is locked down on the opened descriptor. */
2
+ export declare function openHtmlExportDestination(outputPath: string): number;
@@ -1,5 +1,10 @@
1
1
  import { type FunctionHookRegistration } from "./function-hooks";
2
2
  export type TaggedFunctionHookHandler = (...args: unknown[]) => Promise<unknown>;
3
+ /** Host-owned provider adapters must not consume replay authority for other providers. */
4
+ export declare function tagProviderRequestHandler<T extends (...args: never[]) => unknown>(provider: string, handler: T): T;
5
+ export declare function getProviderRequestHandlerProvider(handler: (...args: never[]) => unknown): string | undefined;
6
+ export declare function tagSdkLifecycleObserver<T extends (...args: never[]) => unknown>(handler: T): T;
7
+ export declare function isSdkLifecycleObserver(handler: (...args: never[]) => unknown): boolean;
3
8
  export declare function tagExtensionHandlerRegistrationOrder<T extends (...args: never[]) => unknown>(handler: T, registrationOrder: number): T;
4
9
  export declare function wrapExtensionHandlerRegistration<T extends (...args: never[]) => unknown>(handler: T, registrationOrder: number): T;
5
10
  export declare function getExtensionHandlerRegistrationOrder(value: unknown): number | undefined;
@@ -13,6 +13,7 @@ export type SkillDisabledReason = "protected" | "scope-trust" | "ignored" | "inc
13
13
  export interface ManagedSkillRecord {
14
14
  name: string;
15
15
  description: string;
16
+ importedFrom?: string;
16
17
  path: string;
17
18
  scope: SkillScope;
18
19
  /** Canonical source label, e.g. "project .gjc/skills" or "user ~/.gjc/agent/skills". */
@@ -64,9 +64,9 @@ export interface ResolvedTmuxBinary {
64
64
  viaExplicitOverride: boolean;
65
65
  }
66
66
  /**
67
- * Resolve the tmux command GJC should invoke. Honors the existing
68
- * GJC_TMUX_COMMAND override; on Windows when no
69
- * override is set, psmux (installed as psmux, pmux, or tmux) is picked
67
+ * Resolve the tmux command GJC should invoke. Honors an operator
68
+ * GJC_TMUX_COMMAND override; a project dotenv value is ignored. On Windows
69
+ * when no override is set, psmux (installed as psmux, pmux, or tmux) is picked
70
70
  * automatically so the default gjc --tmux flow lands on a real multiplexer.
71
71
  */
72
72
  export declare function resolveGjcTmuxBinary(options?: ResolveGjcTmuxBinaryOptions): ResolvedTmuxBinary;
@@ -0,0 +1,6 @@
1
+ import { type ProjectEnvSnapshot } from "@gajae-code/utils/env-file";
2
+ /**
3
+ * Operator environment only. A value that matches the project dotenv snapshot
4
+ * was loaded by Bun from the repository and must not become signing material.
5
+ */
6
+ export declare function trustedCoordinatorEnv(name: string, env?: NodeJS.ProcessEnv, snapshot?: ProjectEnvSnapshot): string | undefined;
@@ -168,6 +168,9 @@ export type StrictMutationReadResult = {
168
168
  export declare function readExistingStateForMutation(filePath: string): Promise<StrictMutationReadResult>;
169
169
  export declare function workflowEnvelopeContentSha256(value: unknown): string;
170
170
  export declare function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, computedAt?: string): T;
171
+ export type WorkflowEnvelopeChecksumStatus = "absent" | "match" | "mismatch";
172
+ /** Compare a parsed envelope to its writer-stamped `content_sha256`, when one is present. */
173
+ export declare function workflowEnvelopeChecksumStatus(value: unknown): WorkflowEnvelopeChecksumStatus;
171
174
  export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string): Promise<WorkflowEnvelopeIntegrityMismatch | undefined>;
172
175
  type ActiveStateCacheInvalidator = (cwd?: string, sessionId?: string) => void;
173
176
  export declare function setActiveStateCacheInvalidator(invalidator: ActiveStateCacheInvalidator): void;
@@ -40,11 +40,14 @@ export declare function envDisabled(value: string | undefined): boolean;
40
40
  * failure mode where `gjc --tmux` creates a psmux-backed session and then
41
41
  * `gjc session status` fails because it queries literal `tmux` is closed off.
42
42
  *
43
- * Explicit `GJC_TMUX_COMMAND` overrides are honored on
44
- * every platform. On native Windows without an override the resolver walks
45
- * `psmux`, then `pmux`, then `tmux` and uses the first binary present on PATH.
46
- * On POSIX the resolver returns `tmux` (the historical default) and only
47
- * falls through to the platform-aware walker if the caller opts in.
43
+ * An operator `GJC_TMUX_COMMAND` is honored on every platform. A value that
44
+ * matches the project dotenv snapshot, including a dynamic declaration, is
45
+ * ignored so a repository cannot choose the binary, and the resolver uses the
46
+ * same default as when the variable is unset. On native Windows without an
47
+ * override the resolver walks `psmux`, then `pmux`, then `tmux` and uses the
48
+ * first binary present on PATH. On POSIX the resolver returns `tmux` (the
49
+ * historical default) and only falls through to the platform-aware walker if
50
+ * the caller opts in.
48
51
  */
49
52
  export declare function resolveGjcTmuxCommand(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
50
53
  export type { PsmuxProbe, ResolvedTmuxBinary, ResolveGjcTmuxBinaryOptions } from "./psmux-detect";
@@ -9,13 +9,16 @@ export interface LspConfig {
9
9
  */
10
10
  export declare function hasRootMarkers(cwd: string, markers: string[]): boolean;
11
11
  /**
12
- * Resolve a command to an executable path.
13
- * Checks project-local bin directories first, then falls back to $PATH.
12
+ * Resolve a host-spawn executable from $PATH without consulting project-controlled bin directories.
13
+ * LSP auto-detection and DAP adapter launch both use this rule.
14
14
  *
15
- * @param command - The command name (e.g., "typescript-language-server")
16
- * @param cwd - Working directory to search from
17
- * @returns Absolute path to the executable, or null if not found
15
+ * Trust is evaluated on both the discovered and canonical paths, but the returned
16
+ * path is the discovered one: multiplexed launchers such as rustup pick the proxied
17
+ * tool from argv[0], so `~/.cargo/bin/rust-analyzer -> rustup` must be spawned as
18
+ * `rust-analyzer`, not as the resolved `rustup` binary.
18
19
  */
20
+ export declare function resolveTrustedCommand(command: string, cwd: string): string | null;
21
+ /** Same rule as `resolveTrustedCommand`: project-local bin directories are never searched. */
19
22
  export declare function resolveCommand(command: string, cwd: string): string | null;
20
23
  /**
21
24
  * Load LSP configuration.
@@ -257,10 +257,11 @@ export declare class MCPManager {
257
257
  /**
258
258
  * Resolve auth and shell-command substitutions in config before connecting.
259
259
  */
260
- prepareConfig(config: MCPServerConfig): Promise<MCPServerConfig>;
260
+ prepareConfig(config: MCPServerConfig, source?: SourceMeta): Promise<MCPServerConfig>;
261
261
  /** Acquire a prepared, pool-owned lease for a scoped transient operation. */
262
262
  withPreparedLease<T>(name: string, config: MCPServerConfig, fn: (lease: MCPPoolLease) => Promise<T> | T, options?: {
263
263
  signal?: AbortSignal;
264
+ source?: SourceMeta;
264
265
  }): Promise<T>;
265
266
  /** Read-only test seam for pending retired lease-release records. */
266
267
  get retiredLeaseReleaseCountForTests(): number;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Follow same-origin MCP redirects only.
3
+ *
4
+ * A cross-origin 307/308 would carry custom credential headers, `Mcp-Session-Id`,
5
+ * and the JSON-RPC body. The first request uses `rawUrl` unchanged so a caller
6
+ * that cannot be parsed (diagnostic redaction) still reaches fetch, but with
7
+ * `redirect: "manual"` so that hop is not followed either.
8
+ */
9
+ export declare function fetchMcpRespectingOrigin(rawUrl: string, init: BunFetchRequestInit): Promise<Response>;
@@ -1 +1,3 @@
1
- export declare function assertPublicOAuthUrl(url: string): Promise<void>;
1
+ export declare function assertPublicOAuthUrl(url: string, options?: {
2
+ signal?: AbortSignal;
3
+ }): Promise<void>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Host-value policy for MCP config that comes from an untrusted project file.
3
+ *
4
+ * User mcp.json and an explicit `--mcp-config` file may still substitute
5
+ * secret-named environment variables and run `!` shell commands. A project
6
+ * file (native `.gjc/mcp.json` or a repo-root `mcp.json`) uses the same
7
+ * sensitive-name rule as project `ssh.json`: do not copy those host values
8
+ * into server env or headers, and do not execute `!`.
9
+ */
10
+ import type { SourceMeta } from "../capability/types";
11
+ export declare function nativeMcpSource(level: "user" | "project", filePath: string): SourceMeta;
12
+ /** True when this server was loaded from untrusted project MCP config. */
13
+ export declare function isUntrustedProjectMcpSource(source: SourceMeta | undefined, toolsOnly: boolean): boolean;
14
+ export type ProjectHostValueDecision = {
15
+ action: "resolve";
16
+ } | {
17
+ action: "drop";
18
+ } | {
19
+ action: "literal";
20
+ value: string;
21
+ };
22
+ /**
23
+ * Decide how to interpret one env or header value.
24
+ * `drop` means "do not run the shell, and omit the value".
25
+ * `literal` means "do not read process.env".
26
+ */
27
+ export declare function decideUntrustedProjectConfigValue(value: string, untrusted: boolean): ProjectHostValueDecision;
@@ -3,7 +3,7 @@ import type { ModelProfileErrorDetails } from "../../config/model-profile-contra
3
3
  import type { TaskArtifactOwnerDeletionEvidence, TaskArtifactOwnerRetirementContinuation } from "../../session/task-artifact-owner-codec";
4
4
  import { type SdkHostModelResolver } from "../host/model-pin";
5
5
  import { type DirectoryMigrationPolicy } from "../session-directory";
6
- import { type BrokerStopRequest } from "./broker-exit";
6
+ import { type BrokerExitMode, type BrokerExitReason, type BrokerStopRequest } from "./broker-exit";
7
7
  import { type BrokerDiscovery, type BrokerPublicationObservation, type RedactedBrokerDiscovery } from "./discovery";
8
8
  import { type LifecycleDurableEffectsReceipt, LifecycleLedger, type LifecycleStartupFailureReceipt } from "./lifecycle-ledger";
9
9
  import { type IndexedSession, SessionIndex } from "./session-index";
@@ -373,6 +373,11 @@ export declare class Broker {
373
373
  start(): Promise<BrokerDiscovery>;
374
374
  get ownsDiscovery(): boolean;
375
375
  get completion(): Promise<void>;
376
+ /** Why this broker completed, or null while it is still running. */
377
+ get terminalExit(): {
378
+ readonly mode: BrokerExitMode;
379
+ readonly reason: BrokerExitReason;
380
+ } | null;
376
381
  prepareRestart(options: BrokerRestartPrepareOptions): Promise<BrokerRestartResult>;
377
382
  commitRestart(options: BrokerRestartCommitOptions): Promise<BrokerRestartResult>;
378
383
  cancelRestart(requestId: string): Promise<BrokerRestartResult>;
@@ -23,6 +23,11 @@ export type DoctorBrokerRestartOutcome = {
23
23
  } | {
24
24
  kind: "owner_unavailable";
25
25
  reason: "no_discovery" | "owner_not_confirmed_live";
26
+ }
27
+ /** Attach-only mode: the broker belongs to an external supervisor, so restart through it. */
28
+ | {
29
+ kind: "attach_only_refused";
30
+ reason: "supervisor_owned";
26
31
  } | {
27
32
  kind: "prepare_refused";
28
33
  code: string;
@@ -152,7 +152,20 @@ export declare function closeBrokerClientBeforeDeadline(client: {
152
152
  }, deadline: number): Promise<void>;
153
153
  /** Reconcile an observed broker generation before entering a startup critical section. */
154
154
  export declare function reconcileBrokerGenerationForStartup(settings: EnsureBrokerSettings, deadline?: number): Promise<BrokerDiscovery | undefined>;
155
- /** Starts the detached broker entrypoint when discovery has no live owner. */
155
+ /**
156
+ * Attach-only client mode. When `GJC_SDK_BROKER_AUTOSTART=0`, `ensureBroker`
157
+ * never spawns, retires or restarts a broker: it returns a live, reusable
158
+ * discovery or fails with `broker_unavailable`. Hosts that supervise their own
159
+ * broker (for example a system service) set it for every client they run. The
160
+ * broker entrypoints never call `ensureBroker`, so they ignore the variable and
161
+ * their descendants may inherit it.
162
+ */
163
+ export declare const SDK_BROKER_AUTOSTART_ENV = "GJC_SDK_BROKER_AUTOSTART";
164
+ export declare function isBrokerAttachOnly(env?: NodeJS.ProcessEnv): boolean;
165
+ /**
166
+ * Starts the detached broker entrypoint when discovery has no live owner. In
167
+ * attach-only mode (`SDK_BROKER_AUTOSTART_ENV`) it only attaches.
168
+ */
156
169
  export declare function ensureBroker(settings: EnsureBrokerSettings): Promise<BrokerDiscovery>;
157
170
  /** Starts one fresh fixture broker and returns its sole exact-child close lease. */
158
171
  export declare function startFixtureBrokerWithLeaseForTest(settings: EnsureBrokerSettings): Promise<StartedFixtureBroker>;
@@ -444,6 +444,13 @@ export type NativeDirectoryTreeSnapshot = {
444
444
  rootIno: string;
445
445
  entries: NativeDirectoryTreeEntry[];
446
446
  };
447
+ /**
448
+ * Replace an explicit session file. Creation mode `0600` does not install a
449
+ * Windows owner-only DACL, so this applies and verifies the same native path a
450
+ * primary transcript uses when it has no managed security context. POSIX mode
451
+ * stays `0600`.
452
+ */
453
+ export declare function writeExplicitOwnerOnlyTextSync(pathname: string, content: string): void;
447
454
  /** Physically observed commit-marker state; corrupt JSON is still `present`. */
448
455
  export type SessionCommitMarkerState = {
449
456
  kind: "missing";
@@ -5,6 +5,9 @@ export declare const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE = ".gjc workflow stat
5
5
  export declare const RALPLAN_MUTATION_BLOCK_MESSAGE = "Ralplan planning phase boundary: keep refining the consensus plan and persist plan artifacts through `gjc ralplan --write` (stage scratch files under a temp dir if needed). Product-code mutation tools and patch execution are blocked while ralplan is active; mutate only after the plan is approved and execution begins.";
6
6
  export declare const ULTRAGOAL_GOAL_PLANNING_MUTATION_BLOCK_MESSAGE = "Ultragoal goal-planning phase boundary: finish goal planning and record goals through `gjc ultragoal` before editing code. Product-code mutation tools and patch execution are blocked until goal planning completes and execution begins.";
7
7
  export declare const AUTORESEARCH_MUTATION_BLOCK_MESSAGE = "Autoresearch research-only boundary: this workflow produces findings, evidence, and a verdict \u2014 never product code. Product-code mutation tools and patch execution are blocked everywhere, on every branch. Mission research artifacts (`autoresearch.sh` at the workdir root and mission state through the sanctioned `gjc autoresearch` CLI) stay writable; run experiments through the `python` tool, or finish the mission and hand off to an approved implementation workflow.";
8
+ /** Tools whose execute path can mutate product state. `github` is included so side-effect ops share this guard. */
9
+ export declare const WORKFLOW_MUTATION_TOOL_NAMES: readonly ["edit", "write", "ast_edit", "bash", "github"];
10
+ export declare function isWorkflowMutationTool(name: string): boolean;
8
11
  type ToolWithEditMode = AgentTool & {
9
12
  mode?: unknown;
10
13
  customWireName?: unknown;
@@ -0,0 +1,26 @@
1
+ import * as path from "node:path";
2
+ type PathApi = Pick<typeof path, "dirname" | "isAbsolute" | "relative" | "resolve" | "sep">;
3
+ /**
4
+ * Parent directory of `mergedDir`, but only when it is a single entry inside
5
+ * `worktreeRoot`. Root, empty, nested, and escaped paths are refused so
6
+ * cleanup cannot delete the worktree root or another session's tree by
7
+ * walking up.
8
+ */
9
+ export declare function resolveIsolationRemovalTarget(mergedDir: string, worktreeRoot: string, pathApi?: PathApi): string;
10
+ /**
11
+ * Create an exclusive directory next to `canonicalBaseDir`. Never removes
12
+ * `canonicalBaseDir` or any path that already exists.
13
+ */
14
+ export declare function allocateDisjointIsolationDir(canonicalBaseDir: string, tokenFactory?: () => string): Promise<string>;
15
+ /**
16
+ * Confirm native teardown may see `mergedDir`.
17
+ *
18
+ * `isoStop` deletes that path. The parent entry and `merged` itself must
19
+ * already be real directories, because native removal follows symlinks.
20
+ * A missing parent means there is nothing to stop. This does not close a
21
+ * replacement that happens after the lstat returns.
22
+ */
23
+ export declare function assertIsolationTeardownTarget(mergedDir: string, worktreeRoot: string): Promise<"stop" | "skip">;
24
+ /** Remove the isolation directory that owns `mergedDir`. Missing directories are ignored. */
25
+ export declare function removeIsolationDirectory(mergedDir: string, worktreeRoot: string): Promise<void>;
26
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Path the native writer will mutate: realpath of an existing file, or realpath
3
+ * of the nearest existing ancestor plus the missing tail.
4
+ */
5
+ export declare function canonicalizeAstEditWritePath(absolutePath: string): Promise<string>;
6
+ /**
7
+ * Preview paths as the workflow guard must see them.
8
+ *
9
+ * The guard compares lexically against the session cwd. A raw realpath misses
10
+ * `.gjc/**` when that cwd is not already canonical (`/var` vs `/private/var`,
11
+ * or a symlinked workspace), so an in-workspace real path is reattached to `cwd`.
12
+ */
13
+ export declare function resolveAstEditPreviewWritePaths(cwd: string, previewedPaths: readonly string[]): Promise<string[]>;
@@ -0,0 +1,2 @@
1
+ /** Require an http(s) CDP URL whose host is loopback. */
2
+ export declare function assertLoopbackCdpUrl(cdpUrl: string): string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Classify `github` tool operations for the approval gate and the planning
3
+ * mutation guard. Read-only ops stay available. Every other op, including a
4
+ * missing or unrecognized one, is a side effect and fails closed.
5
+ */
6
+ import type { AgentTool } from "@gajae-code/agent-core";
7
+ /** Name-only tool the planning guard uses when `GithubTool.execute` checks itself. */
8
+ export declare const githubMutationTool: AgentTool;
9
+ export declare function githubOp(args: unknown): string | undefined;
10
+ /** True only for a known read-only github op. Missing and unknown ops are not read-only. */
11
+ export declare function isGithubReadOnlyArgs(args: unknown): boolean;
12
+ export declare function isGithubReadOnlyOp(op: string): boolean;
13
+ /**
14
+ * Approval summary for a side-effect github call. The PR body is omitted so a
15
+ * token pasted into `body` is not copied into the permission title.
16
+ */
17
+ export declare function describeGithubSideEffect(args: unknown): string;
18
+ export declare function githubPermissionIntent(args: unknown): {
19
+ toolName: string;
20
+ title: string;
21
+ cacheKey: string;
22
+ } | undefined;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Judge a monitor command with the same mutation rules as bash. Monitor is a
3
+ * background shell, so a planning-phase mutation must fail before the job starts.
4
+ */
5
+ export declare function assertMonitorMutationAllowed(input: {
6
+ cwd: string;
7
+ sessionId?: string;
8
+ command: string;
9
+ }): Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.18.8",
4
+ "version": "0.18.10",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -57,12 +57,12 @@
57
57
  "@agentclientprotocol/sdk": "1.3.0",
58
58
  "@babel/parser": "^7.29.3",
59
59
  "@mozilla/readability": "^0.6.0",
60
- "@gajae-code/stats": "0.18.8",
61
- "@gajae-code/agent-core": "0.18.8",
62
- "@gajae-code/ai": "0.18.8",
63
- "@gajae-code/natives": "0.18.8",
64
- "@gajae-code/tui": "0.18.8",
65
- "@gajae-code/utils": "0.18.8",
60
+ "@gajae-code/stats": "0.18.10",
61
+ "@gajae-code/agent-core": "0.18.10",
62
+ "@gajae-code/ai": "0.18.10",
63
+ "@gajae-code/natives": "0.18.10",
64
+ "@gajae-code/tui": "0.18.10",
65
+ "@gajae-code/utils": "0.18.10",
66
66
  "@puppeteer/browsers": "^2.13.0",
67
67
  "@types/turndown": "5.0.6",
68
68
  "@xterm/headless": "^6.0.0",
@@ -0,0 +1,26 @@
1
+ import { stablePathKey } from "@gajae-code/utils/path-identity";
2
+
3
+ /**
4
+ * Single source of truth for the AsyncJobManager endpoint key.
5
+ *
6
+ * A session with an explicit provider session id is keyed by (provider id,
7
+ * transcript path); everything else is keyed by its logical session id. The
8
+ * transcript component uses a stable path key so a path alias — symlink,
9
+ * casing difference on a case-insensitive Windows directory, or `..` segment —
10
+ * cannot register the manager under one key at construction and look it up
11
+ * under another across a session-identity transition, stranding ownership.
12
+ * The key remains stable when a transcript is created or atomically replaced,
13
+ * and case-sensitive Windows paths remain distinct.
14
+ *
15
+ * This module is deliberately not re-exported from the broadly imported async
16
+ * barrel: importing the path identity helper loads native bindings.
17
+ */
18
+ export function asyncJobEndpointId(
19
+ providerSessionId: string | undefined,
20
+ sessionId: string,
21
+ sessionFile: string | undefined,
22
+ ): string {
23
+ return providerSessionId !== undefined && sessionFile !== undefined
24
+ ? JSON.stringify(["async-job-endpoint", providerSessionId, stablePathKey(sessionFile)])
25
+ : sessionId;
26
+ }