@gajae-code/coding-agent 0.17.1 → 0.17.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 (53) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/types/async/job-manager.d.ts +7 -6
  3. package/dist/types/config/autorouting-tier-map.d.ts +6 -0
  4. package/dist/types/config/file-lock.d.ts +1 -0
  5. package/dist/types/config/model-profile-activation.d.ts +18 -3
  6. package/dist/types/config/model-registry.d.ts +9 -0
  7. package/dist/types/coordinator-mcp/server.d.ts +3 -0
  8. package/dist/types/modes/acp/acp-agent.d.ts +40 -1
  9. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  10. package/dist/types/sdk/acp/adapter.d.ts +27 -0
  11. package/dist/types/sdk/acp/final-text.d.ts +8 -0
  12. package/dist/types/sdk/broker/ensure.d.ts +46 -1
  13. package/dist/types/sdk/prompt-deadline-manager.d.ts +1 -1
  14. package/dist/types/sdk/prompt-failure.d.ts +30 -1
  15. package/dist/types/sdk/prompt-status.d.ts +8 -0
  16. package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
  17. package/dist/types/sdk/transport/serve-cli.d.ts +27 -0
  18. package/dist/types/thinking.d.ts +3 -1
  19. package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
  20. package/package.json +7 -7
  21. package/src/async/job-manager.ts +38 -8
  22. package/src/commands/sdk.ts +78 -4
  23. package/src/config/autorouting-tier-map.ts +2 -0
  24. package/src/config/file-lock.ts +52 -17
  25. package/src/config/model-profile-activation.ts +72 -10
  26. package/src/config/model-registry.ts +21 -0
  27. package/src/coordinator-mcp/server.ts +21 -6
  28. package/src/debug/report-bundle.ts +114 -24
  29. package/src/gjc-runtime/managed-owner-admission.ts +67 -50
  30. package/src/internal-urls/docs-index.generated.ts +7 -6
  31. package/src/modes/acp/acp-agent.ts +187 -14
  32. package/src/modes/acp/acp-event-mapper.ts +9 -0
  33. package/src/modes/components/model-selector.ts +31 -15
  34. package/src/modes/components/status-line/segments.ts +6 -1
  35. package/src/modes/components/thinking-selector.ts +7 -3
  36. package/src/modes/controllers/selector-controller.ts +16 -3
  37. package/src/sdk/acp/adapter.ts +63 -1
  38. package/src/sdk/acp/final-text.ts +19 -2
  39. package/src/sdk/broker/ensure.ts +97 -19
  40. package/src/sdk/bus/index.ts +31 -4
  41. package/src/sdk/host/session-runtime.test.ts +498 -7
  42. package/src/sdk/host/session-runtime.ts +188 -21
  43. package/src/sdk/prompt-deadline-manager.ts +3 -3
  44. package/src/sdk/prompt-failure.ts +43 -1
  45. package/src/sdk/prompt-status.ts +8 -0
  46. package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
  47. package/src/sdk/router/session-router.ts +22 -3
  48. package/src/sdk/session.ts +12 -9
  49. package/src/sdk/transport/serve-cli.ts +105 -15
  50. package/src/session-import/codex.ts +12 -3
  51. package/src/thinking.ts +21 -2
  52. package/src/utils/mupdf-wasm-embedded.ts +3 -0
  53. package/src/utils/mupdf-wasm.ts +24 -2
@@ -1,6 +1,7 @@
1
1
  import { getAgentDir } from "@gajae-code/utils";
2
2
  import { CliParseError } from "@gajae-code/utils/cli";
3
- import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../client";
3
+ import type { BrokerDiscovery } from "../broker/discovery";
4
+ import { readSdkBrokerDiscovery, SdkClient, SdkClientError, SdkDiscoveryError } from "../client";
4
5
  import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../session-list";
5
6
  import { DEFAULT_PENDING_CEILING_BYTES, MIN_PENDING_CEILING_BYTES, startSocketServe, startStdioServe } from "./index";
6
7
 
@@ -16,6 +17,88 @@ function usageError(message: string): never {
16
17
  throw new CliParseError(`gjc sdk serve: ${message}`);
17
18
  }
18
19
 
20
+ /**
21
+ * A `gjc sdk serve` operational failure with a stable machine-readable code and a
22
+ * documented exit code, mirroring `SdkSessionCliError`. Usage errors keep their own
23
+ * `CliParseError` path; this type covers only failures an embedder must branch on.
24
+ */
25
+ export class SdkServeError extends Error {
26
+ /**
27
+ * Diagnostics for a broker-teardown failure that happened alongside this primary
28
+ * failure. It rides beside `details` rather than inside it because `details`
29
+ * carries the broker's own error payload verbatim and embedders already read it.
30
+ */
31
+ cleanupError?: { code: string; message: string };
32
+
33
+ constructor(
34
+ readonly code: string,
35
+ message: string,
36
+ readonly exitCode: 1,
37
+ readonly details?: unknown,
38
+ ) {
39
+ super(message);
40
+ this.name = "SdkServeError";
41
+ }
42
+ }
43
+
44
+ /** Normalizes a serve-path failure into a typed error so nothing escapes untyped. */
45
+ function toServeError(error: unknown): Error {
46
+ if (error instanceof SdkServeError || error instanceof CliParseError) return error;
47
+ if (error instanceof SdkClientError) return new SdkServeError(error.code, error.message, 1, error.details);
48
+ return new SdkServeError("serve_failed", error instanceof Error ? error.message : "SDK serve failed.", 1);
49
+ }
50
+
51
+ /**
52
+ * Reads the broker discovery record so an unreadable one — a permission or file-kind
53
+ * failure, or a record from a newer state version — fails as a typed serve error
54
+ * instead of escaping the command boundary untyped. A missing record still reads as
55
+ * `null`, which the caller reports as `broker_unavailable`.
56
+ */
57
+ async function readServeDiscovery(agentDir: string): Promise<BrokerDiscovery | null> {
58
+ try {
59
+ return await readSdkBrokerDiscovery(agentDir);
60
+ } catch (error) {
61
+ // The path is a local file inside the agent dir, already named by this
62
+ // command's other diagnostics; the broker token is never surfaced.
63
+ const details =
64
+ error instanceof SdkDiscoveryError
65
+ ? { code: error.code, message: error.message, path: error.path }
66
+ : { code: "discovery_error", message: error instanceof Error ? error.message : String(error) };
67
+ throw new SdkServeError("broker_discovery_unreadable", "SDK broker discovery record is unreadable", 1, details);
68
+ }
69
+ }
70
+
71
+ /** Reduces a teardown failure to the code and message an embedder can branch on. */
72
+ function cleanupDiagnostics(error: Error): { code: string; message: string } {
73
+ return { code: error instanceof SdkClientError ? error.code : "serve_cleanup_failed", message: error.message };
74
+ }
75
+
76
+ /** Runs broker teardown to completion, handing back its failure instead of throwing it. */
77
+ async function brokerCloseFailure(broker: SdkClient): Promise<Error | undefined> {
78
+ try {
79
+ await broker.close();
80
+ return undefined;
81
+ } catch (error) {
82
+ return error instanceof Error ? error : new Error(String(error));
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Combines the serve body's failure with the broker-teardown failure. The primary
88
+ * failure always wins — a rejected teardown only ever rides along as diagnostics, it
89
+ * never replaces the error an embedder branches on — and a teardown that fails on its
90
+ * own still leaves typed. Exported for tests.
91
+ */
92
+ export function resolveServeOutcome(primary: Error | undefined, cleanup: Error | undefined): Error | undefined {
93
+ if (primary) {
94
+ if (cleanup && primary instanceof SdkServeError) primary.cleanupError = cleanupDiagnostics(cleanup);
95
+ return primary;
96
+ }
97
+ if (!cleanup) return undefined;
98
+ const diagnostics = cleanupDiagnostics(cleanup);
99
+ return new SdkServeError("serve_cleanup_failed", `SDK broker cleanup failed: ${cleanup.message}`, 1, diagnostics);
100
+ }
101
+
19
102
  function readFlagValue(argv: string[], index: number, flag: string): string {
20
103
  const value = argv[index + 1];
21
104
  if (value === undefined || value.startsWith("-")) usageError(`${flag} requires a value`);
@@ -113,14 +196,15 @@ export async function listBrokerSessions(broker: SdkClient, explicitSessionId?:
113
196
  export function selectBrokerSession(sessions: BrokerSessionRow[], explicitSessionId: string | undefined): string {
114
197
  if (explicitSessionId !== undefined) {
115
198
  const row = sessions.find(session => session.sessionId === explicitSessionId);
116
- if (!row) throw new Error(`not_found: session ${explicitSessionId} is not indexed by the broker`);
117
- if (row.ambiguous) throw new Error("ambiguous_session: session id maps to more than one state root");
118
- if (!row.live) throw new Error(`endpoint_stale: session ${explicitSessionId} endpoint is not live`);
199
+ if (!row) throw new SdkServeError("not_found", `session ${explicitSessionId} is not indexed by the broker`, 1);
200
+ if (row.ambiguous) throw new SdkServeError("ambiguous_session", "session id maps to more than one state root", 1);
201
+ if (!row.live) throw new SdkServeError("endpoint_stale", `session ${explicitSessionId} endpoint is not live`, 1);
119
202
  return row.sessionId;
120
203
  }
121
204
  const live = sessions.filter(session => session.live && !session.ambiguous);
122
- if (live.length === 0) throw new Error("no_live_endpoint: no live session endpoint");
123
- if (live.length > 1) throw new Error("multiple_live_endpoints: more than one live session; specify --session <id>");
205
+ if (live.length === 0) throw new SdkServeError("no_live_endpoint", "no live session endpoint", 1);
206
+ if (live.length > 1)
207
+ throw new SdkServeError("multiple_live_endpoints", "more than one live session; specify --session <id>", 1);
124
208
  return live[0]!.sessionId;
125
209
  }
126
210
 
@@ -133,25 +217,29 @@ export function selectBrokerSession(sessions: BrokerSessionRow[], explicitSessio
133
217
  export async function runSdkServe(argv: string[]): Promise<void> {
134
218
  const parsed = parseServeArguments(argv);
135
219
  if (parsed.mode.kind === "socket" && process.platform === "win32")
136
- throw new Error("unsupported_platform: --socket is unavailable on Windows.");
220
+ throw new SdkServeError("unsupported_platform", "--socket is unavailable on Windows.", 1);
137
221
  const pendingCeilingBytes = resolveServePendingCeiling(
138
222
  parsed.pendingCeiling,
139
223
  process.env.GJC_SDK_SERVE_PENDING_CEILING_BYTES,
140
224
  );
141
- const discovery = await readSdkBrokerDiscovery(getAgentDir());
142
- if (!discovery) throw new Error("broker_unavailable: SDK broker is not running");
225
+ const discovery = await readServeDiscovery(getAgentDir());
226
+ if (!discovery) throw new SdkServeError("broker_unavailable", "SDK broker is not running", 1);
143
227
  let broker: SdkClient;
144
228
  try {
145
229
  broker = await SdkClient.connect(discovery.url, discovery.token);
146
230
  } catch {
147
- throw new Error("broker_unavailable: SDK broker is not reachable");
231
+ throw new SdkServeError("broker_unavailable", "SDK broker is not reachable", 1);
148
232
  }
233
+ let primary: Error | undefined;
149
234
  try {
150
235
  const sessionId = selectBrokerSession(await listBrokerSessions(broker, parsed.sessionId), parsed.sessionId);
151
236
  const endpoint = brokerResult(await broker.global("session.get_endpoint", { sessionId }));
152
237
  const url = typeof endpoint.url === "string" && endpoint.url ? endpoint.url : undefined;
153
- const token = typeof endpoint.token === "string" ? endpoint.token : "";
154
- if (!url) throw new Error("unavailable: broker returned an invalid endpoint record");
238
+ const token = typeof endpoint.token === "string" && endpoint.token ? endpoint.token : undefined;
239
+ // An empty credential has to die here rather than at the transport: the relay
240
+ // writes it straight into the upstream URL, where it resurfaces as a generic
241
+ // connection failure and this stable malformed-endpoint code is lost.
242
+ if (!url || !token) throw new SdkServeError("unavailable", "broker returned an invalid endpoint record", 1);
155
243
  const options = { url, token, pendingCeilingBytes };
156
244
  const handle =
157
245
  parsed.mode.kind === "stdio"
@@ -169,8 +257,10 @@ export async function runSdkServe(argv: string[]): Promise<void> {
169
257
  process.removeListener("SIGTERM", stop);
170
258
  }
171
259
  } catch (error) {
172
- throw error instanceof SdkClientError ? new Error(`${error.code}: ${error.message}`) : error;
173
- } finally {
174
- await broker.close();
260
+ primary = toServeError(error);
175
261
  }
262
+ // Teardown always runs, but never through a `finally` throw: a rejected
263
+ // `broker.close()` there would replace the typed failure with its own.
264
+ const failure = resolveServeOutcome(primary, await brokerCloseFailure(broker));
265
+ if (failure) throw failure;
176
266
  }
@@ -25,8 +25,18 @@ const MAX_SESSION_INDEX_LINE_BYTES = 64 * 1024;
25
25
  const MAX_SESSION_TITLE_CHARACTERS = 200;
26
26
  const SENSITIVE_KEY = /(?:api[-_]?key|authorization|cookie|credential|password|secret|token)/iu;
27
27
  const CODEX_SESSION_ID = /^[A-Za-z0-9-]{1,128}$/u;
28
+ // Kept in step with the shape catalogue the sibling importer scrubber carries
29
+ // (`./redact.ts`). Both read the same class of external transcript, so a shape
30
+ // either importer recognizes has to be recognized by the other.
31
+ // Stripe restricted keys use `rk_`, which no other alternative reaches: the
32
+ // generic `sk[-_]` branch covers `sk_live_`/`sk_test_` only because they share
33
+ // the `sk` prefix. The sibling lists `(?:sk|rk)_(?:live|test)_` explicitly.
28
34
  const SECRET_VALUE =
29
- /(?:Bearer\s+[A-Za-z0-9._~+/=-]{8,}|\b(?:sk[-_]|ghp_|github_pat_)[A-Za-z0-9_-]{12,}|\bAKIA[A-Z0-9]{16}\b)/giu;
35
+ /(?:Bearer\s+[A-Za-z0-9._~+/=-]{8,}|\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b|\b(?:sk[-_]|gh[opsur]_|github_pat_)[A-Za-z0-9_-]{12,}|\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b|\bAIza[0-9A-Za-z_-]{35}\b|\bxox[baprs]-[A-Za-z0-9-]{10,}|\bnpm_[A-Za-z0-9]{20,}\b|\bglpat-[A-Za-z0-9_-]{20,}|\bhf_[A-Za-z0-9]{20,}\b)/giu;
36
+ // Cheap pre-check that gates the scan above. It must stay a superset of
37
+ // SECRET_VALUE's prefixes or a widened pattern would never run.
38
+ const SECRET_VALUE_HINT =
39
+ /(?:Bearer\s|sk[-_]|rk_(?:live|test)_|gh[opsur]_|github_pat_|AKIA|ASIA|ABIA|ACCA|AIza|xox[baprs]-|npm_|glpat-|hf_)/iu;
30
40
  const JWT_VALUE = /\b[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{8,8192}\.[A-Za-z0-9_-]{8,2048}\b/gu;
31
41
  const SECRET_ASSIGNMENT =
32
42
  /(\b(?:password|passwd|secret|token|api[-_]?key|access[-_]?key)\b\s*[:=]\s*)(?:"[^"\r\n]{4,4096}"|'[^'\r\n]{4,4096}'|[^\s,;]{4,4096})/giu;
@@ -181,8 +191,7 @@ export function sanitizeImportedString(value: string): { value: string; redacted
181
191
  return `${prefix}${scheme ? `${scheme} ${REDACTED_CREDENTIAL}` : REDACTED_CREDENTIAL}`;
182
192
  });
183
193
  }
184
- if (/(?:Bearer\s|(?:sk[-_]|ghp_|github_pat_)|AKIA[A-Z0-9]{16})/iu.test(next))
185
- next = replace(next, SECRET_VALUE, "[redacted-secret]");
194
+ if (SECRET_VALUE_HINT.test(next)) next = replace(next, SECRET_VALUE, "[redacted-secret]");
186
195
  if (next.includes(".")) next = replace(next, JWT_VALUE, "[redacted-secret]");
187
196
  if (/(?:password|passwd|secret|token|api[-_]?key|access[-_]?key)\s*[:=]/iu.test(next)) {
188
197
  next = next.replace(SECRET_ASSIGNMENT, (_match, prefix: string) => {
package/src/thinking.ts CHANGED
@@ -1,8 +1,27 @@
1
1
  import { type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core/thinking";
2
- import { clampThinkingLevelForModel, type Effort, THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
2
+ import {
3
+ clampThinkingLevelForModel,
4
+ type Effort,
5
+ getMiniMaxThinkingMode,
6
+ THINKING_EFFORTS,
7
+ } from "@gajae-code/ai/model-thinking";
3
8
  import type { Model } from "@gajae-code/ai/types";
9
+ import { getThinkingLevelMetadata as getLevelMetadata, type ThinkingLevelMetadata } from "./thinking-metadata";
4
10
 
5
- export { getThinkingLevelMetadata, type ThinkingLevelMetadata } from "./thinking-metadata";
11
+ export type { ThinkingLevelMetadata } from "./thinking-metadata";
12
+
13
+ export function getThinkingLevelMetadata(level: ThinkingLevel, model?: Model): ThinkingLevelMetadata {
14
+ const mode = model && getMiniMaxThinkingMode(model);
15
+ if (mode === "always-on") {
16
+ return { value: level, label: "always on", description: "Reasoning is always on; effort control is unsupported" };
17
+ }
18
+ if (mode === "toggle" && level !== ThinkingLevel.Inherit) {
19
+ return level === ThinkingLevel.Off
20
+ ? { value: level, label: "off", description: "Disable thinking (disabled)" }
21
+ : { value: level, label: "on", description: "Enable thinking (adaptive); no effort or token budget" };
22
+ }
23
+ return getLevelMetadata(level);
24
+ }
6
25
 
7
26
  const THINKING_LEVELS = new Set<string>([ThinkingLevel.Inherit, ThinkingLevel.Off, ...THINKING_EFFORTS]);
8
27
  const EFFORT_LEVELS = new Set<string>(THINKING_EFFORTS);
@@ -0,0 +1,3 @@
1
+ import mupdfWasmPath from "../../../../node_modules/mupdf/dist/mupdf-wasm.wasm" with { type: "file" };
2
+
3
+ export default mupdfWasmPath;
@@ -14,13 +14,35 @@
14
14
  * read the wasm from the bunfs directly — no disk sidecar needed.
15
15
  *
16
16
  * This must run before the first `import("mupdf")` anywhere in the process.
17
+ *
18
+ * The embedded asset lives behind a separate module (#5663) so that an
19
+ * npm-installed source tree never resolves the monorepo-only
20
+ * `node_modules/mupdf/...` specifier at import time. That module is pulled in
21
+ * with a synchronous `require`, NOT a top-level `await import(...)`: a
22
+ * top-level await here makes this module async, and bun 1.4.0 fails to
23
+ * propagate that async-ness through the `model-registry` <-> `model-resolver`
24
+ * import cycle. The resulting bundle emits a non-async `__esm(() => { ...
25
+ * await init_model_registry(); ... })` wrapper, so every compiled binary died
26
+ * at parse time with `SyntaxError: Unexpected identifier
27
+ * 'init_model_registry'` (#5674). Keep this resolution synchronous.
17
28
  */
18
- import mupdfWasmPath from "../../../../node_modules/mupdf/dist/mupdf-wasm.wasm" with { type: "file" };
29
+ import { isCompiledBinary } from "@gajae-code/utils/env";
19
30
 
20
31
  const MODULE_CONFIG_KEY = "$libmupdf_wasm_Module";
21
32
 
33
+ function loadEmbeddedMupdfWasmPath(): string | undefined {
34
+ if (!isCompiledBinary()) return undefined;
35
+ // Only reached inside a compiled binary, where the embedded module — and
36
+ // therefore the bunfs asset it points at — is always bundled in.
37
+ const embedded = require("./mupdf-wasm-embedded") as { default?: unknown };
38
+ const embeddedPath = embedded.default;
39
+ return typeof embeddedPath === "string" ? embeddedPath : undefined;
40
+ }
41
+
42
+ const mupdfWasmPath = loadEmbeddedMupdfWasmPath();
43
+
22
44
  export function ensureMupdfWasmResolution(): void {
23
45
  const globalScope = globalThis as typeof globalThis & Record<string, unknown>;
24
- if (globalScope[MODULE_CONFIG_KEY] !== undefined) return;
46
+ if (globalScope[MODULE_CONFIG_KEY] !== undefined || mupdfWasmPath === undefined) return;
25
47
  globalScope[MODULE_CONFIG_KEY] = { locateFile: () => String(mupdfWasmPath) };
26
48
  }