@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.
- package/CHANGELOG.md +72 -0
- package/dist/types/async/job-manager.d.ts +7 -6
- package/dist/types/config/autorouting-tier-map.d.ts +6 -0
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-profile-activation.d.ts +18 -3
- package/dist/types/config/model-registry.d.ts +9 -0
- package/dist/types/coordinator-mcp/server.d.ts +3 -0
- package/dist/types/modes/acp/acp-agent.d.ts +40 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/sdk/acp/adapter.d.ts +27 -0
- package/dist/types/sdk/acp/final-text.d.ts +8 -0
- package/dist/types/sdk/broker/ensure.d.ts +46 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +1 -1
- package/dist/types/sdk/prompt-failure.d.ts +30 -1
- package/dist/types/sdk/prompt-status.d.ts +8 -0
- package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +27 -0
- package/dist/types/thinking.d.ts +3 -1
- package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
- package/package.json +7 -7
- package/src/async/job-manager.ts +38 -8
- package/src/commands/sdk.ts +78 -4
- package/src/config/autorouting-tier-map.ts +2 -0
- package/src/config/file-lock.ts +52 -17
- package/src/config/model-profile-activation.ts +72 -10
- package/src/config/model-registry.ts +21 -0
- package/src/coordinator-mcp/server.ts +21 -6
- package/src/debug/report-bundle.ts +114 -24
- package/src/gjc-runtime/managed-owner-admission.ts +67 -50
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/modes/acp/acp-agent.ts +187 -14
- package/src/modes/acp/acp-event-mapper.ts +9 -0
- package/src/modes/components/model-selector.ts +31 -15
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/components/thinking-selector.ts +7 -3
- package/src/modes/controllers/selector-controller.ts +16 -3
- package/src/sdk/acp/adapter.ts +63 -1
- package/src/sdk/acp/final-text.ts +19 -2
- package/src/sdk/broker/ensure.ts +97 -19
- package/src/sdk/bus/index.ts +31 -4
- package/src/sdk/host/session-runtime.test.ts +498 -7
- package/src/sdk/host/session-runtime.ts +188 -21
- package/src/sdk/prompt-deadline-manager.ts +3 -3
- package/src/sdk/prompt-failure.ts +43 -1
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
- package/src/sdk/router/session-router.ts +22 -3
- package/src/sdk/session.ts +12 -9
- package/src/sdk/transport/serve-cli.ts +105 -15
- package/src/session-import/codex.ts +12 -3
- package/src/thinking.ts +21 -2
- package/src/utils/mupdf-wasm-embedded.ts +3 -0
- 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 {
|
|
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
|
|
117
|
-
if (row.ambiguous) throw new
|
|
118
|
-
if (!row.live) throw new
|
|
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
|
|
123
|
-
if (live.length > 1)
|
|
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
|
|
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
|
|
142
|
-
if (!discovery) throw new
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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[-_]|
|
|
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 (
|
|
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 {
|
|
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 {
|
|
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);
|
package/src/utils/mupdf-wasm.ts
CHANGED
|
@@ -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
|
|
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
|
}
|