@gajae-code/coding-agent 0.16.4 → 0.16.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/customize-doctor.d.ts +22 -0
  4. package/dist/types/config/file-lock.d.ts +31 -3
  5. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  6. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  7. package/dist/types/discovery/helpers.d.ts +33 -0
  8. package/dist/types/exec/bash-executor.d.ts +17 -1
  9. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  10. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  11. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  12. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  13. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  14. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  15. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  16. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  17. package/dist/types/exec/isolated-shell.d.ts +19 -0
  18. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  19. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  20. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  21. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  22. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  23. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  24. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  25. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  26. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +4 -1
  28. package/dist/types/extensibility/skill-management.d.ts +9 -4
  29. package/dist/types/extensibility/skills.d.ts +2 -0
  30. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  31. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +12 -0
  33. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  34. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  35. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  36. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  37. package/dist/types/modes/interactive-mode.d.ts +2 -0
  38. package/dist/types/modes/types.d.ts +6 -0
  39. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  40. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  41. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  42. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  43. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  44. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  45. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  47. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  48. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  49. package/dist/types/sdk/client/client.d.ts +6 -0
  50. package/dist/types/sdk/host/host.d.ts +3 -1
  51. package/dist/types/sdk/host/session-runtime.d.ts +4 -2
  52. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  53. package/dist/types/sdk/router/session-router.d.ts +10 -2
  54. package/dist/types/sdk/session.d.ts +5 -1
  55. package/dist/types/session/agent-session.d.ts +2 -0
  56. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  57. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  58. package/dist/types/session/messages.d.ts +1 -0
  59. package/dist/types/session/session-manager.d.ts +3 -1
  60. package/dist/types/setup/paseo/announce.d.ts +4 -3
  61. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  62. package/dist/types/system-prompt.d.ts +8 -0
  63. package/dist/types/task/executor.d.ts +3 -0
  64. package/dist/types/tools/bash.d.ts +1 -0
  65. package/dist/types/tools/github-cache.d.ts +2 -1
  66. package/dist/types/tools/index.d.ts +2 -0
  67. package/package.json +9 -7
  68. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  69. package/src/capability/index.ts +14 -2
  70. package/src/capability/types.ts +4 -0
  71. package/src/cli/customize-doctor.ts +155 -12
  72. package/src/cli.ts +22 -0
  73. package/src/commands/sdk.ts +74 -31
  74. package/src/config/file-lock-gc.ts +40 -2
  75. package/src/config/file-lock.ts +198 -14
  76. package/src/config/model-preset-registry.ts +115 -16
  77. package/src/config/model-registry.ts +16 -12
  78. package/src/coordinator-mcp/question-state.ts +33 -2
  79. package/src/coordinator-mcp/server.ts +477 -101
  80. package/src/crash/sanitize.ts +1 -1
  81. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  82. package/src/discovery/builtin.ts +50 -62
  83. package/src/discovery/helpers.ts +297 -32
  84. package/src/exec/bash-executor.ts +330 -50
  85. package/src/exec/bash-shell-guardian.ts +388 -0
  86. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  87. package/src/exec/bash-shell-signals.ts +29 -0
  88. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  89. package/src/exec/bash-shell-supervisor.ts +359 -0
  90. package/src/exec/bash-shell-worker-entry.ts +5 -0
  91. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  92. package/src/exec/bash-shell-worker.ts +116 -0
  93. package/src/exec/isolated-shell.ts +393 -0
  94. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  95. package/src/extensibility/extensions/function-hooks.ts +959 -0
  96. package/src/extensibility/extensions/index.ts +22 -0
  97. package/src/extensibility/extensions/loader.ts +53 -2
  98. package/src/extensibility/extensions/runner.ts +895 -264
  99. package/src/extensibility/extensions/types.ts +42 -0
  100. package/src/extensibility/extensions/wrapper.ts +56 -15
  101. package/src/extensibility/gjc-plugins/compiler.ts +20 -2
  102. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  103. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  104. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  105. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  106. package/src/extensibility/gjc-plugins/types.ts +25 -0
  107. package/src/extensibility/hooks/loader.ts +133 -16
  108. package/src/extensibility/hooks/types.ts +28 -0
  109. package/src/extensibility/runtime-skill-discovery.ts +80 -47
  110. package/src/extensibility/skill-management.ts +200 -52
  111. package/src/extensibility/skills.ts +29 -9
  112. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  113. package/src/gjc-runtime/gc-runtime.ts +29 -0
  114. package/src/gjc-runtime/linux-proc.ts +14 -0
  115. package/src/gjc-runtime/session-state-lock.ts +51 -8
  116. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  117. package/src/gjc-runtime/state-writer.ts +30 -29
  118. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  119. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  120. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  121. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  122. package/src/harness-control-plane/session-lease.ts +2 -2
  123. package/src/internal-urls/docs-index.generated.ts +6 -6
  124. package/src/lsp/config.ts +10 -3
  125. package/src/lsp/index.ts +1 -1
  126. package/src/lsp/path-trust.ts +69 -14
  127. package/src/main.ts +6 -1
  128. package/src/modes/acp/acp-agent.ts +16 -3
  129. package/src/modes/components/bash-execution.ts +4 -1
  130. package/src/modes/components/execution-shared.ts +4 -1
  131. package/src/modes/components/session-observer-overlay.ts +1 -1
  132. package/src/modes/components/tool-status-header.ts +3 -1
  133. package/src/modes/controllers/command-controller.ts +1 -0
  134. package/src/modes/controllers/event-controller.ts +3 -2
  135. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  136. package/src/modes/controllers/input-controller.ts +6 -3
  137. package/src/modes/interactive-mode.ts +6 -5
  138. package/src/modes/print-mode.ts +5 -1
  139. package/src/modes/prompt-action-autocomplete.ts +54 -27
  140. package/src/modes/runtime-init.ts +2 -0
  141. package/src/modes/types.ts +16 -3
  142. package/src/modes/utils/ui-helpers.ts +1 -0
  143. package/src/prompts/tools/subagent.md +1 -0
  144. package/src/runtime-mcp/manager.ts +20 -2
  145. package/src/runtime-mcp/pool.test.ts +20 -11
  146. package/src/sdk/broker/broker.ts +119 -28
  147. package/src/sdk/broker/ensure.ts +248 -41
  148. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  149. package/src/sdk/broker/lifecycle.ts +56 -6
  150. package/src/sdk/broker/startup-failure.ts +15 -6
  151. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  152. package/src/sdk/bus/chat-daemon-runtime.ts +35 -5
  153. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  154. package/src/sdk/bus/conversation-store.ts +29 -0
  155. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  156. package/src/sdk/bus/slack-conversation.ts +1 -0
  157. package/src/sdk/bus/slack-daemon.ts +387 -48
  158. package/src/sdk/bus/telegram-daemon.ts +16 -2
  159. package/src/sdk/bus/topic-registry.ts +19 -0
  160. package/src/sdk/cli/master-cli.ts +1 -1
  161. package/src/sdk/cli/session-cli.ts +2 -2
  162. package/src/sdk/client/client.ts +66 -17
  163. package/src/sdk/host/host.ts +29 -9
  164. package/src/sdk/host/query/revision-store.ts +19 -13
  165. package/src/sdk/host/reverse-leases.ts +7 -4
  166. package/src/sdk/host/session-runtime.test.ts +50 -8
  167. package/src/sdk/host/session-runtime.ts +158 -51
  168. package/src/sdk/lifecycle/service.ts +4 -0
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/router/session-router.ts +89 -38
  171. package/src/sdk/session.ts +155 -26
  172. package/src/sdk/transport/relay.ts +35 -13
  173. package/src/session/agent-session.ts +117 -52
  174. package/src/session/fallback-chain-controller.ts +38 -0
  175. package/src/session/internal/managed-session-storage.ts +19 -5
  176. package/src/session/messages.ts +5 -0
  177. package/src/session/session-manager.ts +29 -5
  178. package/src/session-import/redact.ts +5 -1
  179. package/src/setup/paseo/announce.ts +49 -43
  180. package/src/setup/paseo/setup-deps.ts +1 -1
  181. package/src/skill-state/canonical-skills.ts +12 -0
  182. package/src/skills/skills.test.ts +41 -21
  183. package/src/system-prompt.ts +25 -3
  184. package/src/task/executor.ts +5 -0
  185. package/src/task/index.ts +4 -0
  186. package/src/tools/bash.ts +28 -11
  187. package/src/tools/gh.ts +26 -3
  188. package/src/tools/github-cache.ts +68 -16
  189. package/src/tools/index.ts +2 -0
  190. package/src/tools/job.ts +13 -2
  191. package/src/tools/read.ts +14 -2
  192. package/src/tools/render-utils.ts +1 -3
  193. package/src/tools/resource-gc.ts +28 -8
  194. package/src/tools/skill-discovery.ts +5 -0
  195. package/src/tools/skill.ts +15 -0
  196. package/src/tools/subagent.ts +3 -2
  197. package/src/tools/tool-catalog.generated.ts +2 -2
  198. package/src/tools/ultragoal-ask-guard.ts +2 -2
@@ -32,8 +32,10 @@ import {
32
32
  $flag,
33
33
  getAgentDbPath,
34
34
  getAgentDir,
35
+ getAgentProfileAuthority,
35
36
  getProjectDir,
36
37
  logger,
38
+ normalizePathForComparison,
37
39
  postmortem,
38
40
  prompt,
39
41
  Snowflake,
@@ -91,10 +93,14 @@ import { CustomToolAdapter } from "../extensibility/custom-tools/wrapper";
91
93
  import {
92
94
  createCustomToolSettings,
93
95
  type ExtensionContext,
96
+ type ExtensionEvent,
94
97
  type ExtensionFactory,
95
98
  ExtensionRunner,
96
99
  ExtensionToolWrapper,
97
100
  type ExtensionUIContext,
101
+ type FunctionHook,
102
+ type FunctionHookEventType,
103
+ type FunctionHookResult,
98
104
  type LoadExtensionsResult,
99
105
  loadExtensionFromFactory,
100
106
  type ToolDefinition,
@@ -168,6 +174,7 @@ import {
168
174
  unregisterOwnedRegistration,
169
175
  } from "../session/terminal-abort";
170
176
  import { formatNoModelsAvailableFallback } from "../setup/model-onboarding-guidance";
177
+ import { getSkillFilesystemIdentity } from "../skill-state/canonical-skills";
171
178
  import {
172
179
  type BuildSystemPromptResult,
173
180
  buildSystemPrompt as buildSystemPromptInternal,
@@ -438,6 +445,8 @@ export interface CreateAgentSessionOptions {
438
445
  cwd?: string;
439
446
  /** Global config directory. Default: ~/.gjc/agent */
440
447
  agentDir?: string;
448
+ /** Resolver-owned classification for `agentDir`; required when authority must survive HOME/config refreshes. */
449
+ profileAuthority?: "default" | "custom";
441
450
  /** Spawns to allow. Default: "*" */
442
451
  spawns?: string;
443
452
 
@@ -781,10 +790,11 @@ export async function discoverSkills(
781
790
  */
782
791
  export async function discoverContextFiles(
783
792
  cwd?: string,
784
- _agentDir?: string,
793
+ agentDir?: string,
785
794
  ): Promise<Array<{ path: string; content: string; depth?: number }>> {
786
795
  return await loadContextFilesInternal({
787
796
  cwd: cwd ?? getProjectDir(),
797
+ agentDir,
788
798
  });
789
799
  }
790
800
 
@@ -821,6 +831,8 @@ export interface BuildSystemPromptOptions {
821
831
  skills?: Skill[];
822
832
  contextFiles?: Array<{ path: string; content: string }>;
823
833
  cwd?: string;
834
+ agentDir?: string;
835
+ profileAuthority?: "default" | "custom";
824
836
  appendPrompt?: string;
825
837
  repeatToolDescriptions?: boolean;
826
838
  }
@@ -834,6 +846,8 @@ export interface BuildSystemPromptOptions {
834
846
  export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
835
847
  return await buildSystemPromptInternal({
836
848
  cwd: options.cwd,
849
+ agentDir: options.agentDir,
850
+ profileAuthority: options.profileAuthority,
837
851
  skills: options.skills,
838
852
  contextFiles: options.contextFiles,
839
853
  appendSystemPrompt: options.appendPrompt,
@@ -1015,14 +1029,89 @@ export function createPluginHooksExtension(hooks: ConstrainedPluginHook[]): Exte
1015
1029
  if (!normalized) throw new Error("Hook normalization invariant violated");
1016
1030
  const registrationEvent = normalized.runtimeEvent;
1017
1031
  const target = normalized.toolName === "*" ? undefined : normalized.toolName;
1018
- const handler = target
1019
- ? (event: { toolName?: string; tool?: { name?: string }; name?: string }, ...rest: unknown[]) => {
1020
- const toolName = event?.toolName ?? event?.tool?.name ?? event?.name;
1021
- if (toolName !== target) return undefined;
1022
- return (hook.handler as (...a: unknown[]) => unknown)(event, ...rest);
1032
+ if (!hook.functionHook) {
1033
+ const legacyHandler: (...args: unknown[]) => unknown = target
1034
+ ? (event: unknown, ...rest: unknown[]) => {
1035
+ const record = event as { toolName?: string; tool?: { name?: string }; name?: string } | undefined;
1036
+ const toolName = record?.toolName ?? record?.tool?.name ?? record?.name;
1037
+ if (toolName !== target) return undefined;
1038
+ return (hook.handler as (...args: unknown[]) => unknown)(event, ...rest);
1039
+ }
1040
+ : (hook.handler as (...args: unknown[]) => unknown);
1041
+ (api.on as (event: string, handler: (...args: unknown[]) => void) => void)(
1042
+ registrationEvent,
1043
+ legacyHandler,
1044
+ );
1045
+ continue;
1046
+ }
1047
+ const handler: FunctionHook = async (invocation, _capabilities, next) => {
1048
+ const legacyResult = await (hook.handler as (...args: unknown[]) => unknown)(invocation.payload, undefined);
1049
+ if (legacyResult === undefined) return await next();
1050
+ if (invocation.eventType === "tool_call") {
1051
+ const result = legacyResult as { block?: unknown; reason?: unknown; input?: unknown };
1052
+ if (result.block === true) {
1053
+ return {
1054
+ action: "deny",
1055
+ reason:
1056
+ typeof result.reason === "string" ? result.reason : "Tool execution blocked by plugin hook",
1057
+ };
1058
+ }
1059
+ if (result.input !== undefined) {
1060
+ return {
1061
+ action: "continue",
1062
+ event: { ...invocation.payload, input: result.input as Record<string, unknown> } as ExtensionEvent,
1063
+ } as FunctionHookResult;
1064
+ }
1065
+ }
1066
+ if (invocation.eventType === "tool_result") {
1067
+ const result = legacyResult as { content?: unknown; details?: unknown; isError?: unknown };
1068
+ if (result.content !== undefined || result.details !== undefined || result.isError !== undefined) {
1069
+ return {
1070
+ action: "continue",
1071
+ event: {
1072
+ ...invocation.payload,
1073
+ ...(result.content === undefined ? {} : { content: result.content }),
1074
+ ...(result.details === undefined ? {} : { details: result.details }),
1075
+ ...(result.isError === undefined ? {} : { isError: result.isError }),
1076
+ },
1077
+ } as FunctionHookResult;
1078
+ }
1079
+ }
1080
+ if (invocation.eventType === "context" && (legacyResult as { messages?: unknown }).messages !== undefined) {
1081
+ return {
1082
+ action: "continue",
1083
+ event: {
1084
+ ...invocation.payload,
1085
+ messages: (legacyResult as { messages: unknown }).messages,
1086
+ } as ExtensionEvent,
1087
+ } as FunctionHookResult;
1088
+ }
1089
+ if (
1090
+ invocation.eventType.startsWith("session_before_") &&
1091
+ (legacyResult as { cancel?: unknown }).cancel === true
1092
+ ) {
1093
+ return { action: "deny", reason: "Session change blocked by plugin hook" };
1094
+ }
1095
+ if (invocation.eventType === "before_agent_start") {
1096
+ const result = legacyResult as { message?: unknown; systemPrompt?: unknown };
1097
+ if (result.message !== undefined || result.systemPrompt !== undefined) {
1098
+ return {
1099
+ action: "return",
1100
+ value: {
1101
+ ...(result.message === undefined ? {} : { messages: [result.message] }),
1102
+ ...(result.systemPrompt === undefined ? {} : { systemPrompt: result.systemPrompt }),
1103
+ },
1104
+ } as FunctionHookResult;
1023
1105
  }
1024
- : hook.handler;
1025
- (api.on as (event: string, handler: (...args: unknown[]) => unknown) => void)(registrationEvent, handler);
1106
+ }
1107
+ return await next();
1108
+ };
1109
+ const options = {
1110
+ ...(target === undefined ? {} : { target }),
1111
+ registrationId: hook.extensionId,
1112
+ ...(hook.grant ?? {}),
1113
+ };
1114
+ api.registerFunctionHook(registrationEvent as FunctionHookEventType, handler, options);
1026
1115
  }
1027
1116
  };
1028
1117
  }
@@ -1112,14 +1201,14 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
1112
1201
  function withEmbeddedDefaultGjcSkills(skills: Skill[]): Skill[] {
1113
1202
  const byName = new Map<string, Skill>();
1114
1203
  for (const skill of skills) {
1115
- const key = skill.name.toLowerCase();
1204
+ const key = getSkillFilesystemIdentity(skill.name);
1116
1205
  if (!byName.has(key)) byName.set(key, skill);
1117
1206
  }
1118
1207
  // The four public GJC workflow skills are a product invariant: even if a
1119
1208
  // caller-supplied or filesystem skill shares a name, the bundled definition
1120
1209
  // wins so workflow routing can never be silently hijacked.
1121
1210
  for (const defaultSkill of getEmbeddedDefaultGjcSkills()) {
1122
- byName.set(defaultSkill.name.toLowerCase(), defaultSkill);
1211
+ byName.set(getSkillFilesystemIdentity(defaultSkill.name), defaultSkill);
1123
1212
  }
1124
1213
  return [...byName.values()];
1125
1214
  }
@@ -1393,7 +1482,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1393
1482
  }
1394
1483
  const cwd = options.cwd ?? getProjectDir();
1395
1484
  const explicitMcpConfigPath = !isCanonicalSubSession && !options.mcpManager ? options.mcpConfigPath : undefined;
1396
- const agentDir = options.agentDir ?? getDefaultAgentDir();
1485
+ const agentDir = options.agentDir ?? options.settings?.getAgentDir() ?? getDefaultAgentDir();
1486
+ const profileAuthority =
1487
+ options.profileAuthority ??
1488
+ (options.agentDir !== undefined || options.settings !== undefined
1489
+ ? normalizePathForComparison(agentDir) !== normalizePathForComparison(getAgentDir())
1490
+ ? "custom"
1491
+ : getAgentProfileAuthority()
1492
+ : getAgentProfileAuthority());
1397
1493
  const eventBus = options.eventBus ?? new EventBus();
1398
1494
  const hasInjectedAuth = options.authStorage !== undefined || options.modelRegistry !== undefined;
1399
1495
 
@@ -1581,7 +1677,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1581
1677
  // session-context build, tool creation, MCP discovery, and extension discovery.
1582
1678
  const contextFilesResultPromise = options.contextFiles
1583
1679
  ? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })
1584
- : logger.time("discoverContextFiles", loadContextFilesResultInternal, { cwd });
1680
+ : logger.time("discoverContextFiles", loadContextFilesResultInternal, { cwd, agentDir, profileAuthority });
1585
1681
  contextFilesResultPromise.catch(() => {});
1586
1682
  const promptTemplatesPromise = options.promptTemplates
1587
1683
  ? Promise.resolve(options.promptTemplates)
@@ -1982,9 +2078,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1982
2078
  skills = withEmbeddedDefaultGjcSkills(options.skills);
1983
2079
  skillWarnings = [];
1984
2080
  } else if (settings.get("skills.enabled")) {
1985
- const skillsResult = await logger.time("loadSkills", loadSkills, {
2081
+ const skillsResult = await logger.time("discoverSkills", loadSkills, {
1986
2082
  ...settings.getGroup("skills"),
1987
2083
  agentDir,
2084
+ profileAuthority,
1988
2085
  cwd,
1989
2086
  disabledExtensions: settings.get("disabledExtensions"),
1990
2087
  });
@@ -2006,7 +2103,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2006
2103
  const rulesResult =
2007
2104
  options.rules !== undefined
2008
2105
  ? { items: options.rules, warnings: undefined }
2009
- : await loadCapability<Rule>(ruleCapability.id, { cwd, agentDir, settings });
2106
+ : await loadCapability<Rule>(ruleCapability.id, { cwd, agentDir, profileAuthority, settings });
2010
2107
  const rulebookRules: Rule[] = [];
2011
2108
  const alwaysApplyRules: Rule[] = [];
2012
2109
  for (const rule of rulesResult.items) {
@@ -2334,7 +2431,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2334
2431
  */
2335
2432
  const applyRescopedReadState = async (to: string): Promise<void> => {
2336
2433
  try {
2337
- const rediscovered = await loadContextFilesResultInternal({ cwd: to });
2434
+ const rediscovered = await loadContextFilesResultInternal({ cwd: to, agentDir, profileAuthority });
2338
2435
  contextFiles = rediscovered.contextFiles;
2339
2436
  } catch (error) {
2340
2437
  logger.warn("Failed to re-discover context files after session rescope", {
@@ -2375,6 +2472,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2375
2472
  return sessionManager.getCwd();
2376
2473
  },
2377
2474
  hasUI: options.hasUI ?? false,
2475
+ profileAuthority,
2378
2476
  workflowGateEligible: true,
2379
2477
  enableLsp,
2380
2478
  get hasEditTool() {
@@ -2693,6 +2791,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2693
2791
  }
2694
2792
  : {}),
2695
2793
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
2794
+ getSessionAgentDir: () => session?.getSessionAgentDir() ?? options.agentDir ?? settings.getAgentDir(),
2696
2795
  getEvalKernelOwnerId: () => evalKernelOwnerId,
2697
2796
  assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
2698
2797
  trackEvalExecution: (execution, abortController) =>
@@ -2974,7 +3073,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2974
3073
  cwdCapturingToolNames.push(...pluginToolResult.tools.map(tool => tool.name));
2975
3074
  }
2976
3075
  for (const q of pluginToolResult.quarantine) {
2977
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3076
+ gjcFindings.add({
3077
+ identity: q.identity,
3078
+ surfaceId: q.surfaceId,
3079
+ code: q.code,
3080
+ message: q.message,
3081
+ decision: "quarantined",
3082
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3083
+ });
2978
3084
  logger.warn("Quarantined GJC plugin surface", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
2979
3085
  }
2980
3086
  } catch (error) {
@@ -3064,7 +3170,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3064
3170
  try {
3065
3171
  const { configs, quarantine } = await buildPluginMcpConfigs({ cwd });
3066
3172
  for (const q of quarantine) {
3067
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3173
+ gjcFindings.add({
3174
+ identity: q.identity,
3175
+ surfaceId: q.surfaceId,
3176
+ code: q.code,
3177
+ message: q.message,
3178
+ decision: "quarantined",
3179
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3180
+ });
3068
3181
  logger.warn("Quarantined GJC plugin MCP", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
3069
3182
  }
3070
3183
  const pluginNames = new Set(Object.keys(configs));
@@ -3209,12 +3322,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3209
3322
  // Always-on constrained plugin hooks (validated registry surfaces). Additive
3210
3323
  // and a no-op without installed plugins; the loader denies all dangerous APIs.
3211
3324
  try {
3212
- const pluginHookResult = await loadConstrainedPluginHooks({ cwd });
3325
+ const pluginHookResult = await loadConstrainedPluginHooks({
3326
+ cwd,
3327
+ activationGeneration: gjcActivationGeneration,
3328
+ });
3213
3329
  if (pluginHookResult.hooks.length > 0) {
3214
3330
  inlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));
3215
3331
  }
3216
3332
  for (const q of pluginHookResult.quarantine) {
3217
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3333
+ gjcFindings.add({
3334
+ identity: q.identity,
3335
+ surfaceId: q.surfaceId,
3336
+ code: q.code,
3337
+ message: q.message,
3338
+ decision: "quarantined",
3339
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3340
+ });
3218
3341
  logger.warn("Quarantined GJC plugin hook", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
3219
3342
  }
3220
3343
  } catch (error) {
@@ -3774,7 +3897,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3774
3897
  } else if (!toolRegistry.has("resolve")) {
3775
3898
  const resolveTool = await logger.time("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
3776
3899
  if (resolveTool) {
3777
- const wrappedResolveTool = wrapToolWithMetaNotice(resolveTool);
3900
+ const resolveWithNotice = wrapToolWithMetaNotice(resolveTool);
3901
+ const wrappedResolveTool = extensionRunner
3902
+ ? (new ExtensionToolWrapper(resolveWithNotice, extensionRunner) as AgentTool)
3903
+ : resolveWithNotice;
3778
3904
  builtinCandidateTools.push(wrappedResolveTool);
3779
3905
  toolRegistry.set(wrappedResolveTool.name, wrappedResolveTool);
3780
3906
  builtinToolIdentities.add(wrappedResolveTool);
@@ -3912,6 +4038,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3912
4038
  // Live cwd: the prompt is rebuilt after a rescope, and describing the
3913
4039
  // retired launcher root there is what makes the model pick wrong paths.
3914
4040
  cwd: getLiveCwd(),
4041
+ agentDir,
4042
+ profileAuthority,
3915
4043
  skills,
3916
4044
  contextFiles,
3917
4045
  tools: promptTools,
@@ -4184,21 +4312,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4184
4312
  if (!obfuscator?.hasSecrets() || !obfuscateMessagesFn) return converted;
4185
4313
  return obfuscateMessagesFn(obfuscator, converted);
4186
4314
  };
4187
- const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal, scope?: AttemptScopeRef) => {
4315
+ const transformContext = async (messages: AgentMessage[], signal?: AbortSignal, scope?: AttemptScopeRef) => {
4188
4316
  // External Agent events dispatch listeners without awaiting them. The
4189
4317
  // session-owned barrier makes any pre-admission artifact transformation
4190
4318
  // visible before this provider context is normalized.
4191
4319
  await session?.awaitPendingContextTransformations();
4192
- return extensionRunner ? await extensionRunner.emitContext(messages, scope) : messages;
4320
+ return extensionRunner ? await extensionRunner.emitContext(messages, scope, signal) : messages;
4193
4321
  };
4194
4322
  const onPayload = extensionRunner
4195
- ? async (payload: unknown, _model?: Model, scope?: AttemptScopeRef) => {
4196
- return await extensionRunner.emitBeforeProviderRequest(payload, scope);
4323
+ ? async (payload: unknown, _model?: Model, scope?: AttemptScopeRef, signal?: AbortSignal) => {
4324
+ return await extensionRunner.emitBeforeProviderRequest(payload, scope, signal);
4197
4325
  }
4198
4326
  : undefined;
4199
4327
  const onResponse: SimpleStreamOptions["onResponse"] | undefined = extensionRunner
4200
- ? async (response, model, scope) => {
4201
- await extensionRunner.emitAfterProviderResponse(response, model, scope);
4328
+ ? async (response, model, scope, signal) => {
4329
+ await extensionRunner.emitAfterProviderResponse(response, model, scope, signal);
4202
4330
  }
4203
4331
  : undefined;
4204
4332
 
@@ -4491,6 +4619,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4491
4619
  const reloaded = await loadSkills({
4492
4620
  ...settings.getGroup("skills"),
4493
4621
  agentDir,
4622
+ profileAuthority,
4494
4623
  cwd: reloadCwd,
4495
4624
  disabledExtensions: settings.get("disabledExtensions"),
4496
4625
  });
@@ -56,18 +56,25 @@ function upstreamUrl(url: string, token: string): string {
56
56
  return endpoint.toString();
57
57
  }
58
58
 
59
- function waitForDrain(stream: Writable): Promise<void> {
59
+ function waitForDrain(stream: Writable): { promise: Promise<void>; cancel: () => void } {
60
60
  const { promise, reject, resolve } = Promise.withResolvers<void>();
61
+ let settled = false;
61
62
  const onDrain = (): void => done(resolve);
62
63
  const onError = (error: Error): void => done(() => reject(error));
64
+ const onClose = (): void => done(() => reject(new Error("downstream_closed")));
63
65
  const done = (callback: () => void): void => {
66
+ if (settled) return;
67
+ settled = true;
64
68
  stream.removeListener("drain", onDrain);
65
69
  stream.removeListener("error", onError);
70
+ stream.removeListener("close", onClose);
66
71
  callback();
67
72
  };
68
73
  stream.once("drain", onDrain);
69
74
  stream.once("error", onError);
70
- return promise;
75
+ stream.once("close", onClose);
76
+ if (stream.destroyed) onClose();
77
+ return { promise, cancel: onDrain };
71
78
  }
72
79
 
73
80
  const WEBSOCKET_CONNECTING = 0;
@@ -96,7 +103,9 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
96
103
  const finished = Promise.withResolvers<void>();
97
104
  let closed = false;
98
105
  let completed = false;
99
- let downstreamBuffer = Buffer.alloc(0);
106
+ let downstreamChunks: Buffer[] = [];
107
+ let downstreamBytes = 0;
108
+ let downstreamDrain: { promise: Promise<void>; cancel: () => void } | undefined;
100
109
  const toWs: QueuedFrame[] = [];
101
110
  const toDownstream: QueuedFrame[] = [];
102
111
  let pendingToWs = 0;
@@ -123,6 +132,7 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
123
132
  const close = async (error?: Error): Promise<void> => {
124
133
  if (closed) return finished.promise.catch(() => undefined);
125
134
  closed = true;
135
+ downstreamDrain?.cancel();
126
136
  detach();
127
137
  options.downstream.pause();
128
138
  if (ws.readyState === WEBSOCKET_OPEN || ws.readyState === WEBSOCKET_CONNECTING) ws.close();
@@ -175,33 +185,45 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
175
185
  writingDownstream = true;
176
186
  if (frame.accounted) pendingToDownstream -= frame.bytes.length;
177
187
  try {
178
- if (!options.downstreamSink.write(frame.bytes)) await waitForDrain(options.downstreamSink);
188
+ if (!options.downstreamSink.write(frame.bytes) && !closed) {
189
+ downstreamDrain = waitForDrain(options.downstreamSink);
190
+ await downstreamDrain.promise;
191
+ }
179
192
  } catch (error) {
180
193
  await close(error instanceof Error ? error : new Error(String(error)));
181
194
  return;
182
195
  } finally {
196
+ downstreamDrain = undefined;
183
197
  writingDownstream = false;
184
198
  }
185
199
  void pumpDownstream();
186
200
  };
187
201
  const consumeLines = (chunk: Buffer): void => {
188
- downstreamBuffer = Buffer.concat([downstreamBuffer, chunk]);
189
- let newline = downstreamBuffer.indexOf(0x0a);
202
+ let start = 0;
203
+ let newline = chunk.indexOf(0x0a);
190
204
  while (newline >= 0) {
191
- const line = downstreamBuffer.subarray(0, newline);
192
- downstreamBuffer = downstreamBuffer.subarray(newline + 1);
193
- if (line.length === 0)
205
+ const length = downstreamBytes + newline - start;
206
+ if (length === 0)
194
207
  return fail({ type: "transport_error", code: "protocol_error", direction: "downstream->ws" });
195
- if (line.length > REQUEST_FRAME_BYTES)
208
+ if (length > REQUEST_FRAME_BYTES)
196
209
  return fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
210
+ downstreamChunks.push(chunk.subarray(start, newline));
211
+ const line = Buffer.concat(downstreamChunks, length);
212
+ downstreamChunks = [];
213
+ downstreamBytes = 0;
197
214
  if (options.validateDownstreamFrame && !options.validateDownstreamFrame(line.toString("utf8")))
198
215
  return fail({ type: "transport_error", code: "protocol_error", direction: "downstream->ws" });
199
216
  enqueue(toWs, "downstream->ws", line);
200
217
  if (closed) return;
201
- newline = downstreamBuffer.indexOf(0x0a);
218
+ start = newline + 1;
219
+ newline = chunk.indexOf(0x0a, start);
220
+ }
221
+ if (start < chunk.length) {
222
+ downstreamBytes += chunk.length - start;
223
+ if (downstreamBytes > REQUEST_FRAME_BYTES)
224
+ return fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
225
+ downstreamChunks.push(chunk.subarray(start));
202
226
  }
203
- if (downstreamBuffer.length > REQUEST_FRAME_BYTES)
204
- fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
205
227
  };
206
228
  const onData = (chunk: Buffer | string): void => consumeLines(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
207
229
  const onEnd = (): void => void close();