@gajae-code/coding-agent 0.4.4 → 0.5.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 (132) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/types/cli/fast-help.d.ts +1 -0
  3. package/dist/types/cli/setup-cli.d.ts +2 -0
  4. package/dist/types/commands/harness.d.ts +6 -0
  5. package/dist/types/commands/setup.d.ts +6 -0
  6. package/dist/types/config/model-profile-activation.d.ts +11 -2
  7. package/dist/types/config/model-profiles.d.ts +7 -0
  8. package/dist/types/config/model-registry.d.ts +6 -0
  9. package/dist/types/config/model-resolver.d.ts +2 -0
  10. package/dist/types/config/models-config-schema.d.ts +35 -0
  11. package/dist/types/config/settings-schema.d.ts +4 -3
  12. package/dist/types/coordinator/contract.d.ts +1 -1
  13. package/dist/types/coordinator-mcp/server.d.ts +8 -2
  14. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
  15. package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
  16. package/dist/types/harness-control-plane/finalize.d.ts +5 -0
  17. package/dist/types/harness-control-plane/owner.d.ts +1 -1
  18. package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
  19. package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
  20. package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
  21. package/dist/types/harness-control-plane/receipts.d.ts +46 -0
  22. package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
  23. package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
  24. package/dist/types/harness-control-plane/types.d.ts +13 -1
  25. package/dist/types/hindsight/mental-models.d.ts +5 -5
  26. package/dist/types/main.d.ts +2 -2
  27. package/dist/types/modes/components/model-selector.d.ts +1 -12
  28. package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
  29. package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
  30. package/dist/types/modes/utils/abort-message.d.ts +4 -0
  31. package/dist/types/sdk.d.ts +5 -0
  32. package/dist/types/session/agent-session.d.ts +2 -0
  33. package/dist/types/session/blob-store.d.ts +20 -1
  34. package/dist/types/session/session-manager.d.ts +32 -6
  35. package/dist/types/session/streaming-output.d.ts +3 -2
  36. package/dist/types/session/tool-choice-queue.d.ts +6 -0
  37. package/dist/types/setup/hermes-setup.d.ts +7 -0
  38. package/dist/types/task/fork-context-advisory.d.ts +13 -0
  39. package/dist/types/task/receipt.d.ts +2 -0
  40. package/dist/types/task/roi-reconciliation.d.ts +27 -0
  41. package/dist/types/task/types.d.ts +17 -0
  42. package/dist/types/thinking-metadata.d.ts +16 -0
  43. package/dist/types/thinking.d.ts +3 -12
  44. package/dist/types/tools/index.d.ts +2 -0
  45. package/dist/types/tools/resolve.d.ts +0 -10
  46. package/dist/types/utils/tool-choice.d.ts +14 -1
  47. package/package.json +8 -7
  48. package/scripts/build-binary.ts +4 -0
  49. package/src/cli/fast-help.ts +80 -0
  50. package/src/cli/setup-cli.ts +12 -3
  51. package/src/cli.ts +112 -17
  52. package/src/commands/coordinator.ts +44 -1
  53. package/src/commands/harness.ts +128 -11
  54. package/src/commands/launch.ts +2 -2
  55. package/src/commands/mcp-serve.ts +3 -2
  56. package/src/commands/session.ts +3 -1
  57. package/src/commands/setup.ts +4 -0
  58. package/src/config/model-profile-activation.ts +15 -3
  59. package/src/config/model-profiles.ts +255 -56
  60. package/src/config/model-resolver.ts +9 -6
  61. package/src/config/models-config-schema.ts +2 -0
  62. package/src/config/settings-schema.ts +6 -3
  63. package/src/coordinator/contract.ts +1 -0
  64. package/src/coordinator-mcp/server.ts +427 -193
  65. package/src/cursor.ts +46 -4
  66. package/src/defaults/gjc/skills/team/SKILL.md +3 -2
  67. package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
  68. package/src/export/html/index.ts +13 -9
  69. package/src/gjc-runtime/launch-worktree.ts +12 -1
  70. package/src/gjc-runtime/session-state-sidecar.ts +38 -0
  71. package/src/gjc-runtime/team-runtime.ts +33 -7
  72. package/src/gjc-runtime/tmux-common.ts +15 -0
  73. package/src/gjc-runtime/tmux-sessions.ts +19 -11
  74. package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
  75. package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
  76. package/src/gjc-runtime/workflow-manifest.ts +16 -1
  77. package/src/harness-control-plane/finalize.ts +39 -5
  78. package/src/harness-control-plane/owner.ts +87 -28
  79. package/src/harness-control-plane/phase-rollup.ts +96 -0
  80. package/src/harness-control-plane/receipt-ingest.ts +127 -0
  81. package/src/harness-control-plane/receipt-spool.ts +128 -0
  82. package/src/harness-control-plane/receipts.ts +229 -1
  83. package/src/harness-control-plane/rpc-adapter.ts +8 -0
  84. package/src/harness-control-plane/state-machine.ts +27 -6
  85. package/src/harness-control-plane/storage.ts +23 -0
  86. package/src/harness-control-plane/types.ts +33 -1
  87. package/src/hindsight/mental-models.ts +17 -16
  88. package/src/internal-urls/docs-index.generated.ts +8 -7
  89. package/src/main.ts +7 -3
  90. package/src/modes/components/assistant-message.ts +26 -14
  91. package/src/modes/components/diff.ts +97 -0
  92. package/src/modes/components/model-selector.ts +353 -181
  93. package/src/modes/components/status-line.ts +6 -6
  94. package/src/modes/components/tool-execution.ts +30 -13
  95. package/src/modes/controllers/event-controller.ts +5 -4
  96. package/src/modes/controllers/selector-controller.ts +33 -42
  97. package/src/modes/interactive-mode.ts +4 -5
  98. package/src/modes/print-mode.ts +1 -1
  99. package/src/modes/rpc/rpc-client.ts +3 -2
  100. package/src/modes/rpc/rpc-mode.ts +44 -14
  101. package/src/modes/rpc/rpc-types.ts +5 -2
  102. package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
  103. package/src/modes/shared/agent-wire/command-validation.ts +11 -0
  104. package/src/modes/theme/theme.ts +2 -2
  105. package/src/modes/utils/abort-message.ts +41 -0
  106. package/src/modes/utils/context-usage.ts +15 -8
  107. package/src/modes/utils/ui-helpers.ts +5 -6
  108. package/src/sdk.ts +38 -6
  109. package/src/secrets/obfuscator.ts +102 -27
  110. package/src/session/agent-session.ts +121 -25
  111. package/src/session/blob-store.ts +89 -3
  112. package/src/session/session-manager.ts +328 -57
  113. package/src/session/streaming-output.ts +185 -122
  114. package/src/session/tool-choice-queue.ts +23 -0
  115. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -2
  116. package/src/setup/hermes-setup.ts +63 -8
  117. package/src/task/executor.ts +69 -6
  118. package/src/task/fork-context-advisory.ts +99 -0
  119. package/src/task/index.ts +31 -2
  120. package/src/task/receipt.ts +7 -0
  121. package/src/task/render.ts +21 -1
  122. package/src/task/roi-reconciliation.ts +90 -0
  123. package/src/task/types.ts +15 -0
  124. package/src/thinking-metadata.ts +51 -0
  125. package/src/thinking.ts +26 -46
  126. package/src/tools/bash.ts +1 -1
  127. package/src/tools/index.ts +4 -2
  128. package/src/tools/resolve.ts +93 -18
  129. package/src/tools/subagent-render.ts +10 -1
  130. package/src/utils/edit-mode.ts +1 -1
  131. package/src/utils/title-generator.ts +16 -2
  132. package/src/utils/tool-choice.ts +45 -16
package/src/main.ts CHANGED
@@ -33,7 +33,7 @@ import { getDefault, type SettingPath, Settings, settings } from "./config/setti
33
33
  import { initializeWithSettings } from "./discovery";
34
34
  import { exportFromFile } from "./export/html";
35
35
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
36
- import { InteractiveMode, runAcpMode, runBridgeMode, runPrintMode, runRpcMode } from "./modes";
36
+ import type { InteractiveMode } from "./modes/interactive-mode";
37
37
  import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
38
38
  import type { SubmittedUserInput } from "./modes/types";
39
39
  import type { MCPManager } from "./runtime-mcp";
@@ -304,6 +304,7 @@ async function runInteractiveMode(
304
304
  initialMessage?: string,
305
305
  initialImages?: ImageContent[],
306
306
  ): Promise<void> {
307
+ const { InteractiveMode } = await import("./modes/interactive-mode");
307
308
  const mode = new InteractiveMode(
308
309
  session,
309
310
  version,
@@ -706,7 +707,7 @@ async function buildSessionOptions(
706
707
  interface RunRootCommandDependencies {
707
708
  createAgentSession?: typeof createAgentSession;
708
709
  discoverAuthStorage?: typeof discoverAuthStorage;
709
- runAcpMode?: typeof runAcpMode;
710
+ runAcpMode?: (createSession: AcpSessionFactory) => Promise<void>;
710
711
  settings?: Settings;
711
712
  }
712
713
 
@@ -927,7 +928,7 @@ export async function runRootCommand(
927
928
  rawArgs,
928
929
  createSession,
929
930
  });
930
- await (deps.runAcpMode ?? runAcpMode)(createAcpSession);
931
+ await (deps.runAcpMode ?? (await import("./modes/acp")).runAcpMode)(createAcpSession);
931
932
  } else {
932
933
  const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager, eventBus } =
933
934
  await createSession(sessionOptions);
@@ -973,8 +974,10 @@ export async function runRootCommand(
973
974
  }
974
975
 
975
976
  if (mode === "rpc" || mode === "rpc-ui") {
977
+ const { runRpcMode } = await import("./modes/rpc/rpc-mode");
976
978
  await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
977
979
  } else if (mode === "bridge") {
980
+ const { runBridgeMode } = await import("./modes/bridge/bridge-mode");
978
981
  await runBridgeMode(session, setToolUIContext);
979
982
  } else if (isInteractive) {
980
983
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
@@ -1014,6 +1017,7 @@ export async function runRootCommand(
1014
1017
  initialImages,
1015
1018
  );
1016
1019
  } else {
1020
+ const { runPrintMode } = await import("./modes/print-mode");
1017
1021
  await runPrintMode(session, {
1018
1022
  mode,
1019
1023
  messages: parsedArgs.messages,
@@ -1,5 +1,5 @@
1
1
  import type { AssistantMessage, ImageContent, Usage } from "@gajae-code/ai";
2
- import { Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@gajae-code/tui";
2
+ import { type Component, Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@gajae-code/tui";
3
3
  import { formatNumber } from "@gajae-code/utils";
4
4
  import { settings } from "../../config/settings";
5
5
  import { renderDeepInterviewAssistantText } from "../../deep-interview/render-middleware";
@@ -18,6 +18,7 @@ export class AssistantMessageComponent extends Container {
18
18
  #convertedKittyImages = new Map<string, ImageContent>();
19
19
  #kittyConversionsInFlight = new Set<string>();
20
20
  #responseHeader = new Text(theme.bold(theme.fg("statusLineModel", "gajae")), 1, 0);
21
+ #contentBlocksCache = new WeakMap<object, { source: string; component: Component }>();
21
22
 
22
23
  constructor(
23
24
  message?: AssistantMessage,
@@ -106,6 +107,28 @@ export class AssistantMessageComponent extends Container {
106
107
  }
107
108
  }
108
109
 
110
+ #renderTextBlock(content: { text: string }): Component {
111
+ const cached = this.#contentBlocksCache.get(content);
112
+ if (cached?.source === content.text) return cached.component;
113
+ const trimmed = content.text.trim();
114
+ const component =
115
+ renderDeepInterviewAssistantText(trimmed, theme) ?? new Markdown(trimmed, 1, 0, getMarkdownTheme());
116
+ this.#contentBlocksCache.set(content, { source: content.text, component });
117
+ return component;
118
+ }
119
+
120
+ #renderThinkingBlock(content: { thinking: string }): Markdown {
121
+ const cached = this.#contentBlocksCache.get(content);
122
+ if (cached?.source === content.thinking) return cached.component as Markdown;
123
+ const trimmed = content.thinking.trim();
124
+ const component = new Markdown(trimmed, 1, 0, getMarkdownTheme(), {
125
+ color: (text: string) => theme.fg("thinkingText", text),
126
+ italic: true,
127
+ });
128
+ this.#contentBlocksCache.set(content, { source: content.thinking, component });
129
+ return component;
130
+ }
131
+
109
132
  #renderToolImages(): void {
110
133
  const imageEntries = Array.from(this.#toolImagesByCallId.entries()).flatMap(([toolCallId, images]) =>
111
134
  images.map((image, index) => ({ image, key: `${toolCallId}:${index}` })),
@@ -152,12 +175,7 @@ export class AssistantMessageComponent extends Container {
152
175
  for (let i = 0; i < message.content.length; i++) {
153
176
  const content = message.content[i];
154
177
  if (content.type === "text" && content.text.trim()) {
155
- // Assistant text messages with no background - trim the text
156
- // Set paddingY=0 to avoid extra spacing before tool executions
157
- const text = content.text.trim();
158
- this.#contentContainer.addChild(
159
- renderDeepInterviewAssistantText(text, theme) ?? new Markdown(text, 1, 0, getMarkdownTheme()),
160
- );
178
+ this.#contentContainer.addChild(this.#renderTextBlock(content));
161
179
  } else if (content.type === "thinking" && content.thinking.trim()) {
162
180
  // Add spacing only when another visible assistant content block follows.
163
181
  // This avoids a superfluous blank line before separately-rendered tool execution blocks.
@@ -172,13 +190,7 @@ export class AssistantMessageComponent extends Container {
172
190
  this.#contentContainer.addChild(new Spacer(1));
173
191
  }
174
192
  } else {
175
- // Thinking traces in thinkingText color, italic
176
- this.#contentContainer.addChild(
177
- new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
178
- color: (text: string) => theme.fg("thinkingText", text),
179
- italic: true,
180
- }),
181
- );
193
+ this.#contentContainer.addChild(this.#renderThinkingBlock(content));
182
194
  if (hasVisibleContentAfter) {
183
195
  this.#contentContainer.addChild(new Spacer(1));
184
196
  }
@@ -6,6 +6,9 @@ import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../../to
6
6
  /** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
7
7
  const DIM = "\x1b[2m";
8
8
  const DIM_OFF = "\x1b[22m";
9
+ // Single-span fast path is tuned for rendered code lines; ~500 chars covers typical terminal widths
10
+ // while avoiding duplicate prefix/suffix scans before diffWords on pathological long lines.
11
+ const LONG_LINE_FAST_PATH_LIMIT = 500;
9
12
 
10
13
  /**
11
14
  * Visualize leading whitespace (indentation) with dim glyphs.
@@ -53,6 +56,15 @@ function parseDiffLine(line: string): { prefix: CodeFrameMarker; lineNum: string
53
56
  * Strips leading whitespace from inverse to avoid highlighting indentation.
54
57
  */
55
58
  function renderIntraLineDiff(oldContent: string, newContent: string): { removedLine: string; addedLine: string } {
59
+ const fastPath = renderIntraLineDiffFastPath(oldContent, newContent);
60
+ if (fastPath) return fastPath;
61
+ return renderIntraLineDiffWithDiffWords(oldContent, newContent);
62
+ }
63
+
64
+ function renderIntraLineDiffWithDiffWords(
65
+ oldContent: string,
66
+ newContent: string,
67
+ ): { removedLine: string; addedLine: string } {
56
68
  const wordDiff = Diff.diffWords(oldContent, newContent);
57
69
 
58
70
  let removedLine = "";
@@ -94,6 +106,91 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
94
106
  return { removedLine, addedLine };
95
107
  }
96
108
 
109
+ function renderIntraLineDiffFastPath(
110
+ oldContent: string,
111
+ newContent: string,
112
+ ): { removedLine: string; addedLine: string } | null {
113
+ if (oldContent === newContent) return { removedLine: oldContent, addedLine: newContent };
114
+ if (Math.min(oldContent.length, newContent.length) > LONG_LINE_FAST_PATH_LIMIT) return null;
115
+
116
+ if (isWhitespaceOnlyChange(oldContent, newContent)) return null;
117
+ return renderSingleSpanIntraLineDiff(oldContent, newContent);
118
+ }
119
+
120
+ function isWhitespaceOnlyChange(oldContent: string, newContent: string): boolean {
121
+ return oldContent.replace(/\s+/g, "") === newContent.replace(/\s+/g, "");
122
+ }
123
+
124
+ function renderSingleSpanIntraLineDiff(
125
+ oldContent: string,
126
+ newContent: string,
127
+ ): { removedLine: string; addedLine: string } | null {
128
+ let prefixLength = 0;
129
+ const maxPrefixLength = Math.min(oldContent.length, newContent.length);
130
+ while (
131
+ prefixLength < maxPrefixLength &&
132
+ oldContent.charCodeAt(prefixLength) === newContent.charCodeAt(prefixLength)
133
+ ) {
134
+ prefixLength++;
135
+ if (prefixLength > LONG_LINE_FAST_PATH_LIMIT) return null;
136
+ }
137
+ let suffixLength = 0;
138
+ const maxSuffixLength = maxPrefixLength - prefixLength;
139
+ while (
140
+ suffixLength < maxSuffixLength &&
141
+ oldContent.charCodeAt(oldContent.length - 1 - suffixLength) ===
142
+ newContent.charCodeAt(newContent.length - 1 - suffixLength)
143
+ ) {
144
+ suffixLength++;
145
+ if (prefixLength + suffixLength > LONG_LINE_FAST_PATH_LIMIT) return null;
146
+ }
147
+
148
+ const oldMiddle = oldContent.slice(prefixLength, oldContent.length - suffixLength);
149
+ const newMiddle = newContent.slice(prefixLength, newContent.length - suffixLength);
150
+ if (oldMiddle.length === 0 || newMiddle.length === 0) return null;
151
+ if (!isSingleDiffWordsReplacement(oldContent, newContent, prefixLength, suffixLength)) return null;
152
+
153
+ const prefix = oldContent.slice(0, prefixLength);
154
+ const oldLeadingWs = oldMiddle.match(/^(\s*)/)?.[1] || "";
155
+ const newLeadingWs = newMiddle.match(/^(\s*)/)?.[1] || "";
156
+ return {
157
+ removedLine: `${prefix}${oldLeadingWs}${theme.inverse(oldMiddle.slice(oldLeadingWs.length))}${oldContent.slice(oldContent.length - suffixLength)}`,
158
+ addedLine: `${prefix}${newLeadingWs}${theme.inverse(newMiddle.slice(newLeadingWs.length))}${newContent.slice(newContent.length - suffixLength)}`,
159
+ };
160
+ }
161
+
162
+ function isSingleDiffWordsReplacement(
163
+ oldContent: string,
164
+ newContent: string,
165
+ prefixLength: number,
166
+ suffixLength: number,
167
+ ): boolean {
168
+ if (prefixLength === 0 && suffixLength === 0) return false;
169
+ const oldEnd = oldContent.length - suffixLength;
170
+ const newEnd = newContent.length - suffixLength;
171
+ const snappedPrefixLength = snapPrefixToWhitespaceBoundary(oldContent, newContent, prefixLength);
172
+ const snappedOldEnd = snapEndToWhitespaceBoundary(oldContent, oldEnd);
173
+ const snappedNewEnd = snapEndToWhitespaceBoundary(newContent, newEnd);
174
+ return snappedPrefixLength === prefixLength && snappedOldEnd === oldEnd && snappedNewEnd === newEnd;
175
+ }
176
+
177
+ function snapPrefixToWhitespaceBoundary(oldContent: string, newContent: string, prefixLength: number): number {
178
+ let snapped = prefixLength;
179
+ while (snapped > 0 && !(isWhitespaceBoundary(oldContent, snapped) && isWhitespaceBoundary(newContent, snapped)))
180
+ snapped--;
181
+ return snapped;
182
+ }
183
+
184
+ function snapEndToWhitespaceBoundary(content: string, end: number): number {
185
+ let snapped = end;
186
+ while (snapped < content.length && !isWhitespaceBoundary(content, snapped)) snapped++;
187
+ return snapped;
188
+ }
189
+
190
+ function isWhitespaceBoundary(content: string, index: number): boolean {
191
+ return index <= 0 || index >= content.length || /\s/.test(content[index - 1]!) || /\s/.test(content[index]!);
192
+ }
193
+
97
194
  export interface RenderDiffOptions {
98
195
  /** File path used to resolve indentation (.editorconfig + defaults) */
99
196
  filePath?: string;