@gajae-code/coding-agent 0.8.2 → 0.9.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 (214) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/types/append-only-mode.d.ts +31 -0
  3. package/dist/types/capability/rule.d.ts +8 -0
  4. package/dist/types/cli/fixture-report.d.ts +20 -0
  5. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  6. package/dist/types/cli/nofile-limit.d.ts +11 -0
  7. package/dist/types/cli/update-cli.d.ts +22 -0
  8. package/dist/types/config/settings-schema.d.ts +119 -1
  9. package/dist/types/discovery/index.d.ts +1 -0
  10. package/dist/types/export/html/template.generated.d.ts +1 -1
  11. package/dist/types/export/ttsr.d.ts +14 -3
  12. package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
  13. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
  14. package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
  15. package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
  16. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  17. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
  19. package/dist/types/goals/runtime.d.ts +1 -0
  20. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  21. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  22. package/dist/types/modes/components/status-line.d.ts +9 -0
  23. package/dist/types/modes/components/thinking-selector.d.ts +2 -2
  24. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  25. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  26. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  27. package/dist/types/modes/interactive-mode.d.ts +4 -0
  28. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
  29. package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
  30. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  31. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  32. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  33. package/dist/types/modes/types.d.ts +3 -0
  34. package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
  35. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  36. package/dist/types/notifications/config-commands.d.ts +9 -0
  37. package/dist/types/notifications/config.d.ts +22 -0
  38. package/dist/types/notifications/index.d.ts +2 -0
  39. package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
  40. package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
  41. package/dist/types/notifications/reply-sent-store.d.ts +53 -0
  42. package/dist/types/notifications/rich-draft.d.ts +68 -0
  43. package/dist/types/notifications/rich-render.d.ts +90 -0
  44. package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
  45. package/dist/types/notifications/telegram-daemon.d.ts +30 -0
  46. package/dist/types/notifications/telegram-reference.d.ts +7 -0
  47. package/dist/types/notifications/threaded-render.d.ts +14 -0
  48. package/dist/types/sdk.d.ts +3 -0
  49. package/dist/types/session/agent-session.d.ts +43 -1
  50. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  51. package/dist/types/session/messages.d.ts +4 -0
  52. package/dist/types/session/session-manager.d.ts +17 -1
  53. package/dist/types/skill-state/active-state.d.ts +18 -1
  54. package/dist/types/system-prompt.d.ts +18 -2
  55. package/dist/types/task/executor.d.ts +1 -0
  56. package/dist/types/task/fork-context-budget.d.ts +7 -0
  57. package/dist/types/task/token-log.d.ts +30 -0
  58. package/dist/types/task/token-log.test.d.ts +1 -0
  59. package/dist/types/tools/bisect.d.ts +81 -0
  60. package/dist/types/tools/index.d.ts +1 -0
  61. package/dist/types/tools/search.d.ts +2 -2
  62. package/dist/types/utils/file-mentions.d.ts +14 -0
  63. package/dist/types/utils/git.d.ts +32 -0
  64. package/dist/types/utils/pasted-image-path.d.ts +30 -0
  65. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  66. package/package.json +8 -11
  67. package/src/append-only-mode.ts +58 -0
  68. package/src/autoresearch/prompt.md +1 -1
  69. package/src/autoresearch/tools/log-experiment.ts +1 -1
  70. package/src/capability/rule.ts +9 -0
  71. package/src/cli/config-cli.ts +27 -3
  72. package/src/cli/fixture-report.test.ts +230 -0
  73. package/src/cli/fixture-report.ts +171 -0
  74. package/src/cli/nofile-limit.ts +48 -0
  75. package/src/cli/notify-cli.ts +11 -9
  76. package/src/cli/update-cli.ts +85 -9
  77. package/src/cli.ts +26 -0
  78. package/src/commands/contribution-prep.ts +1 -1
  79. package/src/config/keybindings.ts +1 -1
  80. package/src/config/settings-schema.ts +76 -1
  81. package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
  82. package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
  83. package/src/defaults/gjc/skills/team/SKILL.md +16 -13
  84. package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
  85. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  86. package/src/defaults/gjc-defaults.ts +9 -0
  87. package/src/discovery/helpers.ts +10 -0
  88. package/src/discovery/index.ts +1 -0
  89. package/src/export/html/template.generated.ts +1 -1
  90. package/src/export/html/template.js +60 -2
  91. package/src/export/ttsr.ts +96 -18
  92. package/src/extensibility/extensions/runner.ts +181 -219
  93. package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
  94. package/src/gjc-runtime/deep-interview-state.ts +31 -2
  95. package/src/gjc-runtime/launch-tmux.ts +6 -0
  96. package/src/gjc-runtime/session-state-sidecar.ts +208 -13
  97. package/src/gjc-runtime/state-writer.ts +15 -0
  98. package/src/gjc-runtime/team-runtime.ts +175 -19
  99. package/src/gjc-runtime/tmux-common.ts +1 -0
  100. package/src/gjc-runtime/ultragoal-guard.ts +55 -66
  101. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
  102. package/src/gjc-runtime/ultragoal-runtime.ts +1602 -143
  103. package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
  104. package/src/gjc-runtime/workflow-manifest.ts +74 -1
  105. package/src/goals/runtime.ts +10 -2
  106. package/src/hindsight/backend.ts +1 -3
  107. package/src/hooks/native-skill-hook.ts +95 -4
  108. package/src/internal-urls/docs-index.generated.ts +18 -9
  109. package/src/lsp/client.ts +1 -0
  110. package/src/main.ts +43 -2
  111. package/src/memories/index.ts +12 -1
  112. package/src/modes/components/assistant-message.ts +241 -82
  113. package/src/modes/components/custom-editor.ts +1 -0
  114. package/src/modes/components/footer.ts +7 -2
  115. package/src/modes/components/status-line.ts +167 -54
  116. package/src/modes/components/thinking-selector.ts +10 -8
  117. package/src/modes/components/tool-execution.ts +44 -19
  118. package/src/modes/controllers/command-controller.ts +39 -2
  119. package/src/modes/controllers/event-controller.ts +77 -49
  120. package/src/modes/controllers/extension-ui-controller.ts +8 -1
  121. package/src/modes/controllers/input-controller.ts +13 -18
  122. package/src/modes/controllers/selector-controller.ts +41 -0
  123. package/src/modes/interactive-mode.ts +14 -1
  124. package/src/modes/rpc/rpc-mode.ts +32 -12
  125. package/src/modes/rpc/rpc-socket-security.ts +13 -1
  126. package/src/modes/rpc/rpc-types.ts +9 -0
  127. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  128. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  129. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  130. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  131. package/src/modes/types.ts +3 -0
  132. package/src/modes/utils/injected-user-submission.ts +94 -0
  133. package/src/modes/utils/ui-helpers.ts +17 -4
  134. package/src/notifications/config-commands.ts +20 -0
  135. package/src/notifications/config.ts +43 -4
  136. package/src/notifications/index.ts +105 -9
  137. package/src/notifications/lifecycle-commands.ts +37 -16
  138. package/src/notifications/lifecycle-control-runtime.ts +8 -3
  139. package/src/notifications/reply-sent-store.ts +134 -0
  140. package/src/notifications/rich-draft.ts +107 -0
  141. package/src/notifications/rich-render.ts +142 -0
  142. package/src/notifications/telegram-daemon-cli.ts +54 -7
  143. package/src/notifications/telegram-daemon-control.ts +2 -2
  144. package/src/notifications/telegram-daemon.ts +390 -56
  145. package/src/notifications/telegram-reference.ts +17 -0
  146. package/src/notifications/threaded-render.ts +45 -2
  147. package/src/prompts/agents/architect.md +19 -10
  148. package/src/prompts/agents/critic.md +16 -18
  149. package/src/prompts/agents/executor.md +14 -11
  150. package/src/prompts/agents/planner.md +18 -13
  151. package/src/prompts/ci-green-request.md +3 -8
  152. package/src/prompts/goals/goal-continuation.md +0 -5
  153. package/src/prompts/goals/goal-mode-active.md +0 -5
  154. package/src/prompts/system/agent-creation-architect.md +2 -2
  155. package/src/prompts/system/plan-mode-active.md +1 -1
  156. package/src/prompts/system/plan-mode-subagent.md +3 -3
  157. package/src/prompts/system/project-prompt.md +0 -13
  158. package/src/prompts/system/subagent-system-prompt.md +3 -2
  159. package/src/prompts/system/system-prompt.md +26 -19
  160. package/src/prompts/system/volatile-project-context.md +10 -0
  161. package/src/prompts/system/web-search.md +4 -7
  162. package/src/prompts/tools/apply-patch.md +6 -0
  163. package/src/prompts/tools/ast-edit.md +1 -0
  164. package/src/prompts/tools/bash.md +1 -1
  165. package/src/prompts/tools/bisect.md +22 -0
  166. package/src/prompts/tools/browser.md +1 -1
  167. package/src/prompts/tools/cron.md +0 -2
  168. package/src/prompts/tools/eval.md +2 -2
  169. package/src/prompts/tools/find.md +1 -1
  170. package/src/prompts/tools/github.md +6 -6
  171. package/src/prompts/tools/goal.md +1 -3
  172. package/src/prompts/tools/image-gen.md +12 -5
  173. package/src/prompts/tools/irc.md +5 -4
  174. package/src/prompts/tools/job.md +2 -2
  175. package/src/prompts/tools/lsp.md +1 -1
  176. package/src/prompts/tools/monitor.md +2 -2
  177. package/src/prompts/tools/patch.md +2 -2
  178. package/src/prompts/tools/replace.md +2 -21
  179. package/src/prompts/tools/rewind.md +1 -1
  180. package/src/prompts/tools/search-tool-bm25.md +6 -11
  181. package/src/prompts/tools/search.md +1 -1
  182. package/src/prompts/tools/skill.md +3 -2
  183. package/src/prompts/tools/ssh.md +2 -1
  184. package/src/prompts/tools/subagent.md +2 -2
  185. package/src/prompts/tools/task.md +4 -5
  186. package/src/prompts/tools/todo-write.md +1 -1
  187. package/src/prompts/tools/vim.md +1 -0
  188. package/src/prompts/tools/web-search.md +2 -0
  189. package/src/prompts/tools/write.md +8 -2
  190. package/src/sdk.ts +68 -30
  191. package/src/session/agent-session.ts +734 -107
  192. package/src/session/file-mention-pruning.ts +64 -0
  193. package/src/session/messages.ts +4 -0
  194. package/src/session/session-manager.ts +59 -2
  195. package/src/skill-state/active-state.ts +141 -5
  196. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  197. package/src/slash-commands/builtin-registry.ts +143 -1
  198. package/src/system-prompt.ts +57 -16
  199. package/src/task/executor.ts +125 -20
  200. package/src/task/fork-context-advisory.ts +2 -7
  201. package/src/task/fork-context-budget.ts +9 -0
  202. package/src/task/index.ts +4 -3
  203. package/src/task/output-manager.ts +4 -2
  204. package/src/task/token-log.test.ts +188 -0
  205. package/src/task/token-log.ts +179 -0
  206. package/src/tools/bisect.ts +367 -0
  207. package/src/tools/index.ts +3 -0
  208. package/src/tools/output-meta.ts +116 -4
  209. package/src/tools/search.ts +8 -2
  210. package/src/tools/skill.ts +15 -0
  211. package/src/utils/file-mentions.ts +39 -7
  212. package/src/utils/git.ts +73 -0
  213. package/src/utils/pasted-image-path.ts +170 -0
  214. package/src/web/scrapers/youtube.ts +24 -12
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
- import type { ThinkingLevel } from "@gajae-code/agent-core";
3
+ import { ThinkingLevel } from "@gajae-code/agent-core";
4
4
  import { type Model, modelsAreEqual } from "@gajae-code/ai";
5
5
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
6
6
  import { Spacer, Text } from "@gajae-code/tui";
@@ -32,6 +32,7 @@ import {
32
32
  parseProviderCompatibility,
33
33
  } from "../setup/provider-onboarding";
34
34
  import { parseThinkingLevel } from "../thinking";
35
+ import { getDisplayChangelogEntries } from "../utils/changelog";
35
36
  import { buildContextReportText } from "./helpers/context-report";
36
37
  import { buildFastStatusReport } from "./helpers/fast-status-report";
37
38
  import { formatDuration } from "./helpers/format";
@@ -378,12 +379,90 @@ function modelSelectionUsage(runtime: SlashCommandRuntime, currentModelLine?: st
378
379
  .join("\n\n");
379
380
  }
380
381
 
382
+ const EFFORT_COMMAND_INPUT_HINT = "[inherit|off|minimal|low|medium|high|xhigh|max]";
383
+ const EFFORT_COMMAND_ACCEPTED_VALUES = ["inherit", "off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
384
+
385
+ function effortCommandUsage(prefix?: string): string {
386
+ return [prefix, `Usage: /effort ${EFFORT_COMMAND_INPUT_HINT}`]
387
+ .filter((line): line is string => Boolean(line))
388
+ .join("\n");
389
+ }
390
+
391
+ function formatEffortStatus(runtime: SlashCommandRuntime): string {
392
+ const current = runtime.session.thinkingLevel ?? ThinkingLevel.Off;
393
+ const configuredDefault = runtime.settings.get("defaultThinkingLevel");
394
+ const supported = runtime.session.getAvailableThinkingLevels();
395
+ return [
396
+ `Current effective effort: ${current}`,
397
+ `Configured default effort: ${configuredDefault}`,
398
+ `Accepted values: ${EFFORT_COMMAND_ACCEPTED_VALUES.join(", ")}`,
399
+ `Current-model supported levels: ${supported.length > 0 ? supported.join(", ") : "(none reported)"}`,
400
+ ].join("\n");
401
+ }
402
+
403
+ async function handleEffortCommand(
404
+ command: ParsedSlashCommand,
405
+ runtime: SlashCommandRuntime,
406
+ ): Promise<SlashCommandResult> {
407
+ const tokens = command.args.trim().split(/\s+/).filter(Boolean);
408
+ if (tokens.length === 0) {
409
+ await runtime.output(formatEffortStatus(runtime));
410
+ return commandConsumed();
411
+ }
412
+ if (tokens.length !== 1) {
413
+ return usage(effortCommandUsage("Invalid effort input."), runtime);
414
+ }
415
+
416
+ const requestedToken = tokens[0];
417
+ const requestedLevel = parseThinkingLevel(requestedToken);
418
+ if (!requestedToken || !requestedLevel) {
419
+ return usage(effortCommandUsage(`Invalid effort: ${tokens[0] ?? ""}.`), runtime);
420
+ }
421
+
422
+ const levelToApply =
423
+ requestedLevel === ThinkingLevel.Inherit ? runtime.settings.get("defaultThinkingLevel") : requestedLevel;
424
+ runtime.session.setThinkingLevel(levelToApply, false);
425
+ const effectiveLevel = runtime.session.thinkingLevel ?? ThinkingLevel.Off;
426
+ const requestedLabel =
427
+ requestedLevel === ThinkingLevel.Inherit ? `${requestedLevel} (${levelToApply})` : requestedLevel;
428
+ const clampedSuffix =
429
+ effectiveLevel === levelToApply ? "" : ` Requested ${levelToApply}; effective ${effectiveLevel}.`;
430
+ await runtime.output(
431
+ `Reasoning effort set to ${requestedLabel}. Effective effort: ${effectiveLevel}.${clampedSuffix}`,
432
+ );
433
+ return commandConsumed();
434
+ }
435
+
381
436
  function refreshStatusLine(ctx: InteractiveModeContext): void {
382
437
  ctx.statusLine.invalidate();
383
438
  ctx.updateEditorTopBorder();
384
439
  ctx.ui.requestRender();
385
440
  }
386
441
 
442
+ type ChangelogCommandArgs = { showFull: boolean } | { error: string };
443
+
444
+ function parseChangelogCommandArgs(args: string): ChangelogCommandArgs {
445
+ const normalized = args.trim().toLowerCase();
446
+ if (!normalized) return { showFull: false };
447
+ if (normalized === "full" || normalized === "--full") return { showFull: true };
448
+ return { error: "Usage: /changelog [full|--full]" };
449
+ }
450
+
451
+ function buildChangelogCommandOutput(showFull: boolean): string {
452
+ const allEntries = getDisplayChangelogEntries();
453
+ const entriesToShow = showFull ? allEntries : allEntries.slice(0, 3);
454
+ const changelogMarkdown =
455
+ entriesToShow.length > 0
456
+ ? [...entriesToShow]
457
+ .reverse()
458
+ .map(entry => entry.content)
459
+ .join("\n\n")
460
+ : "No changelog entries found.";
461
+ const title = showFull ? "Full Changelog" : "Recent Changes";
462
+ const hint = showFull ? "" : "\n\nUse `/changelog --full` to view the complete changelog.";
463
+ return `${title}\n\n${changelogMarkdown}${hint}`;
464
+ }
465
+
387
466
  const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime): SlashCommandResult => {
388
467
  runtime.ctx.editor.setText("");
389
468
  void runtime.ctx.shutdown();
@@ -562,6 +641,29 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
562
641
  runtime.ctx.editor.setText("");
563
642
  },
564
643
  },
644
+ {
645
+ name: "effort",
646
+ description: "Show or set model reasoning effort",
647
+ acpDescription: "Show or set model reasoning effort",
648
+ inlineHint: EFFORT_COMMAND_INPUT_HINT,
649
+ acpInputHint: EFFORT_COMMAND_INPUT_HINT,
650
+ allowArgs: true,
651
+ handle: handleEffortCommand,
652
+ handleTui: async (command, runtime) => {
653
+ if (command.args.trim()) {
654
+ const result = await handleEffortCommand(command, toSlashCommandRuntime(runtime));
655
+ runtime.ctx.statusLine.invalidate();
656
+ runtime.ctx.updateEditorBorderColor();
657
+ runtime.ctx.updateEditorTopBorder();
658
+ runtime.ctx.editor.setText("");
659
+ runtime.ctx.ui.requestRender();
660
+ return result;
661
+ }
662
+
663
+ runtime.ctx.showEffortSelector();
664
+ runtime.ctx.editor.setText("");
665
+ },
666
+ },
565
667
  {
566
668
  name: "fast",
567
669
  description: "Toggle priority service tier (OpenAI service_tier=priority, Anthropic speed=fast)",
@@ -822,6 +924,29 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
822
924
  runtime.ctx.editor.setText("");
823
925
  },
824
926
  },
927
+ {
928
+ name: "changelog",
929
+ description: "Show release notes and changelog entries",
930
+ inlineHint: "[full|--full]",
931
+ subcommands: [{ name: "full", description: "Show complete changelog" }],
932
+ allowArgs: true,
933
+ handle: async (command, runtime) => {
934
+ const parsed = parseChangelogCommandArgs(command.args);
935
+ if ("error" in parsed) return usage(parsed.error, runtime);
936
+ await runtime.output(buildChangelogCommandOutput(parsed.showFull));
937
+ return commandConsumed();
938
+ },
939
+ handleTui: async (command, runtime) => {
940
+ const parsed = parseChangelogCommandArgs(command.args);
941
+ if ("error" in parsed) {
942
+ runtime.ctx.showError(parsed.error);
943
+ runtime.ctx.editor.setText("");
944
+ return;
945
+ }
946
+ await runtime.ctx.handleChangelogCommand(parsed.showFull);
947
+ runtime.ctx.editor.setText("");
948
+ },
949
+ },
825
950
  {
826
951
  name: "help",
827
952
  priority: 100,
@@ -1069,6 +1194,22 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1069
1194
  await runtime.ctx.handleSSHCommand(command.text);
1070
1195
  },
1071
1196
  },
1197
+ {
1198
+ name: "clear",
1199
+ priority: 97,
1200
+ description: "Clear context while preserving this session ID",
1201
+ acpDescription: "Clear context while preserving this session ID",
1202
+ handle: async (_command, runtime) => {
1203
+ const beforeSessionId = runtime.session.sessionId;
1204
+ await runtime.session.clearContext();
1205
+ await runtime.output(`Context cleared. Session preserved: ${beforeSessionId}`);
1206
+ return commandConsumed();
1207
+ },
1208
+ handleTui: async (_command, runtime) => {
1209
+ runtime.ctx.editor.setText("");
1210
+ await runtime.ctx.handleContextClearCommand();
1211
+ },
1212
+ },
1072
1213
  {
1073
1214
  name: "new",
1074
1215
  priority: 96,
@@ -1329,6 +1470,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1329
1470
  },
1330
1471
  {
1331
1472
  name: "exit",
1473
+ aliases: ["quit"],
1332
1474
  description: "Exit the application",
1333
1475
  handleTui: shutdownHandlerTui,
1334
1476
  },
@@ -14,6 +14,7 @@ import { loadSkills, type Skill } from "./extensibility/skills";
14
14
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
15
15
  import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
16
16
  import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
17
+ import volatileProjectContextTemplate from "./prompts/system/volatile-project-context.md" with { type: "text" };
17
18
  import { shortenPath } from "./tools/render-utils";
18
19
  import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
19
20
 
@@ -242,9 +243,12 @@ function dedupeExactContextFiles(
242
243
  }
243
244
 
244
245
  /**
245
- * Load all project context files using the capability API.
246
+ * Load all context files using the capability API.
246
247
  * Returns {path, content, depth} entries for all discovered context files.
247
- * Files are sorted by depth (descending) so files closer to cwd appear last/more prominent.
248
+ * Native user-global files (`~/.gjc/agent/AGENTS.md`) come first, then project
249
+ * files sorted by depth (descending) so files closer to cwd appear last/more
250
+ * prominent. User-home files from foreign providers (`~/.claude/CLAUDE.md`,
251
+ * `~/.codex/AGENTS.md`, …) stay excluded — only gjc's own user config applies.
248
252
  */
249
253
  export async function loadProjectContextFiles(
250
254
  options: LoadContextFilesOptions = {},
@@ -252,28 +256,32 @@ export async function loadProjectContextFiles(
252
256
  const resolvedCwd = options.cwd ?? getProjectDir();
253
257
 
254
258
  const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
259
+ const items = result.items as ContextFile[];
260
+
261
+ // Native user-global context applies everywhere and is least specific, so it
262
+ // renders first — project files rendered later take precedence over it.
263
+ const userFiles = items
264
+ .filter(item => item.level === "user" && item._source.provider === "native")
265
+ .map(item => ({ path: item.path, content: item.content }));
255
266
 
256
267
  // Convert project-level ContextFile items and preserve depth info
257
- const files = result.items
258
- .filter(item => (item as ContextFile).level === "project")
259
- .map(item => {
260
- const contextFile = item as ContextFile;
261
- return {
262
- path: contextFile.path,
263
- content: contextFile.content,
264
- depth: contextFile.depth,
265
- };
266
- });
268
+ const projectFiles = items
269
+ .filter(item => item.level === "project")
270
+ .map(item => ({
271
+ path: item.path,
272
+ content: item.content,
273
+ depth: item.depth,
274
+ }));
267
275
 
268
276
  // Sort by depth (descending): higher depth (farther from cwd) comes first,
269
277
  // so files closer to cwd appear later and are more prominent
270
- files.sort((a, b) => {
278
+ projectFiles.sort((a, b) => {
271
279
  const depthA = a.depth ?? -1;
272
280
  const depthB = b.depth ?? -1;
273
281
  return depthB - depthA;
274
282
  });
275
283
 
276
- return dedupeExactContextFiles(files);
284
+ return dedupeExactContextFiles([...userFiles, ...projectFiles]);
277
285
  }
278
286
 
279
287
  /**
@@ -368,6 +376,13 @@ export interface BuildSystemPromptOptions {
368
376
  secretsEnabled?: boolean;
369
377
  /** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
370
378
  workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
379
+ /**
380
+ * Render a trimmed role-agent base prompt for subagent sessions: omits the
381
+ * workflow-surface/routing/self-awareness (`<gjc-runtime>`) and `<soul>` blocks
382
+ * that only apply to the top-level interactive/print agent. Tool safety, repo
383
+ * safety, and the completion contract are retained. Default: false.
384
+ */
385
+ subagent?: boolean;
371
386
  }
372
387
 
373
388
  /** Result of building provider-facing system prompt messages. */
@@ -375,6 +390,29 @@ export interface BuildSystemPromptResult {
375
390
  /** Ordered system prompt blocks. Providers should preserve entries as distinct messages/blocks. */
376
391
  systemPrompt: string[];
377
392
  }
393
+ export interface BuildVolatileProjectContextOptions {
394
+ cwd?: string;
395
+ date?: string;
396
+ workspaceTree?: WorkspaceTree;
397
+ }
398
+
399
+ export function buildVolatileProjectContext(options: BuildVolatileProjectContextOptions = {}): string {
400
+ const resolvedCwd = options.cwd ?? getProjectDir();
401
+ const date = options.date ?? new Date().toISOString().slice(0, 10);
402
+ return prompt
403
+ .render(volatileProjectContextTemplate, {
404
+ date,
405
+ cwd: shortenPath(resolvedCwd.replace(/\\/g, "/")),
406
+ workspaceTree: options.workspaceTree ?? {
407
+ rootPath: resolvedCwd,
408
+ rendered: "",
409
+ truncated: false,
410
+ totalLines: 0,
411
+ agentsMdFiles: [],
412
+ },
413
+ })
414
+ .trim();
415
+ }
378
416
 
379
417
  /** Build the system prompt with tools, guidelines, and context */
380
418
  export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
@@ -403,6 +441,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
403
441
  eagerTasks = false,
404
442
  secretsEnabled = false,
405
443
  workspaceTree: providedWorkspaceTree,
444
+ subagent = false,
406
445
  } = options;
407
446
  const resolvedCwd = cwd ?? getProjectDir();
408
447
 
@@ -534,6 +573,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
534
573
  // Build tool descriptions for system prompt rendering.
535
574
  const toolPromptNames = new Map<string, string>(toolNames.map(name => [name, tools?.get(name)?.wireName ?? name]));
536
575
  const toolRefs = Object.fromEntries(toolPromptNames.entries());
576
+ const hasHiddenToolDiscoveryTool = Object.hasOwn(toolRefs, "search_tool_bm25");
537
577
  const toolInfo = toolNames.map(name => ({
538
578
  name: toolPromptNames.get(name) ?? name,
539
579
  internalName: name,
@@ -575,13 +615,14 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
575
615
  cwd: promptCwd,
576
616
  intentTracing: !!intentField,
577
617
  intentField: intentField ?? "",
578
- mcpDiscoveryMode,
618
+ mcpDiscoveryMode: mcpDiscoveryMode && hasHiddenToolDiscoveryTool,
579
619
  hasMCPDiscoveryServers: mcpDiscoveryServerSummaries.length > 0,
580
620
  mcpDiscoveryServerSummaries,
581
- toolDiscoveryActive,
621
+ toolDiscoveryActive: toolDiscoveryActive && hasHiddenToolDiscoveryTool,
582
622
  discoverableTools,
583
623
  eagerTasks,
584
624
  secretsEnabled,
625
+ subagent,
585
626
  };
586
627
  const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
587
628
  const systemPrompt = [rendered];
@@ -22,6 +22,7 @@ import { runExtensionCompact, runExtensionSetModel } from "../extensibility/exte
22
22
  import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
23
23
  import { buildAgentSubskillInjection, renderAgentPromptAdditions } from "../extensibility/gjc-plugins";
24
24
  import { buildSkillPromptMessage, type Skill } from "../extensibility/skills";
25
+ import { sessionRoot } from "../gjc-runtime/session-layout";
25
26
  import type { HindsightSessionState } from "../hindsight/state";
26
27
  import type { LocalProtocolOptions } from "../internal-urls";
27
28
  import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
@@ -42,6 +43,7 @@ import type { EventBus } from "../utils/event-bus";
42
43
  import { buildNamedToolChoiceResult } from "../utils/tool-choice";
43
44
  import type { WorkspaceTree } from "../workspace-tree";
44
45
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
46
+ import { persistTaskTokenLog, taskTokenLogFromUsage } from "./token-log";
45
47
  import {
46
48
  type AgentDefinition,
47
49
  type AgentProgress,
@@ -245,6 +247,51 @@ function previewOffendingData(value: unknown, maxLength = 500): string {
245
247
  }
246
248
  return serialized.length > maxLength ? `${serialized.slice(0, maxLength)}…` : serialized;
247
249
  }
250
+ const PLACEHOLDER_YIELD_PATTERNS = [
251
+ /^see (?:the )?message body(?:\b|[\s—:.,-])/i,
252
+ /^(?:complete\s+\w+\s+)?returned inline(?:\b|[\s—:.,-])/i,
253
+ /^leader persists(?:\b|[\s—:.,-])/i,
254
+ /^caller persists(?:\b|[\s—:.,-])/i,
255
+ ];
256
+
257
+ const PLACEHOLDER_YIELD_FIELD_NAMES = new Set([
258
+ "artifactmarkdown",
259
+ "finalmarkdown",
260
+ "fullplan",
261
+ "markdown",
262
+ "planmarkdown",
263
+ ]);
264
+
265
+ function looksLikePlaceholderYieldString(value: string): boolean {
266
+ const trimmed = value.trim();
267
+ if (trimmed.length === 0 || trimmed.length > 500) return false;
268
+ return PLACEHOLDER_YIELD_PATTERNS.some(pattern => pattern.test(trimmed));
269
+ }
270
+
271
+ function normalizePlaceholderYieldFieldName(key: string): string {
272
+ return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
273
+ }
274
+
275
+ function findPlaceholderYieldPath(value: unknown, path = "$", depth = 0, inspectStrings = true): string | undefined {
276
+ if (typeof value === "string") {
277
+ return inspectStrings && looksLikePlaceholderYieldString(value) ? path : undefined;
278
+ }
279
+ if (!value || typeof value !== "object" || depth > 4) return undefined;
280
+ if (Array.isArray(value)) {
281
+ for (let i = 0; i < value.length; i++) {
282
+ const found = findPlaceholderYieldPath(value[i], `${path}[${i}]`, depth + 1, false);
283
+ if (found) return found;
284
+ }
285
+ return undefined;
286
+ }
287
+ const record = value as Record<string, unknown>;
288
+ for (const [key, item] of Object.entries(record)) {
289
+ const shouldInspectString = PLACEHOLDER_YIELD_FIELD_NAMES.has(normalizePlaceholderYieldFieldName(key));
290
+ const found = findPlaceholderYieldPath(item, `${path}.${key}`, depth + 1, shouldInspectString);
291
+ if (found) return found;
292
+ }
293
+ return undefined;
294
+ }
248
295
 
249
296
  function tryParseJsonOutput(text: string): unknown | undefined {
250
297
  const trimmed = text.trim();
@@ -321,6 +368,8 @@ interface FinalizeSubprocessOutputResult {
321
368
  export const SUBAGENT_WARNING_NULL_YIELD = "SYSTEM WARNING: Subagent called yield with null data.";
322
369
  export const SUBAGENT_WARNING_MISSING_YIELD =
323
370
  "SYSTEM WARNING: Subagent exited without calling yield tool after 3 reminders.";
371
+ export const SUBAGENT_WARNING_PLACEHOLDER_YIELD =
372
+ "SYSTEM WARNING: Subagent yield data contains a placeholder instead of the actual result.";
324
373
 
325
374
  /** Build a schema_violation outcome — surfaced as a non-zero exit so callers treat it as a failure. */
326
375
  function buildSchemaViolationOutcome(
@@ -347,6 +396,21 @@ function buildSchemaViolationOutcome(
347
396
  return { rawOutput, stderr: headline, exitCode: 1 };
348
397
  }
349
398
 
399
+ function buildPlaceholderYieldOutcome(
400
+ placeholderPath: string,
401
+ data: unknown,
402
+ ): { rawOutput: string; stderr: string; exitCode: number } {
403
+ return buildSchemaViolationOutcome(
404
+ {
405
+ message:
406
+ `${SUBAGENT_WARNING_PLACEHOLDER_YIELD} Offending path: ${placeholderPath}. ` +
407
+ "Return the real payload in yield.result.data or persist a durable artifact receipt.",
408
+ missingRequired: [],
409
+ },
410
+ data,
411
+ );
412
+ }
413
+
350
414
  export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): FinalizeSubprocessOutputResult {
351
415
  let { rawOutput, exitCode, stderr } = args;
352
416
  const { yieldItems, reportFindings, doneAborted, signalAborted, outputSchema } = args;
@@ -376,21 +440,29 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
376
440
  stderr = `schema_violation: invalid output schema: ${schemaError}`;
377
441
  exitCode = 1;
378
442
  } else {
379
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
380
- if (!verdict.ok) {
381
- const outcome = buildSchemaViolationOutcome(verdict, completeData);
443
+ const placeholderPath = findPlaceholderYieldPath(completeData);
444
+ if (placeholderPath) {
445
+ const outcome = buildPlaceholderYieldOutcome(placeholderPath, completeData);
382
446
  rawOutput = outcome.rawOutput;
383
447
  stderr = outcome.stderr;
384
448
  exitCode = outcome.exitCode;
385
449
  } else {
386
- try {
387
- rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
388
- } catch (err) {
389
- const errorMessage = err instanceof Error ? err.message : String(err);
390
- rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
450
+ const verdict = validator ? validator.validate(completeData) : { ok: true as const };
451
+ if (!verdict.ok) {
452
+ const outcome = buildSchemaViolationOutcome(verdict, completeData);
453
+ rawOutput = outcome.rawOutput;
454
+ stderr = outcome.stderr;
455
+ exitCode = outcome.exitCode;
456
+ } else {
457
+ try {
458
+ rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
459
+ } catch (err) {
460
+ const errorMessage = err instanceof Error ? err.message : String(err);
461
+ rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
462
+ }
463
+ exitCode = 0;
464
+ stderr = "";
391
465
  }
392
- exitCode = 0;
393
- stderr = "";
394
466
  }
395
467
  }
396
468
  }
@@ -403,21 +475,29 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
403
475
  if (fallback) {
404
476
  const completeData = normalizeCompleteData(fallback.data, reportFindings);
405
477
  const { validator } = buildOutputValidator(outputSchema);
406
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
407
- if (!verdict.ok) {
408
- const outcome = buildSchemaViolationOutcome(verdict, completeData);
478
+ const placeholderPath = findPlaceholderYieldPath(completeData);
479
+ if (placeholderPath) {
480
+ const outcome = buildPlaceholderYieldOutcome(placeholderPath, completeData);
409
481
  rawOutput = outcome.rawOutput;
410
482
  stderr = outcome.stderr;
411
483
  exitCode = outcome.exitCode;
412
484
  } else {
413
- try {
414
- rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
415
- } catch (err) {
416
- const errorMessage = err instanceof Error ? err.message : String(err);
417
- rawOutput = `{"error":"Failed to serialize fallback completion: ${errorMessage}"}`;
485
+ const verdict = validator ? validator.validate(completeData) : { ok: true as const };
486
+ if (!verdict.ok) {
487
+ const outcome = buildSchemaViolationOutcome(verdict, completeData);
488
+ rawOutput = outcome.rawOutput;
489
+ stderr = outcome.stderr;
490
+ exitCode = outcome.exitCode;
491
+ } else {
492
+ try {
493
+ rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
494
+ } catch (err) {
495
+ const errorMessage = err instanceof Error ? err.message : String(err);
496
+ rawOutput = `{"error":"Failed to serialize fallback completion: ${errorMessage}"}`;
497
+ }
498
+ exitCode = 0;
499
+ stderr = "";
418
500
  }
419
- exitCode = 0;
420
- stderr = "";
421
501
  }
422
502
  } else if (!hasOutputSchema && allowFallback && rawOutput.trim().length > 0) {
423
503
  exitCode = 0;
@@ -1209,6 +1289,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1209
1289
  const subagentAgentIdentity: AgentIdentity | undefined = options.parentTelemetry
1210
1290
  ? { id, name: agent.name, description: agent.description }
1211
1291
  : undefined;
1292
+ let subagentTokenTurn = 0;
1293
+ const tokenLogDir = options.parentSessionId
1294
+ ? path.join(sessionRoot(cwd, options.parentSessionId), "token-logs")
1295
+ : undefined;
1212
1296
  const subagentTelemetry: AgentTelemetryConfig | undefined =
1213
1297
  options.parentTelemetry && subagentAgentIdentity
1214
1298
  ? {
@@ -1217,6 +1301,27 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1217
1301
  // Clear parent's conversationId; the child loop falls back to
1218
1302
  // its own AgentLoopConfig.sessionId.
1219
1303
  conversationId: undefined,
1304
+ // Intentionally REPLACES (does not chain) the parent's onChatUsage:
1305
+ // the parent handler attributes turns to subagentId "root", so
1306
+ // chaining it here would double-log every subagent turn under root.
1307
+ // Subagent turns are attributed to this child's id instead.
1308
+ onChatUsage: async event => {
1309
+ if (!tokenLogDir) return;
1310
+ subagentTokenTurn += 1;
1311
+ await persistTaskTokenLog(
1312
+ taskTokenLogFromUsage(event.usage, {
1313
+ subagentId: id,
1314
+ agent: agent.name,
1315
+ // Monotonic 1-based sequence per subagent session
1316
+ // (event.stepNumber is 0-based and -1 for oneshots).
1317
+ turn: subagentTokenTurn,
1318
+ at: new Date().toISOString(),
1319
+ model: event.model,
1320
+ cost: event.cost,
1321
+ }),
1322
+ { dir: tokenLogDir },
1323
+ );
1324
+ },
1220
1325
  }
1221
1326
  : undefined;
1222
1327
 
@@ -1,3 +1,4 @@
1
+ import { FORK_CONTEXT_TOKEN_BUDGET_BY_MODE } from "./fork-context-budget";
1
2
  import type { ForkContextMode } from "./types";
2
3
 
3
4
  export interface ForkContextAdvisory {
@@ -12,13 +13,7 @@ export interface ForkContextAdvisory {
12
13
  * many parent-context tokens each mode may clone into the child; the actual
13
14
  * cloned amount can never exceed the parent context itself.
14
15
  */
15
- const CLONE_BUDGET_BY_MODE = {
16
- none: 0,
17
- receipt: 2000,
18
- "last-turn": 4000,
19
- bounded: 8000,
20
- full: 15000,
21
- } as const satisfies Record<ForkContextMode, number>;
16
+ const CLONE_BUDGET_BY_MODE = FORK_CONTEXT_TOKEN_BUDGET_BY_MODE;
22
17
 
23
18
  const RECEIPT_TRIGGERS = [
24
19
  { pattern: /as discussed/i, reason: "prior-session-reference:as-discussed" },
@@ -0,0 +1,9 @@
1
+ import type { ForkContextMode } from "./types";
2
+
3
+ export const FORK_CONTEXT_TOKEN_BUDGET_BY_MODE = {
4
+ none: 0,
5
+ receipt: 2000,
6
+ "last-turn": 4000,
7
+ bounded: 8000,
8
+ full: 15000,
9
+ } as const satisfies Record<ForkContextMode, number>;
package/src/task/index.ts CHANGED
@@ -48,6 +48,7 @@ import * as git from "../utils/git";
48
48
  import { discoverAgents, filterVisibleAgents, getAgent } from "./discovery";
49
49
  import { runSubprocess } from "./executor";
50
50
  import { adviseForkContextMode } from "./fork-context-advisory";
51
+ import { FORK_CONTEXT_TOKEN_BUDGET_BY_MODE } from "./fork-context-budget";
51
52
  import { getTaskIdValidationError, validateAllocatedTaskId } from "./id";
52
53
  import { AgentOutputManager } from "./output-manager";
53
54
  import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
@@ -303,11 +304,11 @@ function resolveForkSeedParamsForMode(
303
304
  case "none":
304
305
  return undefined;
305
306
  case "receipt":
306
- return { maxMessages: 1, maxTokens: 64 };
307
+ return { maxMessages: 1, maxTokens: FORK_CONTEXT_TOKEN_BUDGET_BY_MODE.receipt };
307
308
  case "last-turn":
308
- return { maxMessages: 2, maxTokens: 250 };
309
+ return { maxMessages: 2, maxTokens: FORK_CONTEXT_TOKEN_BUDGET_BY_MODE["last-turn"] };
309
310
  case "bounded":
310
- return { maxMessages: capMessages(50), maxTokens: 250 };
311
+ return { maxMessages: capMessages(50), maxTokens: FORK_CONTEXT_TOKEN_BUDGET_BY_MODE.bounded };
311
312
  case "full":
312
313
  return { maxMessages: capMessages(500), maxTokens: resolveForkContextMaxTokens(configuredMaxTokens, model) };
313
314
  default:
@@ -14,6 +14,8 @@ function escapeRegExp(value: string): string {
14
14
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
15
  }
16
16
 
17
+ const RESERVED_OUTPUT_EXTENSIONS_PATTERN = "(?:md|jsonl|patch)";
18
+
17
19
  /**
18
20
  * Manages agent output ID allocation to ensure uniqueness.
19
21
  *
@@ -62,8 +64,8 @@ export class AgentOutputManager {
62
64
  }
63
65
 
64
66
  const pattern = this.#parentPrefix
65
- ? new RegExp(`^${escapeRegExp(this.#parentPrefix)}\\.(\\d+)-.*\\.md$`)
66
- : /^(\d+)-.*\.md$/;
67
+ ? new RegExp(`^${escapeRegExp(this.#parentPrefix)}\\.(\\d+)-.*\\.${RESERVED_OUTPUT_EXTENSIONS_PATTERN}$`)
68
+ : new RegExp(`^(\\d+)-.*\\.${RESERVED_OUTPUT_EXTENSIONS_PATTERN}$`);
67
69
 
68
70
  let maxId = -1;
69
71
  for (const file of files) {