@gajae-code/coding-agent 0.3.2 → 0.4.1

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 (125) hide show
  1. package/CHANGELOG.md +39 -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 +19 -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/defaults/gjc/skills/deep-interview/SKILL.md +19 -23
  58. package/src/defaults/gjc/skills/ralplan/SKILL.md +7 -7
  59. package/src/discovery/claude-plugins.ts +25 -5
  60. package/src/edit/diff.ts +64 -1
  61. package/src/edit/modes/replace.ts +60 -2
  62. package/src/extensibility/gjc-plugins/activation.ts +87 -0
  63. package/src/extensibility/gjc-plugins/index.ts +9 -0
  64. package/src/extensibility/gjc-plugins/injection.ts +114 -0
  65. package/src/extensibility/gjc-plugins/loader.ts +131 -0
  66. package/src/extensibility/gjc-plugins/paths.ts +66 -0
  67. package/src/extensibility/gjc-plugins/schema.ts +79 -0
  68. package/src/extensibility/gjc-plugins/state.ts +29 -0
  69. package/src/extensibility/gjc-plugins/tools.ts +47 -0
  70. package/src/extensibility/gjc-plugins/types.ts +97 -0
  71. package/src/extensibility/gjc-plugins/validation.ts +76 -0
  72. package/src/extensibility/skills.ts +39 -7
  73. package/src/gjc-runtime/state-runtime.ts +93 -2
  74. package/src/gjc-runtime/state-writer.ts +17 -1
  75. package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
  76. package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
  77. package/src/gjc-runtime/workflow-manifest.ts +2 -2
  78. package/src/harness-control-plane/storage.ts +144 -2
  79. package/src/hashline/hash.ts +23 -0
  80. package/src/hooks/skill-state.ts +2 -0
  81. package/src/internal-urls/docs-index.generated.ts +5 -5
  82. package/src/lsp/client.ts +7 -0
  83. package/src/modes/acp/acp-agent.ts +25 -2
  84. package/src/modes/bridge/bridge-mode.ts +124 -2
  85. package/src/modes/controllers/input-controller.ts +14 -2
  86. package/src/modes/prompt-action-autocomplete.ts +49 -10
  87. package/src/modes/rpc/rpc-client.ts +79 -3
  88. package/src/modes/rpc/rpc-mode.ts +67 -0
  89. package/src/modes/rpc/rpc-types.ts +224 -2
  90. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  91. package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
  92. package/src/modes/shared/agent-wire/command-validation.ts +25 -1
  93. package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
  94. package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
  95. package/src/modes/shared/agent-wire/handshake.ts +43 -3
  96. package/src/modes/shared/agent-wire/protocol.ts +7 -0
  97. package/src/modes/shared/agent-wire/responses.ts +2 -2
  98. package/src/modes/shared/agent-wire/scopes.ts +2 -0
  99. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  100. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  101. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  102. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  103. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  104. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  105. package/src/modes/theme/theme.ts +6 -0
  106. package/src/prompts/system/system-prompt.md +9 -0
  107. package/src/runtime-mcp/client.ts +7 -4
  108. package/src/runtime-mcp/manager.ts +45 -13
  109. package/src/runtime-mcp/transports/http.ts +40 -14
  110. package/src/runtime-mcp/transports/stdio.ts +11 -10
  111. package/src/sdk.ts +47 -0
  112. package/src/session/agent-session.ts +211 -2
  113. package/src/session/blob-store.ts +84 -0
  114. package/src/session/messages.ts +3 -0
  115. package/src/session/session-manager.ts +390 -33
  116. package/src/session/session-storage.ts +26 -0
  117. package/src/setup/provider-onboarding.ts +2 -2
  118. package/src/skill-state/active-state.ts +89 -1
  119. package/src/task/discovery.ts +7 -1
  120. package/src/task/executor.ts +16 -2
  121. package/src/thinking.ts +8 -2
  122. package/src/tools/ask.ts +39 -9
  123. package/src/tools/index.ts +3 -0
  124. package/src/tools/skill.ts +15 -3
  125. package/src/utils/edit-mode.ts +1 -1
@@ -40,7 +40,9 @@ export interface SessionStorage {
40
40
  readTextPrefix(path: string, maxBytes: number): Promise<string>;
41
41
  writeText(path: string, content: string): Promise<void>;
42
42
  rename(path: string, nextPath: string): Promise<void>;
43
+ renameSync(path: string, nextPath: string): void;
43
44
  unlink(path: string): Promise<void>;
45
+ unlinkSync(path: string): void;
44
46
  deleteSessionWithArtifacts(sessionPath: string): Promise<void>;
45
47
  openWriter(path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }): SessionStorageWriter;
46
48
  }
@@ -198,10 +200,22 @@ export class FileSessionStorage implements SessionStorage {
198
200
  }
199
201
  }
200
202
 
203
+ renameSync(path: string, nextPath: string): void {
204
+ try {
205
+ fs.renameSync(path, nextPath);
206
+ } catch (err) {
207
+ throw toError(err);
208
+ }
209
+ }
210
+
201
211
  unlink(path: string): Promise<void> {
202
212
  return fs.promises.unlink(path);
203
213
  }
204
214
 
215
+ unlinkSync(path: string): void {
216
+ fs.unlinkSync(path);
217
+ }
218
+
205
219
  openWriter(path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }): SessionStorageWriter {
206
220
  return new FileSessionStorageWriter(path, options);
207
221
  }
@@ -375,10 +389,22 @@ export class MemorySessionStorage implements SessionStorage {
375
389
  return Promise.resolve();
376
390
  }
377
391
 
392
+ renameSync(path: string, nextPath: string): void {
393
+ const entry = this.#files.get(path);
394
+ if (!entry) throw new Error(`File not found: ${path}`);
395
+ this.#files.set(nextPath, entry);
396
+ this.#files.delete(path);
397
+ }
398
+
378
399
  unlink(path: string): Promise<void> {
379
400
  this.#files.delete(path);
380
401
  return Promise.resolve();
381
402
  }
403
+
404
+ unlinkSync(path: string): void {
405
+ this.#files.delete(path);
406
+ }
407
+
382
408
  deleteSessionWithArtifacts(_sessionPath: string): Promise<void> {
383
409
  return Promise.resolve();
384
410
  }
@@ -79,7 +79,7 @@ export const PROVIDER_PRESETS: readonly ProviderPreset[] = [
79
79
  providerId: "minimax-code",
80
80
  baseUrl: "https://api.minimax.io/v1",
81
81
  apiKeyEnv: "MINIMAX_CODE_API_KEY",
82
- models: ["MiniMax-M2.5"],
82
+ models: ["minimax-m3"],
83
83
  compat: MINIMAX_OPENAI_COMPAT,
84
84
  },
85
85
  {
@@ -92,7 +92,7 @@ export const PROVIDER_PRESETS: readonly ProviderPreset[] = [
92
92
  providerId: "minimax-code-cn",
93
93
  baseUrl: "https://api.minimaxi.com/v1",
94
94
  apiKeyEnv: "MINIMAX_CODE_CN_API_KEY",
95
- models: ["MiniMax-M2.5"],
95
+ models: ["minimax-m3"],
96
96
  compat: MINIMAX_OPENAI_COMPAT,
97
97
  },
98
98
  {
@@ -32,6 +32,17 @@ export interface WorkflowHudSummary {
32
32
 
33
33
  export type { WorkflowStateReceipt } from "./workflow-state-contract";
34
34
 
35
+ export interface ActiveSubskillEntry {
36
+ plugin: string;
37
+ subskillName: string;
38
+ parent: string;
39
+ bindsTo: string;
40
+ phase: string;
41
+ activationArg: string;
42
+ filePath: string;
43
+ toolPaths: string[];
44
+ }
45
+
35
46
  export interface SkillActiveEntry {
36
47
  skill: string;
37
48
  phase?: string;
@@ -47,6 +58,7 @@ export interface SkillActiveEntry {
47
58
  handoff_from?: string;
48
59
  handoff_to?: string;
49
60
  handoff_at?: string;
61
+ active_subskills?: ActiveSubskillEntry[];
50
62
  }
51
63
 
52
64
  export interface SkillActiveState {
@@ -64,6 +76,7 @@ export interface SkillActiveState {
64
76
  initialized_mode?: CanonicalGjcWorkflowSkill;
65
77
  initialized_state_path?: string;
66
78
  active_skills?: SkillActiveEntry[];
79
+ active_subskills?: ActiveSubskillEntry[];
67
80
  [key: string]: unknown;
68
81
  }
69
82
 
@@ -87,6 +100,7 @@ export interface SyncSkillActiveStateOptions {
87
100
  handoff_from?: string;
88
101
  handoff_to?: string;
89
102
  handoff_at?: string;
103
+ active_subskills?: ActiveSubskillEntry[];
90
104
  }
91
105
 
92
106
  const HUD_TEXT_LIMIT = 80;
@@ -188,6 +202,48 @@ function normalizeWorkflowStateReceipt(raw: unknown): WorkflowStateReceipt | und
188
202
  mutation_id: mutationId,
189
203
  };
190
204
  }
205
+ function normalizeActiveSubskillEntry(raw: unknown): ActiveSubskillEntry | null {
206
+ if (!raw || typeof raw !== "object") return null;
207
+ const record = raw as Record<string, unknown>;
208
+ const plugin = safeString(record.plugin).trim();
209
+ const subskillName = safeString(record.subskillName).trim();
210
+ const parent = safeString(record.parent).trim();
211
+ const bindsTo = safeString(record.bindsTo).trim();
212
+ const phase = safeString(record.phase).trim();
213
+ const activationArg = safeString(record.activationArg).trim();
214
+ const filePath = safeString(record.filePath).trim();
215
+ const toolPaths = Array.isArray(record.toolPaths)
216
+ ? record.toolPaths.map(item => safeString(item).trim()).filter(Boolean)
217
+ : [];
218
+ if (!plugin || !subskillName || !parent || !bindsTo || !phase || !activationArg || !filePath) return null;
219
+ return { plugin, subskillName, parent, bindsTo, phase, activationArg, filePath, toolPaths };
220
+ }
221
+
222
+ function normalizeActiveSubskillEntries(raw: unknown): ActiveSubskillEntry[] | undefined {
223
+ if (!Array.isArray(raw)) return undefined;
224
+ const entries = raw
225
+ .map(normalizeActiveSubskillEntry)
226
+ .filter((entry): entry is ActiveSubskillEntry => entry !== null);
227
+ return entries.length > 0 ? entries : undefined;
228
+ }
229
+
230
+ function activeSubskillEntryKey(entry: ActiveSubskillEntry): string {
231
+ return [entry.plugin, entry.parent, entry.phase, entry.activationArg].join("\0");
232
+ }
233
+
234
+ function unionActiveSubskillEntries(...entrySets: Array<ActiveSubskillEntry[] | undefined>): ActiveSubskillEntry[] {
235
+ const merged: ActiveSubskillEntry[] = [];
236
+ const seen = new Set<string>();
237
+ for (const entries of entrySets) {
238
+ for (const entry of entries ?? []) {
239
+ const key = activeSubskillEntryKey(entry);
240
+ if (seen.has(key)) continue;
241
+ seen.add(key);
242
+ merged.push(entry);
243
+ }
244
+ }
245
+ return merged;
246
+ }
191
247
 
192
248
  function encodePathSegment(value: string): string {
193
249
  return encodeURIComponent(value).replaceAll(".", "%2E");
@@ -204,6 +260,7 @@ function normalizeEntry(raw: unknown): SkillActiveEntry | null {
204
260
  if (!skill) return null;
205
261
  const hud = normalizeWorkflowHudSummary(record.hud);
206
262
  const receipt = normalizeWorkflowStateReceipt(record.receipt);
263
+ const activeSubskills = normalizeActiveSubskillEntries(record.active_subskills);
207
264
  return {
208
265
  ...record,
209
266
  skill,
@@ -219,6 +276,7 @@ function normalizeEntry(raw: unknown): SkillActiveEntry | null {
219
276
  handoff_at: safeString(record.handoff_at).trim() || undefined,
220
277
  ...(hud ? { hud } : {}),
221
278
  ...(receipt ? { receipt } : {}),
279
+ ...(activeSubskills ? { active_subskills: activeSubskills } : {}),
222
280
  stale: undefined,
223
281
  };
224
282
  }
@@ -278,6 +336,7 @@ export function normalizeSkillActiveState(raw: unknown): SkillActiveState | null
278
336
  session_id: safeString(state.session_id).trim() || primary?.session_id || undefined,
279
337
  thread_id: safeString(state.thread_id).trim() || primary?.thread_id || undefined,
280
338
  turn_id: safeString(state.turn_id).trim() || primary?.turn_id || undefined,
339
+ active_subskills: activeSkills.flatMap(entry => entry.active_subskills ?? []),
281
340
  active_skills: activeSkills.length > 0 ? activeSkills : [],
282
341
  };
283
342
  }
@@ -512,6 +571,7 @@ export async function readVisibleSkillActiveState(cwd: string, sessionId?: strin
512
571
  phase: primary?.phase ?? "",
513
572
  session_id: safeString(sessionId).trim() || primary?.session_id,
514
573
  active_skills: activeSkills,
574
+ active_subskills: activeSkills.flatMap(entry => entry.active_subskills ?? []),
515
575
  };
516
576
  }
517
577
 
@@ -552,7 +612,25 @@ async function rebuildActiveState(cwd: string, sessionScope?: ActiveSessionScope
552
612
  await rebuildActiveSnapshot(cwd, sessionScope, { cwd, audit: activeStateWriterAudit("rebuild-active-snapshot") });
553
613
  }
554
614
 
615
+ async function activeSubskillsForExistingEntry(
616
+ cwd: string,
617
+ sessionId: string | undefined,
618
+ skill: string,
619
+ ): Promise<ActiveSubskillEntry[] | undefined> {
620
+ const { rootPath, sessionPath } = getSkillActiveStatePaths(cwd, sessionId);
621
+ const [rootState, sessionState] = await Promise.all([
622
+ readRawActiveStateForHandoff(rootPath, false),
623
+ sessionPath ? readRawActiveStateForHandoff(sessionPath, false) : Promise.resolve(null),
624
+ ]);
625
+ const existing = mergeVisibleEntries(sessionState, rootState, sessionId).find(entry => entry.skill === skill);
626
+ return existing?.active_subskills;
627
+ }
628
+
555
629
  export async function syncSkillActiveState(options: SyncSkillActiveStateOptions): Promise<void> {
630
+ const preservedActiveSubskills =
631
+ options.active_subskills === undefined
632
+ ? await activeSubskillsForExistingEntry(options.cwd, options.sessionId, options.skill)
633
+ : undefined;
556
634
  const nowIso = options.nowIso ?? new Date().toISOString();
557
635
  const hud = normalizeWorkflowHudSummary(options.hud);
558
636
  const entry: SkillActiveEntry = {
@@ -569,6 +647,11 @@ export async function syncSkillActiveState(options: SyncSkillActiveStateOptions)
569
647
  ...(options.handoff_at ? { handoff_at: options.handoff_at } : {}),
570
648
  ...(hud ? { hud } : {}),
571
649
  ...(options.receipt ? { receipt: options.receipt } : {}),
650
+ ...(options.active_subskills !== undefined
651
+ ? { active_subskills: options.active_subskills }
652
+ : preservedActiveSubskills
653
+ ? { active_subskills: preservedActiveSubskills }
654
+ : {}),
572
655
  };
573
656
  await persistActiveEntry(options.cwd, undefined, entry);
574
657
  await rebuildActiveState(options.cwd);
@@ -636,9 +719,13 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
636
719
  ...(priorCaller.handoff_from && !callerEntry.handoff_from
637
720
  ? { handoff_from: priorCaller.handoff_from }
638
721
  : {}),
722
+ ...(priorCaller.active_subskills ? { active_subskills: priorCaller.active_subskills } : {}),
639
723
  }
640
724
  : callerEntry;
641
- return [...kept, mergedCaller, calleeEntry];
725
+ const activeSubskills = unionActiveSubskillEntries(priorCaller?.active_subskills, calleeEntry.active_subskills);
726
+ const mergedCallee: SkillActiveEntry =
727
+ activeSubskills.length > 0 ? { ...calleeEntry, active_subskills: activeSubskills } : calleeEntry;
728
+ return [...kept, mergedCaller, mergedCallee];
642
729
  };
643
730
  const writeEntries = async (
644
731
  sessionScope: ActiveSessionScope | undefined,
@@ -675,5 +762,6 @@ function buildSyncEntry(options: SyncSkillActiveStateOptions, nowIso: string): S
675
762
  ...(options.handoff_at ? { handoff_at: options.handoff_at } : {}),
676
763
  ...(hud ? { hud } : {}),
677
764
  ...(options.receipt ? { receipt: options.receipt } : {}),
765
+ ...(options.active_subskills ? { active_subskills: options.active_subskills } : {}),
678
766
  };
679
767
  }
@@ -15,6 +15,7 @@ import { logger } from "@gajae-code/utils";
15
15
  import { isProviderEnabled } from "../capability";
16
16
  import { findAllNearestProjectConfigDirs, getConfigDirs } from "../config";
17
17
  import { listClaudePluginRoots } from "../discovery/helpers";
18
+ import { rootContainsGjcManifest } from "../extensibility/gjc-plugins/paths";
18
19
  import { loadBundledAgents, parseAgent } from "./agents";
19
20
  import type { AgentDefinition, AgentSource } from "./types";
20
21
 
@@ -93,7 +94,12 @@ export async function discoverAgents(cwd: string, home: string = os.homedir()):
93
94
  const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
94
95
  ? await listClaudePluginRoots(home, resolvedCwd)
95
96
  : { roots: [] };
96
- const sortedPluginRoots = [...pluginRoots].sort((a, b) => {
97
+ const nonGjcPluginRoots = [];
98
+ for (const plugin of pluginRoots) {
99
+ if (await rootContainsGjcManifest(plugin.path)) continue;
100
+ nonGjcPluginRoots.push(plugin);
101
+ }
102
+ const sortedPluginRoots = nonGjcPluginRoots.sort((a, b) => {
97
103
  if (a.scope === b.scope) return 0;
98
104
  return a.scope === "project" ? -1 : 1;
99
105
  });
@@ -19,6 +19,7 @@ import { Settings } from "../config/settings";
19
19
  import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
20
20
  import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
21
21
  import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
22
+ import { buildAgentSubskillInjection } from "../extensibility/gjc-plugins";
22
23
  import { buildSkillPromptMessage, type Skill } from "../extensibility/skills";
23
24
  import type { HindsightSessionState } from "../hindsight/state";
24
25
  import type { LocalProtocolOptions } from "../internal-urls";
@@ -1159,6 +1160,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1159
1160
  ? `This subagent was started with a forked snapshot of the parent conversation. Included ${options.forkContextSeed.metadata.includedMessages} message(s), skipped ${options.forkContextSeed.metadata.skippedMessages}, approximately ${options.forkContextSeed.metadata.approximateTokens} tokens. The snapshot is not live; use IRC for live coordination when enabled.`
1160
1161
  : "";
1161
1162
 
1163
+ const agentSubskillBlock = await buildAgentSubskillInjection({
1164
+ cwd,
1165
+ sessionId: options.parentSessionId,
1166
+ agentName: agent.name,
1167
+ });
1168
+
1162
1169
  const { session } = await awaitAbortable(
1163
1170
  createAgentSession({
1164
1171
  cwd: worktree ?? cwd,
@@ -1185,15 +1192,22 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1185
1192
  ircSelfId: ircEnabled ? id : "",
1186
1193
  forkContext: forkContextNotice,
1187
1194
  });
1195
+ const promptWithSubskills = `${subagentPrompt}${agentSubskillBlock}`;
1188
1196
  return defaultPrompt.length === 0
1189
- ? [subagentPrompt]
1190
- : [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
1197
+ ? [promptWithSubskills]
1198
+ : [...defaultPrompt.slice(0, -1), promptWithSubskills, defaultPrompt[defaultPrompt.length - 1]];
1191
1199
  },
1192
1200
  sessionManager,
1193
1201
  hasUI: false,
1194
1202
  spawns: spawnsEnv,
1195
1203
  taskDepth: childDepth,
1196
1204
  currentAgentType: agent.name,
1205
+ gjcSubskillToolContext: {
1206
+ cwd,
1207
+ sessionId: options.parentSessionId,
1208
+ parent: agent.name,
1209
+ phase: "prompt",
1210
+ },
1197
1211
  parentHindsightSessionState: options.parentHindsightSessionState,
1198
1212
  parentTaskPrefix: id,
1199
1213
  agentId: id,
package/src/thinking.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core";
2
- import { clampThinkingLevelForModel, type Effort, type Model, THINKING_EFFORTS } from "@gajae-code/ai";
1
+ import { type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core/thinking";
2
+ import { clampThinkingLevelForModel, type Effort, THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
3
+ import type { Model } from "@gajae-code/ai/types";
3
4
 
4
5
  /**
5
6
  * Metadata used to render thinking selector values in the coding-agent UI.
@@ -34,6 +35,11 @@ const THINKING_LEVEL_METADATA: Record<ThinkingLevel, ThinkingLevelMetadata> = {
34
35
  label: "xhigh",
35
36
  description: "Maximum reasoning (~32k tokens)",
36
37
  },
38
+ [ThinkingLevel.Max]: {
39
+ value: ThinkingLevel.Max,
40
+ label: "max",
41
+ description: "Opus maximum reasoning",
42
+ },
37
43
  };
38
44
 
39
45
  const THINKING_LEVELS = new Set<string>([ThinkingLevel.Inherit, ThinkingLevel.Off, ...THINKING_EFFORTS]);
package/src/tools/ask.ts CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  renderDeepInterviewAskQuestion,
35
35
  } from "../deep-interview/render-middleware";
36
36
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
37
+ import { gateAnswerToResult, questionToGate } from "../modes/shared/agent-wire/deep-interview-gate";
37
38
  import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
38
39
  import askDescription from "../prompts/tools/ask.md" with { type: "text" };
39
40
  import { renderStatusLine } from "../tui";
@@ -425,7 +426,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
425
426
  }
426
427
 
427
428
  static createIf(session: ToolSession): AskTool | null {
428
- return session.hasUI ? new AskTool(session) : null;
429
+ return session.hasUI || session.getWorkflowGateEmitter?.() ? new AskTool(session) : null;
429
430
  }
430
431
 
431
432
  /** Send terminal notification when ask tool is waiting for input */
@@ -442,17 +443,25 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
442
443
  _onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
443
444
  context?: AgentToolContext,
444
445
  ): Promise<AgentToolResult<AskToolDetails>> {
445
- // Headless fallback
446
- if (!context?.hasUI || !context.ui) {
446
+ const gateEmitter = this.session.getWorkflowGateEmitter?.();
447
+ const canUseWorkflowGate = gateEmitter?.isUnattended() === true;
448
+
449
+ // Headless fallback: unattended workflow gates are the non-TUI answer path.
450
+ if (!canUseWorkflowGate && (!context?.hasUI || !context.ui)) {
447
451
  context?.abort();
448
452
  throw new ToolAbortError("Ask tool requires interactive mode");
449
453
  }
450
454
 
451
- const extensionUi = context.ui;
455
+ const extensionUi = context?.ui;
452
456
  const ui: UIContext = {
453
- select: (prompt, options, dialogOptions) => extensionUi.select(prompt, options, dialogOptions),
454
- editor: (title, prefill, dialogOptions, editorOptions) =>
455
- extensionUi.editor(title, prefill, dialogOptions, editorOptions),
457
+ select: (prompt, options, dialogOptions) => {
458
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
459
+ return extensionUi.select(prompt, options, dialogOptions);
460
+ },
461
+ editor: (title, prefill, dialogOptions, editorOptions) => {
462
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
463
+ return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
464
+ },
456
465
  };
457
466
 
458
467
  // Determine timeout based on settings and plan mode
@@ -477,6 +486,27 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
477
486
  options?: { previous?: QuestionResult; navigation?: NavigationControls },
478
487
  ) => {
479
488
  const rawOptionLabels = q.options.map(o => o.label);
489
+ // Unattended (#316/#323/G011): route the question through the workflow-gate
490
+ // emitter instead of the interactive UI; the external agent answers over RPC.
491
+ if (gateEmitter && canUseWorkflowGate) {
492
+ const gateQuestion = {
493
+ id: q.id,
494
+ question: q.question,
495
+ options: q.options,
496
+ multi: q.multi,
497
+ recommended: q.recommended,
498
+ };
499
+ const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
500
+ const decoded = gateAnswerToResult(gateQuestion, answer);
501
+ return {
502
+ optionLabels: rawOptionLabels,
503
+ selectedOptions: decoded.selectedOptions,
504
+ customInput: decoded.customInput,
505
+ navigation: undefined as NavigationControls | undefined,
506
+ cancelled: false,
507
+ timedOut: false,
508
+ };
509
+ }
480
510
  try {
481
511
  const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
482
512
  const displayQuestion = deepInterviewPrompt ?? q.question;
@@ -529,7 +559,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
529
559
  const { optionLabels, selectedOptions, customInput, cancelled, timedOut } = await askQuestion(q);
530
560
 
531
561
  if (!timedOut && (cancelled || (selectedOptions.length === 0 && customInput === undefined))) {
532
- context.abort();
562
+ context?.abort();
533
563
  throw new ToolAbortError("Ask tool was cancelled by the user");
534
564
  }
535
565
  const details: AskToolDetails = {
@@ -581,7 +611,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
581
611
  } = await askQuestion(q, { previous, navigation });
582
612
 
583
613
  if (cancelled && !timedOut) {
584
- context.abort();
614
+ context?.abort();
585
615
  throw new ToolAbortError("Ask tool was cancelled by the user");
586
616
  }
587
617
 
@@ -10,6 +10,7 @@ import type { GoalModeState, GoalRuntime } from "../goals";
10
10
  import { GoalTool } from "../goals/tools/goal-tool";
11
11
  import type { HindsightSessionState } from "../hindsight/state";
12
12
  import { LspTool } from "../lsp";
13
+ import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/unattended-session";
13
14
  import type { PlanModeState } from "../plan-mode/state";
14
15
  import type { AgentRegistry } from "../registry/agent-registry";
15
16
  import type { ForkContextSeed, ForkContextSeedOptions } from "../session/agent-session";
@@ -192,6 +193,8 @@ export interface ToolSession {
192
193
  getPlanModeState?: () => PlanModeState | undefined;
193
194
  /** Goal mode state (if active or paused) */
194
195
  getGoalModeState?: () => GoalModeState | undefined;
196
+ /** Unattended workflow-gate emitter (present only when unattended mode is negotiated). */
197
+ getWorkflowGateEmitter?: () => WorkflowGateEmitter | undefined;
195
198
  /** Goal runtime for the active agent session. */
196
199
  getGoalRuntime?: () => GoalRuntime | undefined;
197
200
  /** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
@@ -18,6 +18,7 @@
18
18
  import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
19
19
  import { prompt, untilAborted } from "@gajae-code/utils";
20
20
  import * as z from "zod/v4";
21
+ import { resolveSubskillActivationForSkillInvocation } from "../extensibility/gjc-plugins";
21
22
  import { buildSkillPromptMessage } from "../extensibility/skills";
22
23
  import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
23
24
  import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
@@ -125,7 +126,18 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
125
126
  }
126
127
 
127
128
  const args = (input.args ?? "").trim();
128
- const built = await buildSkillPromptMessage(skill, args);
129
+ const activationResult = await resolveSubskillActivationForSkillInvocation({
130
+ cwd: this.#session.cwd,
131
+ sessionId: this.#session.getSessionId?.() ?? activeState?.session_id?.trim() ?? undefined,
132
+ skillName: skill.name,
133
+ args,
134
+ });
135
+ const built = await buildSkillPromptMessage(skill, activationResult.cleanedArgs, {
136
+ subskillActivation: activationResult.activation,
137
+ subskillActivationSet: activationResult.activeSubskillsToPersist,
138
+ cwd: this.#session.cwd,
139
+ sessionId: this.#session.getSessionId?.() ?? activeState?.session_id?.trim() ?? undefined,
140
+ });
129
141
 
130
142
  await sendCustomMessage(
131
143
  {
@@ -141,7 +153,7 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
141
153
  const summary = JSON.stringify({
142
154
  callee: skill.name,
143
155
  path: skill.filePath,
144
- args: args || undefined,
156
+ args: activationResult.cleanedArgs || undefined,
145
157
  lineCount: built.details.lineCount,
146
158
  });
147
159
  return {
@@ -149,7 +161,7 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
149
161
  details: {
150
162
  name: skill.name,
151
163
  path: skill.filePath,
152
- args: args || undefined,
164
+ args: activationResult.cleanedArgs || undefined,
153
165
  lineCount: built.details.lineCount,
154
166
  },
155
167
  };
@@ -1,4 +1,4 @@
1
- import { $env } from "@gajae-code/utils";
1
+ import { $env } from "@gajae-code/utils/env";
2
2
 
3
3
  export type EditMode = "replace" | "patch" | "hashline" | "vim" | "apply_patch";
4
4