@gajae-code/coding-agent 0.3.2 → 0.4.0

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 (122) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/types/config/model-registry.d.ts +17 -10
  3. package/dist/types/config/models-config-schema.d.ts +37 -0
  4. package/dist/types/config/settings-schema.d.ts +5 -0
  5. package/dist/types/edit/diff.d.ts +16 -0
  6. package/dist/types/edit/modes/replace.d.ts +7 -0
  7. package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
  8. package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
  9. package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
  10. package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
  11. package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
  12. package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
  13. package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
  14. package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
  15. package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
  16. package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
  17. package/dist/types/extensibility/skills.d.ts +9 -1
  18. package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -2
  20. package/dist/types/harness-control-plane/storage.d.ts +7 -0
  21. package/dist/types/lsp/client.d.ts +1 -0
  22. package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
  23. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
  24. package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
  25. package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
  26. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
  27. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
  28. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
  29. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
  30. package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
  31. package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
  32. package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
  33. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
  34. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
  35. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
  36. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
  37. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
  38. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
  39. package/dist/types/modes/theme/theme.d.ts +2 -1
  40. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  41. package/dist/types/sdk.d.ts +7 -0
  42. package/dist/types/session/agent-session.d.ts +10 -0
  43. package/dist/types/session/blob-store.d.ts +17 -0
  44. package/dist/types/session/messages.d.ts +3 -0
  45. package/dist/types/session/session-storage.d.ts +6 -0
  46. package/dist/types/skill-state/active-state.d.ts +13 -0
  47. package/dist/types/thinking.d.ts +3 -2
  48. package/dist/types/tools/index.d.ts +3 -0
  49. package/package.json +9 -7
  50. package/src/cli.ts +14 -0
  51. package/src/commands/harness.ts +192 -7
  52. package/src/commands/ultragoal.ts +1 -21
  53. package/src/config/model-equivalence.ts +1 -1
  54. package/src/config/model-registry.ts +32 -5
  55. package/src/config/models-config-schema.ts +7 -2
  56. package/src/config/settings-schema.ts +4 -1
  57. package/src/discovery/claude-plugins.ts +25 -5
  58. package/src/edit/diff.ts +64 -1
  59. package/src/edit/modes/replace.ts +60 -2
  60. package/src/extensibility/gjc-plugins/activation.ts +87 -0
  61. package/src/extensibility/gjc-plugins/index.ts +9 -0
  62. package/src/extensibility/gjc-plugins/injection.ts +114 -0
  63. package/src/extensibility/gjc-plugins/loader.ts +131 -0
  64. package/src/extensibility/gjc-plugins/paths.ts +66 -0
  65. package/src/extensibility/gjc-plugins/schema.ts +79 -0
  66. package/src/extensibility/gjc-plugins/state.ts +29 -0
  67. package/src/extensibility/gjc-plugins/tools.ts +47 -0
  68. package/src/extensibility/gjc-plugins/types.ts +97 -0
  69. package/src/extensibility/gjc-plugins/validation.ts +76 -0
  70. package/src/extensibility/skills.ts +39 -7
  71. package/src/gjc-runtime/state-runtime.ts +93 -2
  72. package/src/gjc-runtime/state-writer.ts +17 -1
  73. package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
  74. package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
  75. package/src/gjc-runtime/workflow-manifest.ts +2 -2
  76. package/src/harness-control-plane/storage.ts +144 -2
  77. package/src/hashline/hash.ts +23 -0
  78. package/src/hooks/skill-state.ts +2 -0
  79. package/src/internal-urls/docs-index.generated.ts +5 -5
  80. package/src/lsp/client.ts +7 -0
  81. package/src/modes/acp/acp-agent.ts +25 -2
  82. package/src/modes/bridge/bridge-mode.ts +124 -2
  83. package/src/modes/controllers/input-controller.ts +14 -2
  84. package/src/modes/prompt-action-autocomplete.ts +49 -10
  85. package/src/modes/rpc/rpc-client.ts +57 -3
  86. package/src/modes/rpc/rpc-mode.ts +67 -0
  87. package/src/modes/rpc/rpc-types.ts +224 -2
  88. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  89. package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
  90. package/src/modes/shared/agent-wire/command-validation.ts +25 -1
  91. package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
  92. package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
  93. package/src/modes/shared/agent-wire/handshake.ts +43 -3
  94. package/src/modes/shared/agent-wire/protocol.ts +7 -0
  95. package/src/modes/shared/agent-wire/responses.ts +2 -2
  96. package/src/modes/shared/agent-wire/scopes.ts +2 -0
  97. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  98. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  99. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  100. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  101. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  102. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  103. package/src/modes/theme/theme.ts +6 -0
  104. package/src/runtime-mcp/client.ts +7 -4
  105. package/src/runtime-mcp/manager.ts +45 -13
  106. package/src/runtime-mcp/transports/http.ts +40 -14
  107. package/src/runtime-mcp/transports/stdio.ts +11 -10
  108. package/src/sdk.ts +47 -0
  109. package/src/session/agent-session.ts +211 -2
  110. package/src/session/blob-store.ts +84 -0
  111. package/src/session/messages.ts +3 -0
  112. package/src/session/session-manager.ts +390 -33
  113. package/src/session/session-storage.ts +26 -0
  114. package/src/setup/provider-onboarding.ts +2 -2
  115. package/src/skill-state/active-state.ts +89 -1
  116. package/src/task/discovery.ts +7 -1
  117. package/src/task/executor.ts +16 -2
  118. package/src/thinking.ts +8 -2
  119. package/src/tools/ask.ts +39 -9
  120. package/src/tools/index.ts +3 -0
  121. package/src/tools/skill.ts +15 -3
  122. package/src/utils/edit-mode.ts +1 -1
@@ -163,6 +163,10 @@ import type {
163
163
  } from "../extensibility/extensions";
164
164
  import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
165
165
  import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
166
+ import type { LoadedSubskillActivation } from "../extensibility/gjc-plugins";
167
+ import { resolveCurrentPhaseForParent } from "../extensibility/gjc-plugins/injection";
168
+ import { readActiveSubskillsForParent, toActiveSubskillEntry } from "../extensibility/gjc-plugins/state";
169
+ import { loadActiveSubskillTools } from "../extensibility/gjc-plugins/tools";
166
170
  import type { HookCommandContext } from "../extensibility/hooks/types";
167
171
  import type { Skill, SkillWarning } from "../extensibility/skills";
168
172
  import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
@@ -174,7 +178,9 @@ import type { Goal, GoalModeState } from "../goals/state";
174
178
  import type { HindsightSessionState } from "../hindsight/state";
175
179
  import { ensureWorkflowSkillActivationState } from "../hooks/skill-state";
176
180
  import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
181
+ import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
177
182
  import { resolveMemoryBackend } from "../memory-backend";
183
+ import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/unattended-session";
178
184
  import { getCurrentThemeName, theme } from "../modes/theme/theme";
179
185
  import type { PlanModeState } from "../plan-mode/state";
180
186
  import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
@@ -197,9 +203,14 @@ import {
197
203
  isMCPToolName,
198
204
  selectDiscoverableMCPToolNamesByServer,
199
205
  } from "../runtime-mcp/discoverable-tool-metadata";
206
+ import { MCPManager } from "../runtime-mcp/manager";
200
207
  import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
201
208
  import { formatNoCredentialOnboardingError, formatNoModelOnboardingError } from "../setup/model-onboarding-guidance";
202
- import { isCanonicalGjcWorkflowSkill } from "../skill-state/active-state";
209
+ import {
210
+ isCanonicalGjcWorkflowSkill,
211
+ readVisibleSkillActiveState,
212
+ syncSkillActiveState,
213
+ } from "../skill-state/active-state";
203
214
  import { assertDeepInterviewMutationAllowed } from "../skill-state/deep-interview-mutation-guard";
204
215
  import { invalidateHostMetadata } from "../ssh/connection-manager";
205
216
  import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
@@ -209,9 +220,11 @@ import {
209
220
  type DiscoverableTool,
210
221
  type DiscoverableToolSearchIndex,
211
222
  } from "../tool-discovery/tool-index";
223
+ import type { ToolSession } from "../tools";
224
+ import { AskTool } from "../tools/ask";
212
225
  import { assertEditableFile } from "../tools/auto-generated-guard";
213
226
  import type { CheckpointState } from "../tools/checkpoint";
214
- import { outputMeta } from "../tools/output-meta";
227
+ import { outputMeta, wrapToolWithMetaNotice } from "../tools/output-meta";
215
228
  import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils";
216
229
  import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
217
230
  import { ToolAbortError, ToolError } from "../tools/tool-errors";
@@ -337,6 +350,8 @@ export interface AgentSessionConfig {
337
350
  taskDepth?: number;
338
351
  /** Tool registry for LSP and settings */
339
352
  toolRegistry?: Map<string, AgentTool>;
353
+ /** Tool-session factory context used to lazily attach workflow-gate-only tools. */
354
+ workflowGateToolSession?: ToolSession;
340
355
  /** Current session pre-LLM message transform pipeline */
341
356
  transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
342
357
  /** Provider payload hook used by the active session request path */
@@ -844,6 +859,7 @@ export class AgentSession {
844
859
  #scheduledHiddenNextTurnGeneration: number | undefined = undefined;
845
860
  #planModeState: PlanModeState | undefined;
846
861
  #goalModeState: GoalModeState | undefined;
862
+ #workflowGateEmitter: WorkflowGateEmitter | undefined;
847
863
  #goalRuntime: GoalRuntime;
848
864
  #goalTurnCounter = 0;
849
865
  #planReferenceSent = false;
@@ -923,6 +939,7 @@ export class AgentSession {
923
939
 
924
940
  // Tool registry and prompt builder for extensions
925
941
  #toolRegistry: Map<string, AgentTool>;
942
+ #workflowGateToolSession: ToolSession | undefined;
926
943
  #transformContext: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
927
944
  #onPayload: SimpleStreamOptions["onPayload"] | undefined;
928
945
  #onResponse: SimpleStreamOptions["onResponse"] | undefined;
@@ -950,6 +967,8 @@ export class AgentSession {
950
967
  #discoverableToolSearchIndex: DiscoverableToolSearchIndex | null = null;
951
968
  #selectedDiscoveredToolNames = new Set<string>();
952
969
  #rpcHostToolNames = new Set<string>();
970
+ #gjcSubskillToolNames = new Set<string>();
971
+ #gjcSubskillToolSignature: string | undefined;
953
972
  #defaultSelectedMCPServerNames = new Set<string>();
954
973
  #defaultSelectedMCPToolNames = new Set<string>();
955
974
  #sessionDefaultSelectedMCPToolNames = new Map<string, string[]>();
@@ -1100,6 +1119,7 @@ export class AgentSession {
1100
1119
  }
1101
1120
  this.#validateRetryFallbackChains();
1102
1121
  this.#toolRegistry = config.toolRegistry ?? new Map();
1122
+ this.#workflowGateToolSession = config.workflowGateToolSession;
1103
1123
  this.#requestedToolNames = config.requestedToolNames;
1104
1124
  this.#transformContext = config.transformContext ?? (messages => messages);
1105
1125
  this.#onPayload = config.onPayload;
@@ -3112,6 +3132,14 @@ export class AgentSession {
3112
3132
  AsyncJobManager.setInstance(undefined);
3113
3133
  }
3114
3134
  }
3135
+ const mcpManager = MCPManager.instance();
3136
+ if (mcpManager) {
3137
+ await mcpManager.disconnectAll();
3138
+ if (MCPManager.instance() === mcpManager) {
3139
+ MCPManager.setInstance(undefined);
3140
+ }
3141
+ }
3142
+ await shutdownAllLspClients();
3115
3143
  const pythonExecutionsSettled = await this.#prepareEvalExecutionsForDispose();
3116
3144
  if (!pythonExecutionsSettled) {
3117
3145
  logger.warn(
@@ -3942,6 +3970,123 @@ export class AgentSession {
3942
3970
  );
3943
3971
  }
3944
3972
 
3973
+ async #hasActiveGjcSubskillTools(parent: string, sessionId: string | undefined): Promise<boolean> {
3974
+ if (!parent.trim()) return false;
3975
+ const cwd = this.sessionManager.getCwd();
3976
+ const phase = await resolveCurrentPhaseForParent({ cwd, sessionId, parent });
3977
+ const entries = await readActiveSubskillsForParent({ cwd, sessionId, parent, phase });
3978
+ return entries.some(entry => (entry.toolPaths ?? []).some(toolPath => toolPath.trim().length > 0));
3979
+ }
3980
+
3981
+ #getCustomToolContext(): CustomToolContext {
3982
+ return {
3983
+ sessionManager: this.sessionManager,
3984
+ modelRegistry: this.#modelRegistry,
3985
+ model: this.model,
3986
+ isIdle: () => !this.isStreaming,
3987
+ hasQueuedMessages: () => this.queuedMessageCount > 0,
3988
+ abort: () => {
3989
+ this.agent.abort();
3990
+ },
3991
+ };
3992
+ }
3993
+
3994
+ #computeGjcSubskillToolSignature(tools: CustomTool[]): string {
3995
+ return tools
3996
+ .map(tool => `${tool.name}\u0000${tool.description}\u0000${JSON.stringify(tool.parameters)}`)
3997
+ .sort()
3998
+ .join("\u0001");
3999
+ }
4000
+
4001
+ /**
4002
+ * Refresh plugin sub-skill tools after workflow/sub-skill activation or phase changes.
4003
+ */
4004
+ async refreshGjcSubskillTools(): Promise<void> {
4005
+ const activeState = await readVisibleSkillActiveState(
4006
+ this.sessionManager.getCwd(),
4007
+ this.sessionManager.getSessionId(),
4008
+ );
4009
+ const activeSkill =
4010
+ this.#activeSkillState?.skill ??
4011
+ activeState?.skill ??
4012
+ activeState?.active_skills?.find(entry => entry.active !== false)?.skill;
4013
+ const parent = activeSkill?.trim();
4014
+ if (!parent) {
4015
+ if (this.#gjcSubskillToolNames.size === 0) return;
4016
+ const previousGjcSubskillToolNames = new Set(this.#gjcSubskillToolNames);
4017
+ const previousActiveToolNames = this.getActiveToolNames();
4018
+ for (const name of previousGjcSubskillToolNames) {
4019
+ this.#toolRegistry.delete(name);
4020
+ }
4021
+ this.#gjcSubskillToolNames.clear();
4022
+ this.#invalidateDiscoveryCaches();
4023
+ await this.#applyActiveToolsByName(
4024
+ previousActiveToolNames.filter(name => !previousGjcSubskillToolNames.has(name)),
4025
+ );
4026
+ return;
4027
+ }
4028
+
4029
+ const cwd = this.sessionManager.getCwd();
4030
+ const sessionId =
4031
+ this.#activeSkillState?.sessionId ?? activeState?.session_id ?? this.sessionManager.getSessionId();
4032
+ if (this.#gjcSubskillToolNames.size === 0 && !(await this.#hasActiveGjcSubskillTools(parent, sessionId))) return;
4033
+
4034
+ const phase = await resolveCurrentPhaseForParent({ cwd, sessionId, parent });
4035
+ const reservedToolNames = Array.from(this.#toolRegistry.keys()).filter(
4036
+ name => !this.#gjcSubskillToolNames.has(name),
4037
+ );
4038
+ const customTools = await loadActiveSubskillTools({ cwd, sessionId, parent, phase, reservedToolNames });
4039
+ const nextToolNames = customTools.map(tool => tool.name);
4040
+ const uniqueToolNames = new Set(nextToolNames);
4041
+ if (uniqueToolNames.size !== nextToolNames.length) {
4042
+ throw new Error("GJC sub-skill tool names must be unique");
4043
+ }
4044
+
4045
+ const previousGjcSubskillToolNames = new Set(this.#gjcSubskillToolNames);
4046
+ const nextSignature = this.#computeGjcSubskillToolSignature(customTools);
4047
+ if (this.#gjcSubskillToolSignature === nextSignature) {
4048
+ return;
4049
+ }
4050
+
4051
+ const previousActiveToolNames = this.getActiveToolNames();
4052
+ for (const name of previousGjcSubskillToolNames) {
4053
+ this.#toolRegistry.delete(name);
4054
+ }
4055
+ this.#gjcSubskillToolNames.clear();
4056
+ this.#gjcSubskillToolSignature = undefined;
4057
+
4058
+ const getCustomToolContext = () => this.#getCustomToolContext();
4059
+ for (const customTool of customTools) {
4060
+ const wrapped = CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool;
4061
+ const finalTool = (
4062
+ this.#extensionRunner ? new ExtensionToolWrapper(wrapped, this.#extensionRunner) : wrapped
4063
+ ) as AgentTool;
4064
+ this.#toolRegistry.set(finalTool.name, finalTool);
4065
+ this.#gjcSubskillToolNames.add(finalTool.name);
4066
+ }
4067
+ this.#gjcSubskillToolSignature = nextSignature;
4068
+
4069
+ this.#invalidateDiscoveryCaches();
4070
+ const activeNonGjcSubskillToolNames = previousActiveToolNames.filter(
4071
+ name => !previousGjcSubskillToolNames.has(name),
4072
+ );
4073
+ const preservedGjcSubskillToolNames = previousActiveToolNames.filter(
4074
+ name => previousGjcSubskillToolNames.has(name) && this.#gjcSubskillToolNames.has(name),
4075
+ );
4076
+ const autoActivatedGjcSubskillToolNames = customTools
4077
+ .filter(tool => !tool.hidden && !previousGjcSubskillToolNames.has(tool.name))
4078
+ .map(tool => tool.name);
4079
+ await this.#applyActiveToolsByName(
4080
+ Array.from(
4081
+ new Set([
4082
+ ...activeNonGjcSubskillToolNames,
4083
+ ...preservedGjcSubskillToolNames,
4084
+ ...autoActivatedGjcSubskillToolNames,
4085
+ ]),
4086
+ ),
4087
+ );
4088
+ }
4089
+
3945
4090
  /** Whether auto-compaction is currently running */
3946
4091
  get isCompacting(): boolean {
3947
4092
  return this.#autoCompactionAbortController !== undefined || this.#compactionAbortController !== undefined;
@@ -4088,6 +4233,45 @@ export class AgentSession {
4088
4233
  this.#goalModeState = state;
4089
4234
  }
4090
4235
 
4236
+ getWorkflowGateEmitter(): WorkflowGateEmitter | undefined {
4237
+ return this.#workflowGateEmitter;
4238
+ }
4239
+
4240
+ setWorkflowGateEmitter(emitter: WorkflowGateEmitter | undefined): void {
4241
+ this.#workflowGateEmitter = emitter;
4242
+ if (emitter) {
4243
+ this.#ensureWorkflowGateAskTool();
4244
+ }
4245
+ }
4246
+
4247
+ #ensureWorkflowGateAskTool(): void {
4248
+ if (this.#toolRegistry.has("ask")) return;
4249
+ if (!this.#workflowGateToolSession) return;
4250
+
4251
+ const askTool = AskTool.createIf(this.#workflowGateToolSession);
4252
+ if (!askTool) return;
4253
+
4254
+ const wrappedTool = wrapToolWithMetaNotice(askTool as unknown as AgentTool);
4255
+ const finalTool: AgentTool = this.#extensionRunner
4256
+ ? new ExtensionToolWrapper(wrappedTool, this.#extensionRunner)
4257
+ : wrappedTool;
4258
+ this.#toolRegistry.set(finalTool.name, finalTool);
4259
+
4260
+ if (!this.getActiveToolNames().includes(finalTool.name)) {
4261
+ const activeTools = [
4262
+ ...this.agent.state.tools,
4263
+ this.#wrapToolForDeepInterviewMutationGuard(this.#wrapToolForAcpPermission(finalTool)),
4264
+ ];
4265
+ this.agent.setTools(activeTools);
4266
+ this.#invalidateDiscoveryCaches();
4267
+ void this.refreshBaseSystemPrompt().catch(error => {
4268
+ logger.warn("Failed to refresh system prompt after workflow gate ask tool registration", {
4269
+ error: error instanceof Error ? error.message : String(error),
4270
+ });
4271
+ });
4272
+ }
4273
+ }
4274
+
4091
4275
  get goalRuntime(): GoalRuntime {
4092
4276
  return this.#goalRuntime;
4093
4277
  }
@@ -4364,6 +4548,7 @@ export class AgentSession {
4364
4548
  const message = options?.synthetic
4365
4549
  ? { role: "developer" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() }
4366
4550
  : { role: "user" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() };
4551
+ await this.refreshGjcSubskillTools();
4367
4552
 
4368
4553
  if (eagerTodoPrelude) {
4369
4554
  this.#toolChoiceQueue.pushOnce(eagerTodoPrelude.toolChoice, {
@@ -4409,9 +4594,33 @@ export class AgentSession {
4409
4594
  // of relying on the skill prompt to run its own state-init steps.
4410
4595
  if (active) {
4411
4596
  await ensureWorkflowSkillActivationState({ cwd: this.sessionManager.getCwd(), skill, sessionId });
4597
+ const subskillDetails = details as {
4598
+ subskillActivation?: LoadedSubskillActivation;
4599
+ subskillActivationSet?: LoadedSubskillActivation[];
4600
+ };
4601
+ const subskillActivations =
4602
+ subskillDetails.subskillActivationSet && subskillDetails.subskillActivationSet.length > 0
4603
+ ? subskillDetails.subskillActivationSet
4604
+ : subskillDetails.subskillActivation
4605
+ ? [subskillDetails.subskillActivation]
4606
+ : [];
4607
+ if (subskillActivations.length > 0) {
4608
+ const skillBoundActivation = subskillDetails.subskillActivation ?? subskillActivations[0];
4609
+ await syncSkillActiveState({
4610
+ cwd: this.sessionManager.getCwd(),
4611
+ skill,
4612
+ active: true,
4613
+ phase: skillBoundActivation?.phase,
4614
+ sessionId,
4615
+ active_subskills: subskillActivations.map(toActiveSubskillEntry),
4616
+ });
4617
+ }
4412
4618
  }
4413
4619
  // In-memory tracking keeps `getActiveSkillState` accurate for the chain guard.
4414
4620
  this.#activeSkillState = active ? { skill, sessionId } : undefined;
4621
+ if (active) {
4622
+ await this.refreshGjcSubskillTools();
4623
+ }
4415
4624
  }
4416
4625
 
4417
4626
  async #syncSkillPromptActiveStateSafely(
@@ -73,6 +73,17 @@ export class BlobStore {
73
73
  }
74
74
  }
75
75
 
76
+ /** Synchronously read blob by hash, returns Buffer or null if not found. */
77
+ getSync(hash: string): Buffer | null {
78
+ const blobPath = path.join(this.dir, hash);
79
+ try {
80
+ return fs.readFileSync(blobPath);
81
+ } catch (err) {
82
+ if (isEnoent(err)) return null;
83
+ throw err;
84
+ }
85
+ }
86
+
76
87
  /** Check if a blob exists. */
77
88
  async has(hash: string): Promise<boolean> {
78
89
  try {
@@ -84,6 +95,43 @@ export class BlobStore {
84
95
  }
85
96
  }
86
97
 
98
+ export class MemoryBlobStore extends BlobStore {
99
+ #blobs = new Map<string, Buffer>();
100
+
101
+ constructor() {
102
+ super(":memory:");
103
+ }
104
+
105
+ async put(data: Buffer): Promise<BlobPutResult> {
106
+ return this.putSync(data);
107
+ }
108
+
109
+ putSync(data: Buffer): BlobPutResult {
110
+ const hash = new Bun.SHA256().update(data).digest("hex");
111
+ this.#blobs.set(hash, Buffer.from(data));
112
+ return {
113
+ hash,
114
+ path: `memory:${hash}`,
115
+ get ref() {
116
+ return `${BLOB_PREFIX}${hash}`;
117
+ },
118
+ };
119
+ }
120
+
121
+ async get(hash: string): Promise<Buffer | null> {
122
+ return this.getSync(hash);
123
+ }
124
+
125
+ getSync(hash: string): Buffer | null {
126
+ const data = this.#blobs.get(hash);
127
+ return data ? Buffer.from(data) : null;
128
+ }
129
+
130
+ async has(hash: string): Promise<boolean> {
131
+ return this.#blobs.has(hash);
132
+ }
133
+ }
134
+
87
135
  /** Check if a data string is a blob reference. */
88
136
  export function isBlobRef(data: string): boolean {
89
137
  return data.startsWith(BLOB_PREFIX);
@@ -166,3 +214,39 @@ export async function resolveImageData(blobStore: BlobStore, data: string): Prom
166
214
  }
167
215
  return buffer.toString("base64");
168
216
  }
217
+
218
+ /** Synchronously resolve an externalized provider image data URL back to its original string. */
219
+ export function resolveImageDataUrlSync(blobStore: BlobStore, data: string): string {
220
+ const hash = parseBlobRef(data);
221
+ if (!hash) return data;
222
+ const buffer = blobStore.getSync(hash);
223
+ if (!buffer) {
224
+ logger.warn("Blob not found for persisted image data URL", { hash });
225
+ return data;
226
+ }
227
+ return buffer.toString("utf8");
228
+ }
229
+
230
+ /** Synchronously resolve a blob reference back to base64 data. */
231
+ export function resolveImageDataSync(blobStore: BlobStore, data: string): string {
232
+ const hash = parseBlobRef(data);
233
+ if (!hash) return data;
234
+ const buffer = blobStore.getSync(hash);
235
+ if (!buffer) {
236
+ logger.warn("Blob not found for image reference", { hash });
237
+ return data;
238
+ }
239
+ return buffer.toString("base64");
240
+ }
241
+
242
+ /** Synchronously resolve a blob reference back to utf8 text. */
243
+ export function resolveTextBlobSync(blobStore: BlobStore, data: string): string {
244
+ const hash = parseBlobRef(data);
245
+ if (!hash) return data;
246
+ const buffer = blobStore.getSync(hash);
247
+ if (!buffer) {
248
+ logger.warn("Blob not found for text reference", { hash });
249
+ return data;
250
+ }
251
+ return buffer.toString("utf8");
252
+ }
@@ -27,6 +27,7 @@ export {
27
27
  createCompactionSummaryMessage,
28
28
  } from "@gajae-code/agent-core/compaction/messages";
29
29
 
30
+ import type { LoadedSubskillActivation } from "../extensibility/gjc-plugins";
30
31
  import type { OutputMeta } from "../tools/output-meta";
31
32
  import { formatOutputNotice } from "../tools/output-meta";
32
33
 
@@ -37,6 +38,8 @@ export interface SkillPromptDetails {
37
38
  path: string;
38
39
  args?: string;
39
40
  lineCount: number;
41
+ subskillActivation?: LoadedSubskillActivation;
42
+ subskillActivationSet?: LoadedSubskillActivation[];
40
43
  /** Internal: tag used by AgentSession to remove the pending-display chip
41
44
  * from `#steeringMessages` / `#followUpMessages` when the agent consumes
42
45
  * this message. Not surfaced to renderers; the `__` prefix signals