@gajae-code/coding-agent 0.3.0 → 0.3.2

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 (213) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +7 -0
  4. package/dist/types/cli/args.d.ts +3 -1
  5. package/dist/types/commands/deep-interview.d.ts +3 -0
  6. package/dist/types/commands/launch.d.ts +6 -0
  7. package/dist/types/config/keybindings.d.ts +5 -0
  8. package/dist/types/config/model-profile-activation.d.ts +30 -0
  9. package/dist/types/config/model-profiles.d.ts +19 -0
  10. package/dist/types/config/model-registry.d.ts +8 -0
  11. package/dist/types/config/model-resolver.d.ts +1 -1
  12. package/dist/types/config/models-config-schema.d.ts +47 -0
  13. package/dist/types/config/settings-schema.d.ts +14 -4
  14. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  15. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  16. package/dist/types/deep-interview/render-middleware.d.ts +1 -0
  17. package/dist/types/eval/py/executor.d.ts +2 -0
  18. package/dist/types/eval/py/kernel.d.ts +2 -0
  19. package/dist/types/exec/bash-executor.d.ts +10 -0
  20. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  21. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  22. package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
  23. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  24. package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
  25. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -1
  26. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  27. package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
  28. package/dist/types/hooks/skill-state.d.ts +21 -0
  29. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  30. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  31. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  32. package/dist/types/internal-urls/types.d.ts +4 -0
  33. package/dist/types/lsp/index.d.ts +10 -10
  34. package/dist/types/main.d.ts +10 -1
  35. package/dist/types/modes/bridge/auth.d.ts +12 -0
  36. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  37. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  38. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  39. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  40. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  41. package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  43. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  44. package/dist/types/modes/components/model-selector.d.ts +6 -1
  45. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  46. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  47. package/dist/types/modes/components/status-line.d.ts +2 -0
  48. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  49. package/dist/types/modes/controllers/selector-controller.d.ts +9 -0
  50. package/dist/types/modes/index.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +1 -0
  52. package/dist/types/modes/jobs-observer.d.ts +57 -0
  53. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  54. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  55. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  56. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  57. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  58. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  59. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  60. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  61. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  62. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  63. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  64. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  65. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  66. package/dist/types/modes/types.d.ts +2 -0
  67. package/dist/types/sdk.d.ts +3 -1
  68. package/dist/types/session/agent-session.d.ts +11 -1
  69. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
  70. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  71. package/dist/types/task/executor.d.ts +1 -0
  72. package/dist/types/task/id.d.ts +7 -0
  73. package/dist/types/task/index.d.ts +5 -0
  74. package/dist/types/task/receipt.d.ts +85 -0
  75. package/dist/types/task/spawn-gate.d.ts +38 -0
  76. package/dist/types/task/types.d.ts +143 -11
  77. package/dist/types/tools/cron.d.ts +6 -0
  78. package/dist/types/tools/hindsight-recall.d.ts +0 -2
  79. package/dist/types/tools/hindsight-reflect.d.ts +0 -2
  80. package/dist/types/tools/hindsight-retain.d.ts +0 -2
  81. package/dist/types/tools/index.d.ts +6 -4
  82. package/dist/types/tools/path-utils.d.ts +1 -0
  83. package/dist/types/tools/subagent.d.ts +15 -0
  84. package/package.json +7 -7
  85. package/scripts/build-binary.ts +7 -0
  86. package/src/async/job-manager.ts +36 -0
  87. package/src/cli/args.ts +19 -2
  88. package/src/commands/deep-interview.ts +1 -0
  89. package/src/commands/harness.ts +289 -19
  90. package/src/commands/launch.ts +10 -2
  91. package/src/commands/state.ts +2 -1
  92. package/src/commands/team.ts +22 -4
  93. package/src/config/keybindings.ts +6 -0
  94. package/src/config/model-profile-activation.ts +157 -0
  95. package/src/config/model-profiles.ts +155 -0
  96. package/src/config/model-registry.ts +19 -0
  97. package/src/config/model-resolver.ts +3 -2
  98. package/src/config/models-config-schema.ts +36 -0
  99. package/src/config/settings-schema.ts +16 -3
  100. package/src/dap/client.ts +17 -3
  101. package/src/debug/crash-diagnostics.ts +223 -0
  102. package/src/debug/runtime-gauges.ts +20 -0
  103. package/src/deep-interview/render-middleware.ts +6 -0
  104. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  105. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  106. package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -3
  107. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
  108. package/src/defaults/gjc-defaults.ts +7 -0
  109. package/src/eval/py/executor.ts +21 -1
  110. package/src/eval/py/kernel.ts +15 -0
  111. package/src/exec/bash-executor.ts +41 -0
  112. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  113. package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
  114. package/src/gjc-runtime/ralplan-runtime.ts +213 -36
  115. package/src/gjc-runtime/state-migrations.ts +54 -7
  116. package/src/gjc-runtime/state-runtime.ts +461 -64
  117. package/src/gjc-runtime/state-schema.ts +192 -0
  118. package/src/gjc-runtime/state-writer.ts +32 -1
  119. package/src/gjc-runtime/team-runtime.ts +177 -105
  120. package/src/gjc-runtime/ultragoal-runtime.ts +231 -38
  121. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  122. package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
  123. package/src/gjc-runtime/workflow-manifest.ts +3 -1
  124. package/src/harness-control-plane/control-endpoint.ts +19 -8
  125. package/src/harness-control-plane/owner.ts +57 -10
  126. package/src/harness-control-plane/state-machine.ts +2 -1
  127. package/src/hooks/skill-state.ts +176 -26
  128. package/src/internal-urls/agent-protocol.ts +68 -21
  129. package/src/internal-urls/artifact-protocol.ts +12 -17
  130. package/src/internal-urls/docs-index.generated.ts +8 -10
  131. package/src/internal-urls/registry-helpers.ts +19 -16
  132. package/src/internal-urls/types.ts +4 -0
  133. package/src/lsp/client.ts +18 -2
  134. package/src/main.ts +88 -6
  135. package/src/modes/bridge/auth.ts +41 -0
  136. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  137. package/src/modes/bridge/bridge-mode.ts +520 -0
  138. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  139. package/src/modes/bridge/event-stream.ts +70 -0
  140. package/src/modes/components/custom-editor.ts +101 -0
  141. package/src/modes/components/custom-provider-wizard.ts +318 -0
  142. package/src/modes/components/hook-selector.ts +61 -18
  143. package/src/modes/components/jobs-overlay-model.ts +109 -0
  144. package/src/modes/components/jobs-overlay.ts +172 -0
  145. package/src/modes/components/model-selector.ts +108 -18
  146. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  147. package/src/modes/components/status-line/presets.ts +7 -5
  148. package/src/modes/components/status-line/segments.ts +25 -0
  149. package/src/modes/components/status-line/types.ts +2 -0
  150. package/src/modes/components/status-line.ts +9 -1
  151. package/src/modes/controllers/extension-ui-controller.ts +39 -3
  152. package/src/modes/controllers/input-controller.ts +97 -9
  153. package/src/modes/controllers/selector-controller.ts +86 -1
  154. package/src/modes/index.ts +1 -0
  155. package/src/modes/interactive-mode.ts +27 -0
  156. package/src/modes/jobs-observer.ts +204 -0
  157. package/src/modes/rpc/host-tools.ts +1 -186
  158. package/src/modes/rpc/host-uris.ts +1 -235
  159. package/src/modes/rpc/rpc-client.ts +25 -10
  160. package/src/modes/rpc/rpc-mode.ts +12 -381
  161. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  162. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  163. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  164. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  165. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  166. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  167. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  168. package/src/modes/shared/agent-wire/responses.ts +17 -0
  169. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  170. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  171. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  172. package/src/modes/types.ts +2 -0
  173. package/src/prompts/memories/consolidation.md +1 -1
  174. package/src/prompts/memories/read-path.md +6 -7
  175. package/src/prompts/memories/unavailable.md +2 -2
  176. package/src/prompts/tools/bash.md +1 -1
  177. package/src/prompts/tools/irc.md +1 -1
  178. package/src/prompts/tools/read.md +2 -2
  179. package/src/prompts/tools/recall.md +1 -0
  180. package/src/prompts/tools/reflect.md +1 -0
  181. package/src/prompts/tools/retain.md +1 -0
  182. package/src/prompts/tools/subagent.md +12 -7
  183. package/src/prompts/tools/task-summary.md +3 -9
  184. package/src/prompts/tools/task.md +5 -1
  185. package/src/sdk.ts +5 -1
  186. package/src/session/agent-session.ts +214 -38
  187. package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
  188. package/src/skill-state/workflow-state-contract.ts +7 -4
  189. package/src/skill-state/workflow-state-version.ts +3 -0
  190. package/src/slash-commands/builtin-registry.ts +9 -1
  191. package/src/task/executor.ts +31 -5
  192. package/src/task/id.ts +33 -0
  193. package/src/task/index.ts +259 -67
  194. package/src/task/output-manager.ts +5 -4
  195. package/src/task/receipt.ts +297 -0
  196. package/src/task/render.ts +48 -131
  197. package/src/task/spawn-gate.ts +132 -0
  198. package/src/task/types.ts +48 -7
  199. package/src/tools/ask.ts +73 -33
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash.ts +1 -1
  203. package/src/tools/cron.ts +48 -0
  204. package/src/tools/find.ts +4 -1
  205. package/src/tools/hindsight-recall.ts +0 -2
  206. package/src/tools/hindsight-reflect.ts +0 -2
  207. package/src/tools/hindsight-retain.ts +0 -2
  208. package/src/tools/index.ts +6 -18
  209. package/src/tools/path-utils.ts +3 -2
  210. package/src/tools/read.ts +4 -3
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/skill.ts +6 -1
  213. package/src/tools/subagent.ts +237 -84
@@ -1,25 +1,28 @@
1
1
  /**
2
- * Shared helpers for internal-url protocol handlers that resolve IDs against
3
- * registered agent sessions.
2
+ * Shared helpers for internal-url protocol handlers that resolve session-scoped
3
+ * artifact IDs.
4
4
  */
5
- import { AgentRegistry } from "../registry/agent-registry";
5
+ import * as path from "node:path";
6
+ import type { ResolveContext } from "./types";
7
+
8
+ function addDir(dirs: string[], dir: string | null | undefined): void {
9
+ if (!dir) return;
10
+ const normalized = path.resolve(dir);
11
+ if (!dirs.includes(normalized)) dirs.push(normalized);
12
+ }
6
13
 
7
14
  /**
8
- * Snapshot of artifacts dirs for every registered session, deduped.
15
+ * Snapshot of artifacts dirs explicitly authorized for the calling session.
9
16
  *
10
- * Prefers `sessionManager.getArtifactsDir()` because subagents adopt their
11
- * parent's `ArtifactManager` and report the parent's dir there; dedup then
12
- * collapses parent + N subagents (the whole agent tree) to one entry. Falls
13
- * back to the raw session file (with the `.jsonl` suffix stripped) when no
14
- * live session reference is attached.
17
+ * Normal reads are scoped to the caller's artifacts directory. Parent/child
18
+ * agent tree sharing is allowed only when the caller supplies explicit
19
+ * authorized directories at the ResolveContext boundary. This intentionally
20
+ * does not enumerate AgentRegistry.global(); live but unrelated sessions are
21
+ * not an authorization source.
15
22
  */
16
- export function artifactsDirsFromRegistry(): string[] {
23
+ export function authorizedArtifactsDirsFromContext(context?: ResolveContext): string[] {
17
24
  const dirs: string[] = [];
18
- for (const ref of AgentRegistry.global().list()) {
19
- const dir =
20
- ref.session?.sessionManager.getArtifactsDir() ?? (ref.sessionFile ? ref.sessionFile.slice(0, -6) : null);
21
- if (!dir) continue;
22
- if (!dirs.includes(dir)) dirs.push(dir);
23
- }
25
+ addDir(dirs, context?.getArtifactsDir?.());
26
+ for (const dir of context?.getAuthorizedArtifactsDirs?.() ?? []) addDir(dirs, dir);
24
27
  return dirs;
25
28
  }
@@ -59,6 +59,10 @@ export interface ResolveContext {
59
59
  cwd?: string;
60
60
  /** Settings of the calling session (used by `issue://`/`pr://` for cache TTLs). */
61
61
  settings?: unknown;
62
+ /** Artifacts directory of the calling session. */
63
+ getArtifactsDir?: () => string | null;
64
+ /** Additional artifacts directories explicitly authorized for this caller (for parent/child agent trees). */
65
+ getAuthorizedArtifactsDirs?: () => readonly string[];
62
66
  /** Caller's abort signal. */
63
67
  signal?: AbortSignal;
64
68
  }
package/src/lsp/client.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isEnoent, logger, ptree, untilAborted } from "@gajae-code/utils";
2
+ import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
2
3
  import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
3
4
  import { applyWorkspaceEdit } from "./edits";
4
5
  import { getLspmuxCommand, isLspmuxSupported } from "./lspmux";
@@ -486,7 +487,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
486
487
  clients.set(key, client);
487
488
 
488
489
  // Register crash recovery - remove client on process exit
489
- proc.exited.then(() => {
490
+ proc.exited.then(async () => {
490
491
  clients.delete(key);
491
492
  clientLocks.delete(key);
492
493
  client.resolveProjectLoaded();
@@ -501,9 +502,24 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
501
502
  .filter(line => !/^\[\d{2}:\d{2}:\d{2} (?:INF|DBG|VRB)\]/.test(line))
502
503
  .join("\n")
503
504
  .trim();
505
+ const crashNotice = formatCrashDiagnosticNotice(
506
+ await writeCrashReport(
507
+ {
508
+ kind: "lsp",
509
+ command: [command, ...args],
510
+ exitCode: proc.exitCode,
511
+ stderr,
512
+ protocol: "lsp",
513
+ },
514
+ { cwd },
515
+ ),
516
+ );
517
+ const diagnosticSuffix = crashNotice ? `\n${crashNotice}` : "";
504
518
  const code = proc.exitCode;
505
519
  const err = new Error(
506
- stderr ? `LSP server exited (code ${code}): ${stderr}` : `LSP server exited unexpectedly (code ${code})`,
520
+ stderr
521
+ ? `LSP server exited (code ${code}): ${stderr}${diagnosticSuffix}`
522
+ : `LSP server exited unexpectedly (code ${code})${diagnosticSuffix}`,
507
523
  );
508
524
  for (const pending of client.pendingRequests.values()) {
509
525
  pending.reject(err);
package/src/main.ts CHANGED
@@ -26,13 +26,14 @@ import { buildInitialMessage } from "./cli/initial-message";
26
26
  import { runListModelsCommand } from "./cli/list-models";
27
27
  import { selectSession } from "./cli/session-picker";
28
28
  import { findConfigFile } from "./config";
29
+ import { activateModelProfile } from "./config/model-profile-activation";
29
30
  import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
30
31
  import { resolveCliModel, resolveModelRoleValue, resolveModelScope, type ScopedModel } from "./config/model-resolver";
31
32
  import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
32
33
  import { initializeWithSettings } from "./discovery";
33
34
  import { exportFromFile } from "./export/html";
34
35
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
35
- import { InteractiveMode, runAcpMode, runPrintMode, runRpcMode } from "./modes";
36
+ import { InteractiveMode, runAcpMode, runBridgeMode, runPrintMode, runRpcMode } from "./modes";
36
37
  import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
37
38
  import type { SubmittedUserInput } from "./modes/types";
38
39
  import type { MCPManager } from "./runtime-mcp";
@@ -194,11 +195,59 @@ export interface AcpSessionFactoryOptions {
194
195
  sessionDir?: string;
195
196
  authStorage: AuthStorage;
196
197
  modelRegistry: ModelRegistry;
197
- parsedArgs: Pick<Args, "apiKey">;
198
+ parsedArgs: Pick<Args, "apiKey" | "default" | "model" | "mpreset" | "thinking">;
198
199
  rawArgs: string[];
199
200
  createSession: (options: CreateAgentSessionOptions) => Promise<CreateAgentSessionResult>;
200
201
  }
201
202
 
203
+ export async function applyStartupModelProfiles(args: {
204
+ session: AgentSession;
205
+ settings: Settings;
206
+ modelRegistry: ModelRegistry;
207
+ parsedArgs: Pick<Args, "default" | "model" | "mpreset" | "thinking">;
208
+ startupModel?: CreateAgentSessionOptions["model"];
209
+ startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
210
+ }): Promise<void> {
211
+ const applyProfile = async (profileName: string, persistDefault: boolean): Promise<void> => {
212
+ await activateModelProfile(
213
+ { session: args.session, modelRegistry: args.modelRegistry, settings: args.settings, profileName },
214
+ { persistDefault },
215
+ );
216
+ };
217
+
218
+ // Capture the explicitly-selected startup model BEFORE profile activation can
219
+ // override it. startupModel covers the eager path; session.model covers the
220
+ // deferred `--model <pattern>` path resolved inside createAgentSession.
221
+ const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
222
+
223
+ const defaultProfile = args.settings.get("modelProfile.default");
224
+ if (defaultProfile) {
225
+ await applyProfile(defaultProfile, false);
226
+ }
227
+ if (args.parsedArgs.mpreset) {
228
+ await applyProfile(args.parsedArgs.mpreset, args.parsedArgs.default === true);
229
+ }
230
+
231
+ // Explicit CLI --model/--thinking must win over any activated profile.
232
+ if (explicitModel) {
233
+ await args.session.setModelTemporary(explicitModel, args.startupThinkingLevel ?? args.parsedArgs.thinking);
234
+ } else if (args.parsedArgs.thinking && args.session.model) {
235
+ await args.session.setModelTemporary(args.session.model, args.parsedArgs.thinking);
236
+ }
237
+ }
238
+
239
+ export async function applyStartupModelProfilesOrExit(
240
+ args: Parameters<typeof applyStartupModelProfiles>[0],
241
+ ): Promise<void> {
242
+ try {
243
+ await applyStartupModelProfiles(args);
244
+ } catch (error) {
245
+ const message = error instanceof Error ? error.message : String(error);
246
+ process.stderr.write(`${chalk.red(`Error: ${message}`)}\n`);
247
+ process.exit(1);
248
+ }
249
+ }
250
+
202
251
  /**
203
252
  * Build the per-`session/new` factory used by ACP mode.
204
253
  *
@@ -225,6 +274,14 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
225
274
  hasUI: false,
226
275
  enableMCP: false,
227
276
  });
277
+ await applyStartupModelProfilesOrExit({
278
+ session: nextSession,
279
+ settings: nextSettings,
280
+ modelRegistry: args.modelRegistry,
281
+ parsedArgs: args.parsedArgs,
282
+ startupModel: args.baseOptions.model,
283
+ startupThinkingLevel: args.baseOptions.thinkingLevel,
284
+ });
228
285
  if (args.parsedArgs.apiKey && !args.baseOptions.model && nextSession.model) {
229
286
  args.authStorage.setRuntimeApiKey(nextSession.model.provider, args.parsedArgs.apiKey);
230
287
  }
@@ -707,21 +764,35 @@ export async function runRootCommand(
707
764
  process.exit(0);
708
765
  }
709
766
 
710
- if ((parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui") && parsedArgs.fileArgs.length > 0) {
711
- process.stderr.write(`${chalk.red("Error: @file arguments are not supported in RPC mode")}\n`);
767
+ if (
768
+ (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "bridge") &&
769
+ parsedArgs.fileArgs.length > 0
770
+ ) {
771
+ process.stderr.write(`${chalk.red("Error: @file arguments are not supported in RPC or bridge mode")}\n`);
712
772
  process.exit(1);
713
773
  }
714
774
 
715
775
  const cwd = getProjectDir();
716
776
  const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd }));
717
- if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "acp") {
777
+ if (
778
+ parsedArgs.mode === "rpc" ||
779
+ parsedArgs.mode === "rpc-ui" ||
780
+ parsedArgs.mode === "acp" ||
781
+ parsedArgs.mode === "bridge"
782
+ ) {
718
783
  applyRpcDefaultSettingOverrides(settingsInstance);
719
784
  }
720
785
  modelRegistry.applyConfiguredModelBindings(settingsInstance);
721
786
  if (parsedArgs.noPty || parsedArgs.mode === "rpc-ui") {
722
787
  Bun.env.PI_NO_PTY = "1";
723
788
  }
724
- if (parsedArgs.noTitle || parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "acp") {
789
+ if (
790
+ parsedArgs.noTitle ||
791
+ parsedArgs.mode === "rpc" ||
792
+ parsedArgs.mode === "rpc-ui" ||
793
+ parsedArgs.mode === "acp" ||
794
+ parsedArgs.mode === "bridge"
795
+ ) {
725
796
  Bun.env.PI_NO_TITLE = "1";
726
797
  }
727
798
  const { pipedInput, fileText, fileImages } = await logger.time("prepareInitialMessage", async () => {
@@ -864,6 +935,15 @@ export async function runRootCommand(
864
935
  authStorage.setRuntimeApiKey(session.model.provider, parsedArgs.apiKey);
865
936
  }
866
937
 
938
+ await applyStartupModelProfilesOrExit({
939
+ session,
940
+ settings: settingsInstance,
941
+ modelRegistry,
942
+ parsedArgs,
943
+ startupModel: sessionOptions.model,
944
+ startupThinkingLevel: sessionOptions.thinkingLevel,
945
+ });
946
+
867
947
  if (modelFallbackMessage) {
868
948
  notifs.push({ kind: "warn", message: modelFallbackMessage });
869
949
  }
@@ -894,6 +974,8 @@ export async function runRootCommand(
894
974
 
895
975
  if (mode === "rpc" || mode === "rpc-ui") {
896
976
  await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
977
+ } else if (mode === "bridge") {
978
+ await runBridgeMode(session, setToolUIContext);
897
979
  } else if (isInteractive) {
898
980
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
899
981
  const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs);
@@ -0,0 +1,41 @@
1
+ import * as crypto from "node:crypto";
2
+
3
+ export interface BridgeAuthConfig {
4
+ token: string;
5
+ }
6
+
7
+ export interface BridgeBindConfig {
8
+ hostname: string;
9
+ port: number;
10
+ tlsConfigured: boolean;
11
+ }
12
+
13
+ export function extractBearerToken(authorization: string | null | undefined): string | undefined {
14
+ if (!authorization) return undefined;
15
+ const trimmed = authorization.trim();
16
+ const match = /^Bearer\s+(.+)$/i.exec(trimmed);
17
+ const token = match?.[1]?.trim();
18
+ return token ? token : undefined;
19
+ }
20
+
21
+ export function isBridgeTokenAuthorized(authorization: string | null | undefined, config: BridgeAuthConfig): boolean {
22
+ const candidate = extractBearerToken(authorization);
23
+ if (candidate === undefined) return false;
24
+ const candidateBytes = Buffer.from(candidate, "utf8");
25
+ const expectedBytes = Buffer.from(config.token, "utf8");
26
+ return candidateBytes.length === expectedBytes.length && crypto.timingSafeEqual(candidateBytes, expectedBytes);
27
+ }
28
+
29
+ export function isLoopbackHost(hostname: string): boolean {
30
+ const normalized = hostname.trim().toLowerCase();
31
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
32
+ }
33
+
34
+ export function assertSafeBridgeBind(config: BridgeBindConfig): void {
35
+ if (!config.tlsConfigured) {
36
+ throw new Error(
37
+ `Refusing to start bridge on ${config.hostname}:${config.port} without TLS configured. ` +
38
+ "Set GJC_BRIDGE_TLS_CERT and GJC_BRIDGE_TLS_KEY.",
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,47 @@
1
+ import type {
2
+ ClientBridge,
3
+ ClientBridgePermissionOption,
4
+ ClientBridgePermissionOutcome,
5
+ ClientBridgePermissionToolCall,
6
+ } from "../../session/client-bridge";
7
+ import type { UiRequestBroker, UiRequestCancelled, UiRequestResolution } from "../shared/agent-wire/ui-request-broker";
8
+
9
+ export interface BridgePermissionRequestPayload {
10
+ kind: "permission";
11
+ toolCall: ClientBridgePermissionToolCall;
12
+ options: ClientBridgePermissionOption[];
13
+ }
14
+
15
+ export type BridgePermissionBroker = UiRequestBroker<BridgePermissionRequestPayload, ClientBridgePermissionOutcome>;
16
+ function isUiRequestCancelled(
17
+ resolution: UiRequestResolution<ClientBridgePermissionOutcome>,
18
+ ): resolution is UiRequestCancelled {
19
+ return "status" in resolution && resolution.status === "cancelled";
20
+ }
21
+
22
+ function toPermissionOutcome(
23
+ resolution: UiRequestResolution<ClientBridgePermissionOutcome>,
24
+ ): ClientBridgePermissionOutcome {
25
+ if (isUiRequestCancelled(resolution)) {
26
+ return { outcome: "cancelled" };
27
+ }
28
+ return resolution;
29
+ }
30
+
31
+ export function createBridgeClientBridge(broker: BridgePermissionBroker): ClientBridge {
32
+ return {
33
+ capabilities: { requestPermission: true },
34
+ deferAgentInitiatedTurns: true,
35
+ async requestPermission(toolCall, options, signal) {
36
+ return toPermissionOutcome(
37
+ await broker.request(
38
+ { kind: "permission", toolCall, options },
39
+ {
40
+ correlationId: toolCall.toolCallId,
41
+ signal,
42
+ },
43
+ ),
44
+ );
45
+ },
46
+ };
47
+ }