@gajae-code/coding-agent 0.13.1 → 0.13.3

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 (45) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/types/config/model-registry.d.ts +1 -0
  3. package/dist/types/config/settings-schema.d.ts +3 -3
  4. package/dist/types/main.d.ts +1 -0
  5. package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
  6. package/dist/types/modes/components/gajae-pet-widget.d.ts +5 -5
  7. package/dist/types/modes/components/pet-capability.d.ts +5 -5
  8. package/dist/types/modes/components/settings-selector.d.ts +5 -0
  9. package/dist/types/modes/theme/defaults/index.d.ts +118 -0
  10. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  11. package/dist/types/session/internal/managed-session-storage.d.ts +13 -0
  12. package/dist/types/session/session-manager.d.ts +7 -0
  13. package/dist/types/tools/image-gen.d.ts +1 -0
  14. package/package.json +8 -8
  15. package/src/config/model-profile-activation.ts +3 -1
  16. package/src/config/model-profiles.ts +59 -2
  17. package/src/config/model-registry.ts +16 -1
  18. package/src/config/settings-schema.ts +1 -1
  19. package/src/coordinator-mcp/server.ts +3 -0
  20. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
  21. package/src/internal-urls/docs-index.generated.ts +7 -6
  22. package/src/main.ts +4 -0
  23. package/src/modes/components/chrome.ts +4 -3
  24. package/src/modes/components/dynamic-theme-text.ts +22 -0
  25. package/src/modes/components/gajae-pet-widget.ts +130 -42
  26. package/src/modes/components/model-selector.ts +1 -6
  27. package/src/modes/components/pet-capability.ts +10 -5
  28. package/src/modes/components/settings-selector.ts +20 -5
  29. package/src/modes/components/status-line/segments.ts +3 -3
  30. package/src/modes/controllers/selector-controller.ts +61 -5
  31. package/src/modes/interactive-mode.ts +4 -0
  32. package/src/modes/theme/defaults/index.ts +2 -0
  33. package/src/modes/theme/defaults/ouroboros.json +118 -0
  34. package/src/sdk/broker/process-incarnation.ts +6 -1
  35. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  36. package/src/sdk/bus/telegram-daemon.ts +51 -43
  37. package/src/session/agent-session.ts +39 -0
  38. package/src/session/internal/managed-session-storage.ts +162 -40
  39. package/src/session/session-manager.ts +28 -0
  40. package/src/slash-commands/builtin-registry.ts +62 -8
  41. package/src/task/index.ts +5 -2
  42. package/src/tools/bash.ts +11 -9
  43. package/src/tools/image-gen.ts +322 -126
  44. package/src/tools/todo-contract.ts +55 -3
  45. package/src/tools/todo-write.ts +4 -3
@@ -14774,6 +14774,20 @@ export class SessionManager {
14774
14774
  getSessionFile(): string | undefined {
14775
14775
  return this.#sessionFile;
14776
14776
  }
14777
+ /**
14778
+ * On-disk transcript file size in bytes. Returns 0 when the file is
14779
+ * unavailable, unreadable, or no session file is set. The managed-storage
14780
+ * path reads through the descriptor (no full-file scan); the plain-file
14781
+ * path uses statSync.
14782
+ */
14783
+ getTranscriptFileBytes(): number {
14784
+ if (!this.#sessionFile) return 0;
14785
+ try {
14786
+ return this.#statSync(this.#sessionFile).size;
14787
+ } catch {
14788
+ return 0;
14789
+ }
14790
+ }
14777
14791
 
14778
14792
  getSessionMemoryStats(): SessionMemoryStats {
14779
14793
  const runtime = this.#sidecarRuntime;
@@ -15783,6 +15797,20 @@ export class SessionManager {
15783
15797
  if (publishResumeBreadcrumb && persisted) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
15784
15798
  if (persisted) this.#readOnlyResume = false;
15785
15799
  } catch (err) {
15800
+ // content_too_large on the managed append hot path means the append-only
15801
+ // transcript file has reached the per-file storage limit (64 MiB). The
15802
+ // on-disk file is append-only and grew past the limit even though the
15803
+ // in-memory entry list may be much smaller (compaction evicts old
15804
+ // content but the append-only file never shrinks until a full rewrite).
15805
+ // Fall back to a full rewrite (replaceSync) which writes only the live
15806
+ // in-memory entries, shrinking the file below the limit. The entry has
15807
+ // already been added to #fileEntries by #appendEntryWithinPersistenceFence.
15808
+ if (err instanceof Error && err.message === "content_too_large") {
15809
+ this.#rewriteFileSync();
15810
+ if (publishResumeBreadcrumb) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
15811
+ this.#readOnlyResume = false;
15812
+ return;
15813
+ }
15786
15814
  this.#recordPersistError(err);
15787
15815
  throw this.#persistError ?? toError(err);
15788
15816
  }
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { ThinkingLevel } from "@gajae-code/agent-core";
4
4
  import { type Model, modelsAreEqual } from "@gajae-code/ai/core";
5
5
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
6
- import { PET_SKINS, type PetMode, Spacer, Text } from "@gajae-code/tui";
6
+ import { PET_SKIN_IDS, PET_SKINS, type PetMode, Spacer, Text } from "@gajae-code/tui";
7
7
  import { setProjectDir } from "@gajae-code/utils";
8
8
  import { jobElapsedMs } from "../async";
9
9
  import { activateModelProfile, materializeActiveModelProfileAssignments } from "../config/model-profile-activation";
@@ -12,6 +12,7 @@ import {
12
12
  GJC_MODEL_ASSIGNMENT_TARGET_IDS,
13
13
  GJC_MODEL_ASSIGNMENT_TARGETS,
14
14
  type GjcModelAssignmentTargetId,
15
+ requiresExplicitThinkingChoice,
15
16
  } from "../config/model-registry";
16
17
 
17
18
  import {
@@ -23,7 +24,7 @@ import {
23
24
  } from "../config/model-resolver";
24
25
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../discovery/helpers.js";
25
26
  import { DynamicBorder } from "../modes/components/dynamic-border";
26
- import { theme } from "../modes/theme/theme";
27
+ import { getAvailableThemes, getDetectedThemeSettingsPath, setTheme, theme } from "../modes/theme/theme";
27
28
  import {
28
29
  type ComposerSubmissionOptions,
29
30
  canApplyComposerSubmission,
@@ -70,8 +71,11 @@ function canClearComposer(runtime: BuiltinSlashCommandRuntime): boolean {
70
71
 
71
72
  const PET_COMMAND_OPTIONS: ReadonlyArray<{ name: string; mode: PetMode; description: string }> = [
72
73
  { name: "off", mode: "off", description: "Hide the pet" },
73
- { name: "RedGajae", mode: "red", description: PET_SKINS.red.description },
74
- { name: "BlueGajae", mode: "blue", description: PET_SKINS.blue.description },
74
+ ...PET_SKIN_IDS.map(id => ({
75
+ name: PET_SKINS[id].label,
76
+ mode: id,
77
+ description: PET_SKINS[id].description,
78
+ })),
75
79
  ];
76
80
  const PET_COMMAND_HINT = `[${PET_COMMAND_OPTIONS.map(option => option.name).join("|")}]`;
77
81
  /**
@@ -709,10 +713,48 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
709
713
  },
710
714
  {
711
715
  name: "theme",
712
- description: "Open theme selector",
713
- handleTui: (_command, runtime) => {
714
- runtime.ctx.showThemeSelector();
715
- runtime.ctx.editor.setText("");
716
+ description: "Change theme immediately, or open the theme selector without args",
717
+ inlineHint: "[theme]",
718
+ allowArgs: true,
719
+ handleTui: async (command, runtime) => {
720
+ const ctx = runtime.ctx;
721
+ const name = command.args?.trim();
722
+ if (!name) {
723
+ ctx.showThemeSelector();
724
+ ctx.editor.setText("");
725
+ return;
726
+ }
727
+ const available = await getAvailableThemes();
728
+ if (!available.includes(name)) {
729
+ ctx.showError(`Unknown theme "${name}". Available themes: ${available.join(", ")}`);
730
+ ctx.editor.setText("");
731
+ return;
732
+ }
733
+ if (!ctx.settings.canWriteDurableConfig()) {
734
+ ctx.showError(
735
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
736
+ );
737
+ ctx.editor.setText("");
738
+ return;
739
+ }
740
+ try {
741
+ ctx.settings.set(getDetectedThemeSettingsPath(), name);
742
+ } catch (error) {
743
+ ctx.showError(error instanceof Error ? error.message : String(error));
744
+ ctx.editor.setText("");
745
+ return;
746
+ }
747
+ const result = await setTheme(name, true, { shouldApply: () => !ctx.isStopped?.() });
748
+ if (ctx.isStopped?.()) return;
749
+ ctx.statusLine.invalidate();
750
+ ctx.updateEditorTopBorder();
751
+ ctx.ui.invalidate();
752
+ if (result.success) {
753
+ ctx.showStatus(`Theme changed to ${name}`);
754
+ } else {
755
+ ctx.showError(`Failed to load theme "${name}": ${result.error}\nFell back to dark theme.`);
756
+ }
757
+ ctx.editor.setText("");
716
758
  },
717
759
  },
718
760
  {
@@ -832,6 +874,18 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
832
874
  return usage(modelSelectionUsage(runtime, resolution.failure.message), runtime);
833
875
  }
834
876
  const { selection } = resolution;
877
+ if (
878
+ selection.thinkingLevel === undefined &&
879
+ targetIds.some(targetId => requiresExplicitThinkingChoice(selection.model, targetId))
880
+ ) {
881
+ return usage(
882
+ modelSelectionUsage(
883
+ runtime,
884
+ `Model ${selection.model.provider}/${selection.model.id} requires an explicit effort suffix.`,
885
+ ),
886
+ runtime,
887
+ );
888
+ }
835
889
  try {
836
890
  const includesDefault = targetIds.includes("default");
837
891
  const includesRoleAgent = targetIds.some(role => role !== "default");
package/src/task/index.ts CHANGED
@@ -1900,6 +1900,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1900
1900
  file => path.basename(file.path).toLowerCase() !== "agents.md",
1901
1901
  );
1902
1902
  const promptTemplates = this.session.promptTemplates;
1903
+ const parentMcpManager = this.session.getMcpManager?.();
1904
+ // Exact-config tools-only managers belong to the top-level ACP session and cannot be reused by sub-sessions.
1905
+ const reusableParentMcpManager = parentMcpManager?.isToolsOnly() ? undefined : parentMcpManager;
1903
1906
 
1904
1907
  // Initialize progress for all tasks
1905
1908
  for (let i = 0; i < tasksWithUniqueIds.length; i++) {
@@ -2036,7 +2039,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2036
2039
  parentArtifactManager,
2037
2040
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
2038
2041
  parentTelemetry: this.session.getTelemetry?.(),
2039
- parentMcpManager: this.session.getMcpManager?.(),
2042
+ parentMcpManager: reusableParentMcpManager,
2040
2043
  forkContextSeed,
2041
2044
  });
2042
2045
  return {
@@ -2113,7 +2116,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2113
2116
  parentArtifactManager,
2114
2117
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
2115
2118
  parentTelemetry: this.session.getTelemetry?.(),
2116
- parentMcpManager: this.session.getMcpManager?.(),
2119
+ parentMcpManager: reusableParentMcpManager,
2117
2120
  forkContextSeed,
2118
2121
  });
2119
2122
  let capturedResult = {
package/src/tools/bash.ts CHANGED
@@ -862,26 +862,28 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
862
862
  return { kind: "aborted" };
863
863
  }
864
864
 
865
+ const threshold = Promise.withResolvers<{ kind: "running" }>();
866
+ const thresholdTimer = setTimeout(() => threshold.resolve({ kind: "running" }), Math.max(0, thresholdMs));
865
867
  const waiters: Array<Promise<ManagedBashJobCompletion | { kind: "running" } | { kind: "aborted" }>> = [
866
868
  job.completion,
867
- Bun.sleep(thresholdMs).then(() => ({ kind: "running" as const })),
869
+ threshold.promise,
868
870
  ];
869
871
  if (backgroundRequest) {
870
872
  waiters.push(backgroundRequest.then(() => ({ kind: "running" as const })));
871
873
  }
872
874
 
873
- if (!signal) {
874
- return await Promise.race(waiters);
875
+ let onAbort: (() => void) | undefined;
876
+ if (signal) {
877
+ const aborted = Promise.withResolvers<{ kind: "aborted" }>();
878
+ onAbort = () => aborted.resolve({ kind: "aborted" });
879
+ signal.addEventListener("abort", onAbort, { once: true });
880
+ waiters.push(aborted.promise);
875
881
  }
876
-
877
- const { promise: abortedPromise, resolve: resolveAborted } = Promise.withResolvers<{ kind: "aborted" }>();
878
- const onAbort = () => resolveAborted({ kind: "aborted" });
879
- signal.addEventListener("abort", onAbort, { once: true });
880
- waiters.push(abortedPromise);
881
882
  try {
882
883
  return await Promise.race(waiters);
883
884
  } finally {
884
- signal.removeEventListener("abort", onAbort);
885
+ clearTimeout(thresholdTimer);
886
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
885
887
  }
886
888
  }
887
889