@gajae-code/coding-agent 0.3.2 → 0.4.1
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 +39 -0
- package/dist/types/config/model-registry.d.ts +17 -10
- package/dist/types/config/models-config-schema.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +5 -0
- package/dist/types/edit/diff.d.ts +16 -0
- package/dist/types/edit/modes/replace.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/skills.d.ts +9 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -2
- package/dist/types/harness-control-plane/storage.d.ts +7 -0
- package/dist/types/lsp/client.d.ts +1 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +19 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/sdk.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/blob-store.d.ts +17 -0
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/skill-state/active-state.d.ts +13 -0
- package/dist/types/thinking.d.ts +3 -2
- package/dist/types/tools/index.d.ts +3 -0
- package/package.json +9 -7
- package/src/cli.ts +14 -0
- package/src/commands/harness.ts +192 -7
- package/src/commands/ultragoal.ts +1 -21
- package/src/config/model-equivalence.ts +1 -1
- package/src/config/model-registry.ts +32 -5
- package/src/config/models-config-schema.ts +7 -2
- package/src/config/settings-schema.ts +4 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +19 -23
- package/src/defaults/gjc/skills/ralplan/SKILL.md +7 -7
- package/src/discovery/claude-plugins.ts +25 -5
- package/src/edit/diff.ts +64 -1
- package/src/edit/modes/replace.ts +60 -2
- package/src/extensibility/gjc-plugins/activation.ts +87 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +114 -0
- package/src/extensibility/gjc-plugins/loader.ts +131 -0
- package/src/extensibility/gjc-plugins/paths.ts +66 -0
- package/src/extensibility/gjc-plugins/schema.ts +79 -0
- package/src/extensibility/gjc-plugins/state.ts +29 -0
- package/src/extensibility/gjc-plugins/tools.ts +47 -0
- package/src/extensibility/gjc-plugins/types.ts +97 -0
- package/src/extensibility/gjc-plugins/validation.ts +76 -0
- package/src/extensibility/skills.ts +39 -7
- package/src/gjc-runtime/state-runtime.ts +93 -2
- package/src/gjc-runtime/state-writer.ts +17 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
- package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
- package/src/gjc-runtime/workflow-manifest.ts +2 -2
- package/src/harness-control-plane/storage.ts +144 -2
- package/src/hashline/hash.ts +23 -0
- package/src/hooks/skill-state.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +7 -0
- package/src/modes/acp/acp-agent.ts +25 -2
- package/src/modes/bridge/bridge-mode.ts +124 -2
- package/src/modes/controllers/input-controller.ts +14 -2
- package/src/modes/prompt-action-autocomplete.ts +49 -10
- package/src/modes/rpc/rpc-client.ts +79 -3
- package/src/modes/rpc/rpc-mode.ts +67 -0
- package/src/modes/rpc/rpc-types.ts +224 -2
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
- package/src/modes/shared/agent-wire/command-validation.ts +25 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
- package/src/modes/shared/agent-wire/handshake.ts +43 -3
- package/src/modes/shared/agent-wire/protocol.ts +7 -0
- package/src/modes/shared/agent-wire/responses.ts +2 -2
- package/src/modes/shared/agent-wire/scopes.ts +2 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/system/system-prompt.md +9 -0
- package/src/runtime-mcp/client.ts +7 -4
- package/src/runtime-mcp/manager.ts +45 -13
- package/src/runtime-mcp/transports/http.ts +40 -14
- package/src/runtime-mcp/transports/stdio.ts +11 -10
- package/src/sdk.ts +47 -0
- package/src/session/agent-session.ts +211 -2
- package/src/session/blob-store.ts +84 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-manager.ts +390 -33
- package/src/session/session-storage.ts +26 -0
- package/src/setup/provider-onboarding.ts +2 -2
- package/src/skill-state/active-state.ts +89 -1
- package/src/task/discovery.ts +7 -1
- package/src/task/executor.ts +16 -2
- package/src/thinking.ts +8 -2
- package/src/tools/ask.ts +39 -9
- package/src/tools/index.ts +3 -0
- package/src/tools/skill.ts +15 -3
- package/src/utils/edit-mode.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
37
37
|
"lint": "biome lint .",
|
|
38
38
|
"test": "bun test",
|
|
39
|
+
"generate-schemas": "bun ../../scripts/generate-json-schemas.ts",
|
|
40
|
+
"check:schemas": "bun ../../scripts/generate-json-schemas.ts --check",
|
|
39
41
|
"fix": "biome check --write --unsafe . && bun run format-prompts && bun run generate-docs-index",
|
|
40
42
|
"fmt": "biome format --write . && bun run format-prompts",
|
|
41
43
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
@@ -48,12 +50,12 @@
|
|
|
48
50
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
49
51
|
"@babel/parser": "^7.29.3",
|
|
50
52
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@gajae-code/stats": "0.
|
|
52
|
-
"@gajae-code/agent-core": "0.
|
|
53
|
-
"@gajae-code/ai": "0.
|
|
54
|
-
"@gajae-code/natives": "0.
|
|
55
|
-
"@gajae-code/tui": "0.
|
|
56
|
-
"@gajae-code/utils": "0.
|
|
53
|
+
"@gajae-code/stats": "0.4.1",
|
|
54
|
+
"@gajae-code/agent-core": "0.4.1",
|
|
55
|
+
"@gajae-code/ai": "0.4.1",
|
|
56
|
+
"@gajae-code/natives": "0.4.1",
|
|
57
|
+
"@gajae-code/tui": "0.4.1",
|
|
58
|
+
"@gajae-code/utils": "0.4.1",
|
|
57
59
|
"@puppeteer/browsers": "^2.13.0",
|
|
58
60
|
"@types/turndown": "5.0.6",
|
|
59
61
|
"@xterm/headless": "^6.0.0",
|
package/src/cli.ts
CHANGED
|
@@ -84,6 +84,20 @@ function isSubcommand(first: string | undefined): boolean {
|
|
|
84
84
|
async function runSmokeTest(): Promise<void> {
|
|
85
85
|
const { smokeTestSyncWorker } = await import("@gajae-code/stats");
|
|
86
86
|
await smokeTestSyncWorker();
|
|
87
|
+
// Prove the embedded native addon extracts and the new perf exports resolve in
|
|
88
|
+
// the COMPILED single binary (dev runs only load the on-disk .node). Loading the
|
|
89
|
+
// natives module triggers loadNative()/embedded extraction; calling each new
|
|
90
|
+
// export confirms the symbols are present in the shipped binary.
|
|
91
|
+
const { h06FormatHashLines, h02ScoreSequenceFuzzy, h01FindBestFuzzyMatch } = await import(
|
|
92
|
+
"../../natives/native/index.js"
|
|
93
|
+
);
|
|
94
|
+
const hashed = h06FormatHashLines("a\nb", 1);
|
|
95
|
+
if (hashed.split("\n").length !== 2) {
|
|
96
|
+
throw new Error(`smoke-test: h06FormatHashLines returned unexpected output: ${JSON.stringify(hashed)}`);
|
|
97
|
+
}
|
|
98
|
+
if (typeof h02ScoreSequenceFuzzy !== "function" || typeof h01FindBestFuzzyMatch !== "function") {
|
|
99
|
+
throw new Error("smoke-test: native fuzzy exports missing from embedded addon");
|
|
100
|
+
}
|
|
87
101
|
process.stdout.write("smoke-test: ok\n");
|
|
88
102
|
}
|
|
89
103
|
|
package/src/commands/harness.ts
CHANGED
|
@@ -9,20 +9,28 @@
|
|
|
9
9
|
* until the RuntimeOwner (M3+) lands.
|
|
10
10
|
*/
|
|
11
11
|
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { randomBytes } from "node:crypto";
|
|
12
13
|
import { existsSync } from "node:fs";
|
|
13
14
|
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
14
15
|
import { resolveGjcTmuxCommand, sanitizeTmuxToken } from "../gjc-runtime/tmux-common";
|
|
15
16
|
import { classifyRecovery } from "../harness-control-plane/classifier";
|
|
16
17
|
import { callEndpoint, EndpointUnreachableError } from "../harness-control-plane/control-endpoint";
|
|
17
|
-
import { RuntimeOwner, resolveOwner } from "../harness-control-plane/owner";
|
|
18
|
+
import { type ResolvedOwner, RuntimeOwner, resolveOwner } from "../harness-control-plane/owner";
|
|
19
|
+
import { preserveDirtyWorktree } from "../harness-control-plane/preserve";
|
|
20
|
+
import { buildReceipt, requiresVanishBeforeAction, type VanishEvidence } from "../harness-control-plane/receipts";
|
|
18
21
|
import { GajaeCodeRpc } from "../harness-control-plane/rpc-adapter";
|
|
22
|
+
import { classifyLeaseStatus, readLease } from "../harness-control-plane/session-lease";
|
|
19
23
|
import { buildResponse, buildStateView } from "../harness-control-plane/state-machine";
|
|
20
24
|
import {
|
|
25
|
+
canonicalWorkspacePath,
|
|
21
26
|
generateSessionId,
|
|
22
27
|
readEvents,
|
|
23
28
|
readSessionState,
|
|
29
|
+
rememberHarnessSessionRoot,
|
|
24
30
|
resolveHarnessRoot,
|
|
31
|
+
resolveHarnessSessionRoot,
|
|
25
32
|
sessionPaths,
|
|
33
|
+
writeReceiptImmutable,
|
|
26
34
|
writeSessionState,
|
|
27
35
|
} from "../harness-control-plane/storage";
|
|
28
36
|
import {
|
|
@@ -31,6 +39,7 @@ import {
|
|
|
31
39
|
type GitDelta,
|
|
32
40
|
type Harness as HarnessKind,
|
|
33
41
|
type Observation,
|
|
42
|
+
type RecoveryClassification,
|
|
34
43
|
type RetryBudget,
|
|
35
44
|
SESSION_SCHEMA_VERSION,
|
|
36
45
|
type SessionHandle,
|
|
@@ -120,8 +129,12 @@ function gitOutput(workspace: string, args: string[]): string | null {
|
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
|
|
132
|
+
function resolveInputWorkspace(input: Record<string, unknown>): string {
|
|
133
|
+
return canonicalWorkspacePath(typeof input.workspace === "string" ? input.workspace : process.cwd());
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
function buildPreflight(input: Record<string, unknown>): HarnessPreflight {
|
|
124
|
-
const workspace =
|
|
137
|
+
const workspace = resolveInputWorkspace(input);
|
|
125
138
|
const declaredBranch = typeof input.branch === "string" && input.branch.trim() ? input.branch.trim() : null;
|
|
126
139
|
const blockers: string[] = [];
|
|
127
140
|
const gitRoot = gitOutput(workspace, ["rev-parse", "--show-toplevel"]);
|
|
@@ -198,6 +211,7 @@ async function buildObservation(
|
|
|
198
211
|
const observedSignals = ["SessionStart"];
|
|
199
212
|
for (const event of events.slice(-200)) {
|
|
200
213
|
pushUnique(observedSignals, (event.evidence as { signal?: unknown } | undefined)?.signal);
|
|
214
|
+
if (event.kind === "prompt_accepted") pushUnique(observedSignals, "prompt-accepted");
|
|
201
215
|
}
|
|
202
216
|
const terminalEvent = completedTerminalEvent(events);
|
|
203
217
|
const lastEventAt = events.at(-1)?.createdAt;
|
|
@@ -215,6 +229,114 @@ async function buildObservation(
|
|
|
215
229
|
completedTerminalEvent: terminalEvent,
|
|
216
230
|
};
|
|
217
231
|
}
|
|
232
|
+
interface OwnerExitEvidence {
|
|
233
|
+
reason: string;
|
|
234
|
+
leaseStatus: string;
|
|
235
|
+
pid: number | null;
|
|
236
|
+
endpointPresent: boolean;
|
|
237
|
+
heartbeatAt: string | null;
|
|
238
|
+
expiresAt: string | null;
|
|
239
|
+
lastEventKind: string | null;
|
|
240
|
+
lastEventAt: string | null;
|
|
241
|
+
lastSignal: string | null;
|
|
242
|
+
promptAcceptedSeen: boolean;
|
|
243
|
+
completedSeen: boolean;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function buildOwnerExitEvidence(root: string, state: SessionState): Promise<OwnerExitEvidence> {
|
|
247
|
+
const lease = await readLease(root, state.sessionId);
|
|
248
|
+
const leaseStatus = classifyLeaseStatus(lease);
|
|
249
|
+
const events = await readEvents(root, state.sessionId, 0);
|
|
250
|
+
const lastEvent = events.at(-1) ?? null;
|
|
251
|
+
let lastSignal: string | null = null;
|
|
252
|
+
let promptAcceptedSeen = false;
|
|
253
|
+
let completedSeen = false;
|
|
254
|
+
for (const event of events) {
|
|
255
|
+
const signal = (event.evidence as { signal?: unknown } | undefined)?.signal;
|
|
256
|
+
if (typeof signal === "string") lastSignal = signal;
|
|
257
|
+
if (event.kind === "prompt_accepted" || signal === "prompt-accepted") promptAcceptedSeen = true;
|
|
258
|
+
if (event.kind === "rpc_agent_completed" || signal === "completed") completedSeen = true;
|
|
259
|
+
}
|
|
260
|
+
let reason = "owner-not-live";
|
|
261
|
+
if (!lease) {
|
|
262
|
+
reason = promptAcceptedSeen && !completedSeen ? "owner-exited-after-prompt-acceptance" : "owner-lease-missing";
|
|
263
|
+
} else if (leaseStatus === "dead") {
|
|
264
|
+
reason = promptAcceptedSeen && !completedSeen ? "owner-exited-after-prompt-acceptance" : "owner-process-dead";
|
|
265
|
+
} else if (leaseStatus === "expiredAlive") {
|
|
266
|
+
reason = "owner-lease-expired";
|
|
267
|
+
} else if (leaseStatus === "epermAlive") {
|
|
268
|
+
reason = "owner-liveness-unknown-permission-denied";
|
|
269
|
+
} else {
|
|
270
|
+
reason = "owner-endpoint-unreachable";
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
reason,
|
|
274
|
+
leaseStatus,
|
|
275
|
+
pid: lease?.pid ?? null,
|
|
276
|
+
endpointPresent: Boolean(lease?.endpoint?.path),
|
|
277
|
+
heartbeatAt: lease?.heartbeatAt ?? null,
|
|
278
|
+
expiresAt: lease?.expiresAt ?? null,
|
|
279
|
+
lastEventKind: lastEvent?.kind ?? null,
|
|
280
|
+
lastEventAt: lastEvent?.createdAt ?? null,
|
|
281
|
+
lastSignal,
|
|
282
|
+
promptAcceptedSeen,
|
|
283
|
+
completedSeen,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function writeVanishReceiptForDecision(
|
|
288
|
+
root: string,
|
|
289
|
+
state: SessionState,
|
|
290
|
+
observation: Observation,
|
|
291
|
+
classification: RecoveryClassification,
|
|
292
|
+
): Promise<string | null> {
|
|
293
|
+
if (!requiresVanishBeforeAction(classification)) return null;
|
|
294
|
+
const dirty = observation.gitDelta === "dirty" || observation.gitDelta === "unknown";
|
|
295
|
+
const preservation = dirty ? preserveDirtyWorktree(observation.cwd) : null;
|
|
296
|
+
const evidence: VanishEvidence = {
|
|
297
|
+
classification,
|
|
298
|
+
gitDelta: observation.gitDelta,
|
|
299
|
+
gitStatusPorcelain: preservation
|
|
300
|
+
? `tracked:${preservation.trackedDiffSha256};untracked:${preservation.untrackedManifest.length}`
|
|
301
|
+
: observation.observedSignals.join(","),
|
|
302
|
+
untrackedManifest: preservation?.untrackedManifest ?? [],
|
|
303
|
+
preservation: preservation?.stashRef ? "stash" : "snapshot",
|
|
304
|
+
stashRef: preservation?.stashRef ?? null,
|
|
305
|
+
snapshotComplete: preservation?.snapshotComplete ?? true,
|
|
306
|
+
forbiddenActions: dirty ? ["restart-clean", "delete", "reset"] : [],
|
|
307
|
+
};
|
|
308
|
+
const receipt = buildReceipt<VanishEvidence>({
|
|
309
|
+
receiptId: `vanish-${Date.now()}-${randomBytes(4).toString("hex")}`,
|
|
310
|
+
sessionId: state.sessionId,
|
|
311
|
+
family: "vanish",
|
|
312
|
+
source: "cli-recover",
|
|
313
|
+
subject: {
|
|
314
|
+
workspace: observation.cwd,
|
|
315
|
+
branch: observation.branch,
|
|
316
|
+
head: null,
|
|
317
|
+
commit: null,
|
|
318
|
+
},
|
|
319
|
+
evidence,
|
|
320
|
+
});
|
|
321
|
+
await writeReceiptImmutable(root, state.sessionId, "vanish", receipt.receiptId, receipt);
|
|
322
|
+
return receipt.receiptId;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function updateStateWithRestoredOwner(state: SessionState, leasePath: string, resolved: ResolvedOwner): void {
|
|
326
|
+
state.lifecycle = "observing";
|
|
327
|
+
state.blockers = state.blockers.filter(blocker => !isOwnerLivenessBlocker(blocker));
|
|
328
|
+
state.handle.processHandle = {
|
|
329
|
+
kind: "runtime-owner",
|
|
330
|
+
ownerId: resolved.lease?.ownerId ?? null,
|
|
331
|
+
pid: resolved.lease?.pid ?? null,
|
|
332
|
+
};
|
|
333
|
+
state.handle.ownerHandle = {
|
|
334
|
+
leasePath,
|
|
335
|
+
endpoint: resolved.socketPath,
|
|
336
|
+
heartbeatAt: resolved.lease?.heartbeatAt ?? null,
|
|
337
|
+
};
|
|
338
|
+
state.updatedAt = nowIso();
|
|
339
|
+
}
|
|
218
340
|
|
|
219
341
|
function isOwnerLivenessBlocker(blocker: string): boolean {
|
|
220
342
|
return blocker === "detached-owner-not-live" || blocker.startsWith("owner-vanished:");
|
|
@@ -327,9 +449,14 @@ export default class Harness extends Command {
|
|
|
327
449
|
async run(): Promise<void> {
|
|
328
450
|
const { args, flags } = await this.parse(Harness);
|
|
329
451
|
const verb = String(args.verb);
|
|
330
|
-
|
|
452
|
+
let root = resolveHarnessRoot();
|
|
331
453
|
try {
|
|
332
454
|
const input = parseInput(flags.input);
|
|
455
|
+
const sessionId = flags.session ?? (typeof input.sessionId === "string" ? input.sessionId : undefined);
|
|
456
|
+
const expectedWorkspace = typeof input.workspace === "string" ? resolveInputWorkspace(input) : undefined;
|
|
457
|
+
if (verb !== "start" && sessionId) {
|
|
458
|
+
root = await resolveHarnessSessionRoot(root, sessionId, process.env, { expectedWorkspace });
|
|
459
|
+
}
|
|
333
460
|
switch (verb) {
|
|
334
461
|
case "start":
|
|
335
462
|
return await this.#start(root, input);
|
|
@@ -468,6 +595,9 @@ export default class Harness extends Command {
|
|
|
468
595
|
if (process.env.GJC_HARNESS_RPC_COMMAND) {
|
|
469
596
|
envAssignments.push(`GJC_HARNESS_RPC_COMMAND=${shellQuote(process.env.GJC_HARNESS_RPC_COMMAND)}`);
|
|
470
597
|
}
|
|
598
|
+
if (process.env.GJC_HARNESS_TEST_NODE_MODULES) {
|
|
599
|
+
envAssignments.push(`GJC_HARNESS_TEST_NODE_MODULES=${shellQuote(process.env.GJC_HARNESS_TEST_NODE_MODULES)}`);
|
|
600
|
+
}
|
|
471
601
|
const ownerCommand = this.#buildOwnerCommand(sessionId).map(shellQuote).join(" ");
|
|
472
602
|
const shellCommand = `exec env ${envAssignments.join(" ")} ${ownerCommand}`;
|
|
473
603
|
const created = Bun.spawnSync([tmuxCommand, "new-session", "-d", "-s", sessionName, "-c", cwd, shellCommand], {
|
|
@@ -498,7 +628,13 @@ export default class Harness extends Command {
|
|
|
498
628
|
const cmd = this.#buildOwnerCommand(sessionId);
|
|
499
629
|
const child = Bun.spawn(cmd, {
|
|
500
630
|
cwd,
|
|
501
|
-
env: {
|
|
631
|
+
env: {
|
|
632
|
+
...process.env,
|
|
633
|
+
GJC_HARNESS_STATE_ROOT: root,
|
|
634
|
+
...(process.env.GJC_HARNESS_TEST_NODE_MODULES
|
|
635
|
+
? { GJC_HARNESS_TEST_NODE_MODULES: process.env.GJC_HARNESS_TEST_NODE_MODULES }
|
|
636
|
+
: {}),
|
|
637
|
+
},
|
|
502
638
|
stdout: "ignore",
|
|
503
639
|
stderr: "ignore",
|
|
504
640
|
stdin: "ignore",
|
|
@@ -540,7 +676,7 @@ export default class Harness extends Command {
|
|
|
540
676
|
process.exitCode = 1;
|
|
541
677
|
return;
|
|
542
678
|
}
|
|
543
|
-
const workspace =
|
|
679
|
+
const workspace = resolveInputWorkspace(input);
|
|
544
680
|
const sessionId = typeof input.sessionId === "string" ? input.sessionId : generateSessionId();
|
|
545
681
|
const eventsPath = `${root}/sessions/${sessionId}/events.jsonl`;
|
|
546
682
|
const leasePath = `${root}/sessions/${sessionId}/lease.json`;
|
|
@@ -573,6 +709,7 @@ export default class Harness extends Command {
|
|
|
573
709
|
updatedAt: startedAt,
|
|
574
710
|
};
|
|
575
711
|
await writeSessionState(root, state);
|
|
712
|
+
await rememberHarnessSessionRoot(root, sessionId);
|
|
576
713
|
let ownerLive = false;
|
|
577
714
|
let ownerRuntime: OwnerSpawnResult["runtime"] = "manual";
|
|
578
715
|
let ownerFallbackReason: string | null = null;
|
|
@@ -676,6 +813,10 @@ export default class Harness extends Command {
|
|
|
676
813
|
state = await reconcileCompletedOwnerExited(root, state, observation, completedTerminalEvent);
|
|
677
814
|
const vanishedOwnerBlock = needsVanishedOwnerBlock(state, observation, completedTerminalEvent);
|
|
678
815
|
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
816
|
+
const ownerExit =
|
|
817
|
+
!ownerLive && (vanishedOwnerBlock || completedTerminalEvent)
|
|
818
|
+
? await buildOwnerExitEvidence(root, state)
|
|
819
|
+
: null;
|
|
679
820
|
writeJson(
|
|
680
821
|
buildResponse(state, ownerLive, {
|
|
681
822
|
observation: { ...observation, lifecycle: state.lifecycle },
|
|
@@ -686,6 +827,7 @@ export default class Harness extends Command {
|
|
|
686
827
|
...(completedTerminalEvent && !ownerLive
|
|
687
828
|
? { completedOwnerExited: true, terminalResult: completedTerminalEvent }
|
|
688
829
|
: {}),
|
|
830
|
+
...(ownerExit ? { ownerExit } : {}),
|
|
689
831
|
}),
|
|
690
832
|
);
|
|
691
833
|
}
|
|
@@ -767,7 +909,9 @@ export default class Harness extends Command {
|
|
|
767
909
|
buildResponse(state, ownerLiveFor(state), {
|
|
768
910
|
events,
|
|
769
911
|
cursor: nextCursor,
|
|
770
|
-
note: "tail-only;
|
|
912
|
+
note: "tail-only; events are preserved after owner exit",
|
|
913
|
+
ownerLive: ownerLiveFor(state),
|
|
914
|
+
ownerExit: ownerLiveFor(state) ? null : await buildOwnerExitEvidence(root, state),
|
|
771
915
|
}),
|
|
772
916
|
);
|
|
773
917
|
}
|
|
@@ -802,21 +946,62 @@ export default class Harness extends Command {
|
|
|
802
946
|
async #recoverWithoutOwner(root: string, sessionId: string, input: Record<string, unknown>): Promise<void> {
|
|
803
947
|
const budget = resolveRetryBudget(input);
|
|
804
948
|
let state = await loadState(root, sessionId);
|
|
949
|
+
const beforeExit = await buildOwnerExitEvidence(root, state);
|
|
805
950
|
const { observation, completedTerminalEvent } = await buildObservation(root, state, false);
|
|
806
951
|
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
807
952
|
const decision = classifyRecovery({
|
|
808
953
|
observation: { ...observation, lifecycle: state.lifecycle },
|
|
809
954
|
retryBudget: budget,
|
|
810
955
|
});
|
|
956
|
+
const vanishReceiptId = await writeVanishReceiptForDecision(root, state, observation, decision.classification);
|
|
957
|
+
const restoredOwner =
|
|
958
|
+
decision.ownerRequired && beforeExit.endpointPresent
|
|
959
|
+
? await this.#spawnDetachedOwner(root, sessionId, state.handle.workspace)
|
|
960
|
+
: null;
|
|
961
|
+
if (restoredOwner?.live) {
|
|
962
|
+
const resolved = await resolveOwner(root, sessionId);
|
|
963
|
+
if (resolved.live && resolved.socketPath) {
|
|
964
|
+
updateStateWithRestoredOwner(state, state.handle.ownerHandle.leasePath, resolved);
|
|
965
|
+
if (restoredOwner.tmuxSessionName)
|
|
966
|
+
state.handle.viewportHandle.tmuxSessionName = restoredOwner.tmuxSessionName;
|
|
967
|
+
await writeSessionState(root, state);
|
|
968
|
+
writeJson(
|
|
969
|
+
buildResponse(state, true, {
|
|
970
|
+
pending: false,
|
|
971
|
+
restoredOwner: true,
|
|
972
|
+
decision,
|
|
973
|
+
observation: { ...observation, lifecycle: state.lifecycle, ownerLive: true },
|
|
974
|
+
ownerExit: beforeExit,
|
|
975
|
+
ownerRuntime: restoredOwner.runtime,
|
|
976
|
+
...(restoredOwner.fallbackReason ? { ownerFallbackReason: restoredOwner.fallbackReason } : {}),
|
|
977
|
+
...(vanishReceiptId ? { vanishReceiptId } : {}),
|
|
978
|
+
}),
|
|
979
|
+
);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const afterExit = await buildOwnerExitEvidence(root, state);
|
|
811
984
|
writeJson(
|
|
812
985
|
buildResponse(
|
|
813
986
|
state,
|
|
814
987
|
false,
|
|
815
988
|
{
|
|
816
989
|
pending: false,
|
|
817
|
-
reason:
|
|
990
|
+
reason: afterExit.reason,
|
|
818
991
|
decision,
|
|
819
992
|
observation: { ...observation, lifecycle: state.lifecycle },
|
|
993
|
+
ownerExit: afterExit,
|
|
994
|
+
...(restoredOwner
|
|
995
|
+
? {
|
|
996
|
+
restoreAttempt: {
|
|
997
|
+
runtime: restoredOwner.runtime,
|
|
998
|
+
live: restoredOwner.live,
|
|
999
|
+
fallbackReason: restoredOwner.fallbackReason,
|
|
1000
|
+
blockerReason: restoredOwner.blockerReason,
|
|
1001
|
+
},
|
|
1002
|
+
}
|
|
1003
|
+
: {}),
|
|
1004
|
+
...(vanishReceiptId ? { vanishReceiptId } : {}),
|
|
820
1005
|
},
|
|
821
1006
|
false,
|
|
822
1007
|
),
|
|
@@ -6,13 +6,7 @@ import {
|
|
|
6
6
|
writeCurrentSessionGoalModeState,
|
|
7
7
|
writePendingGoalModeRequest,
|
|
8
8
|
} from "../gjc-runtime/goal-mode-request";
|
|
9
|
-
import {
|
|
10
|
-
buildUltragoalHudSummary,
|
|
11
|
-
getUltragoalStatus,
|
|
12
|
-
readUltragoalLedger,
|
|
13
|
-
runNativeUltragoalCommand,
|
|
14
|
-
} from "../gjc-runtime/ultragoal-runtime";
|
|
15
|
-
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
9
|
+
import { runNativeUltragoalCommand } from "../gjc-runtime/ultragoal-runtime";
|
|
16
10
|
|
|
17
11
|
export default class Ultragoal extends Command {
|
|
18
12
|
static description = "Run native GJC Ultragoal workflow commands";
|
|
@@ -25,20 +19,6 @@ export default class Ultragoal extends Command {
|
|
|
25
19
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
26
20
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
27
21
|
process.exitCode = result.status;
|
|
28
|
-
try {
|
|
29
|
-
const summary = await getUltragoalStatus(process.cwd());
|
|
30
|
-
const ledger = await readUltragoalLedger(process.cwd());
|
|
31
|
-
await syncSkillActiveState({
|
|
32
|
-
cwd: process.cwd(),
|
|
33
|
-
skill: "ultragoal",
|
|
34
|
-
active: summary.exists && summary.status !== "complete",
|
|
35
|
-
phase: summary.status,
|
|
36
|
-
hud: buildUltragoalHudSummary(summary, ledger.at(-1)),
|
|
37
|
-
source: "gjc-ultragoal",
|
|
38
|
-
});
|
|
39
|
-
} catch {
|
|
40
|
-
// HUD sync is best-effort and must not change command semantics.
|
|
41
|
-
}
|
|
42
22
|
if (result.status !== 0 || !shouldActivateGoalMode) return;
|
|
43
23
|
|
|
44
24
|
const cwd = process.cwd();
|
|
@@ -42,7 +42,7 @@ interface ResolvedCanonicalModel {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const TRAILING_MARKER_PATTERN =
|
|
45
|
-
/[-:](?:thinking|customtools|high|low|medium|minimal|xhigh|free|cloud|exacto|nitro|original|optimized|nvfp4|fp8|fp4|bf16|int8|int4)$/i;
|
|
45
|
+
/[-:](?:thinking|customtools|high|low|medium|minimal|xhigh|max|free|cloud|exacto|nitro|original|optimized|nvfp4|fp8|fp4|bf16|int8|int4)$/i;
|
|
46
46
|
const WRAPPER_PREFIXES = ["duo-chat-"] as const;
|
|
47
47
|
|
|
48
48
|
let referenceDataCache: CanonicalReferenceData | undefined;
|
|
@@ -2,6 +2,7 @@ import * as path from "node:path";
|
|
|
2
2
|
import {
|
|
3
3
|
type Api,
|
|
4
4
|
type AssistantMessageEventStream,
|
|
5
|
+
type CacheRetention,
|
|
5
6
|
type Context,
|
|
6
7
|
createModelManager,
|
|
7
8
|
enrichModelThinking,
|
|
@@ -240,6 +241,7 @@ interface ProviderValidationConfig {
|
|
|
240
241
|
compat?: Model<Api>["compat"];
|
|
241
242
|
requestTransform?: ModelRequestTransform;
|
|
242
243
|
disableStrictTools?: boolean;
|
|
244
|
+
cacheRetention?: CacheRetention;
|
|
243
245
|
modelOverrides?: Record<string, unknown>;
|
|
244
246
|
models: ProviderValidationModel[];
|
|
245
247
|
}
|
|
@@ -267,11 +269,12 @@ function validateProviderConfiguration(
|
|
|
267
269
|
!config.apiKeyEnv &&
|
|
268
270
|
!config.disableStrictTools &&
|
|
269
271
|
!config.requestTransform &&
|
|
272
|
+
!config.cacheRetention &&
|
|
270
273
|
!hasModelOverrides &&
|
|
271
274
|
!config.discovery
|
|
272
275
|
) {
|
|
273
276
|
throw new Error(
|
|
274
|
-
`Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "requestTransform", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
|
|
277
|
+
`Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "requestTransform", "cacheRetention", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
|
|
275
278
|
);
|
|
276
279
|
}
|
|
277
280
|
}
|
|
@@ -384,6 +387,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
384
387
|
compat: providerConfig.compat,
|
|
385
388
|
requestTransform: providerConfig.requestTransform,
|
|
386
389
|
disableStrictTools: providerConfig.disableStrictTools,
|
|
390
|
+
cacheRetention: providerConfig.cacheRetention,
|
|
387
391
|
modelOverrides: providerConfig.modelOverrides,
|
|
388
392
|
models: (providerConfig.models ?? []) as ProviderValidationModel[],
|
|
389
393
|
},
|
|
@@ -402,6 +406,7 @@ interface ProviderOverride {
|
|
|
402
406
|
compat?: Model<Api>["compat"];
|
|
403
407
|
transport?: Model<Api>["transport"];
|
|
404
408
|
requestTransform?: ModelRequestTransform;
|
|
409
|
+
cacheRetention?: CacheRetention;
|
|
405
410
|
}
|
|
406
411
|
|
|
407
412
|
const PROVIDER_BASE_URL_ENV_ALIASES: Record<string, readonly string[]> = {
|
|
@@ -449,7 +454,10 @@ function resolveProviderBaseUrlFromEnv(provider: string): string | undefined {
|
|
|
449
454
|
export function mergeDiscoveredModel<TApi extends Api>(
|
|
450
455
|
model: Model<TApi>,
|
|
451
456
|
existing: Model<Api> | undefined,
|
|
452
|
-
providerOverride?: Pick<
|
|
457
|
+
providerOverride?: Pick<
|
|
458
|
+
ProviderOverride,
|
|
459
|
+
"baseUrl" | "headers" | "transport" | "requestTransform" | "cacheRetention"
|
|
460
|
+
>,
|
|
453
461
|
): Model<TApi> {
|
|
454
462
|
if (existing) {
|
|
455
463
|
return {
|
|
@@ -460,6 +468,7 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
460
468
|
mergeRequestTransform(existing.requestTransform, model.requestTransform),
|
|
461
469
|
providerOverride?.requestTransform,
|
|
462
470
|
),
|
|
471
|
+
cacheRetention: model.cacheRetention ?? existing.cacheRetention ?? providerOverride?.cacheRetention,
|
|
463
472
|
};
|
|
464
473
|
}
|
|
465
474
|
if (providerOverride) {
|
|
@@ -481,6 +490,7 @@ interface DiscoveryProviderConfig {
|
|
|
481
490
|
headers?: Record<string, string>;
|
|
482
491
|
compat?: Model<Api>["compat"];
|
|
483
492
|
requestTransform?: ModelRequestTransform;
|
|
493
|
+
cacheRetention?: CacheRetention;
|
|
484
494
|
discovery: ProviderDiscovery;
|
|
485
495
|
optional?: boolean;
|
|
486
496
|
}
|
|
@@ -678,6 +688,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
678
688
|
if (override.reasoning !== undefined) result.reasoning = override.reasoning;
|
|
679
689
|
if (override.thinking !== undefined) result.thinking = override.thinking as ThinkingConfig;
|
|
680
690
|
if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
|
|
691
|
+
if (override.cacheRetention !== undefined) result.cacheRetention = override.cacheRetention;
|
|
681
692
|
if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
|
|
682
693
|
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
683
694
|
if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
|
|
@@ -716,6 +727,7 @@ interface CustomModelDefinitionLike {
|
|
|
716
727
|
premiumMultiplier?: number;
|
|
717
728
|
wireModelId?: string;
|
|
718
729
|
requestTransform?: ModelRequestTransform;
|
|
730
|
+
cacheRetention?: CacheRetention;
|
|
719
731
|
}
|
|
720
732
|
|
|
721
733
|
interface CustomModelBuildOptions {
|
|
@@ -740,6 +752,7 @@ type CustomModelOverlay = {
|
|
|
740
752
|
premiumMultiplier?: number;
|
|
741
753
|
wireModelId?: string;
|
|
742
754
|
requestTransform?: ModelRequestTransform;
|
|
755
|
+
cacheRetention?: CacheRetention;
|
|
743
756
|
isOAuth?: boolean;
|
|
744
757
|
};
|
|
745
758
|
|
|
@@ -791,6 +804,7 @@ function buildCustomModelOverlay(
|
|
|
791
804
|
providerCompat: Model<Api>["compat"] | undefined,
|
|
792
805
|
providerRequestTransform: ModelRequestTransform | undefined,
|
|
793
806
|
providerAuth: ProviderAuthMode | undefined,
|
|
807
|
+
providerCacheRetention: CacheRetention | undefined,
|
|
794
808
|
modelDef: CustomModelDefinitionLike,
|
|
795
809
|
): CustomModelOverlay | undefined {
|
|
796
810
|
const api = modelDef.api ?? providerApi;
|
|
@@ -813,6 +827,7 @@ function buildCustomModelOverlay(
|
|
|
813
827
|
wireModelId: modelDef.wireModelId,
|
|
814
828
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
815
829
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
830
|
+
cacheRetention: modelDef.cacheRetention ?? providerCacheRetention,
|
|
816
831
|
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
817
832
|
};
|
|
818
833
|
}
|
|
@@ -913,6 +928,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
913
928
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
914
929
|
wireModelId: resolvedModel.wireModelId,
|
|
915
930
|
requestTransform: resolvedModel.requestTransform,
|
|
931
|
+
cacheRetention: resolvedModel.cacheRetention ?? reference?.cacheRetention,
|
|
916
932
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
917
933
|
isOAuth: resolvedModel.isOAuth,
|
|
918
934
|
} as Model<Api>);
|
|
@@ -1139,6 +1155,7 @@ export class ModelRegistry {
|
|
|
1139
1155
|
return {
|
|
1140
1156
|
...withTransportOverride,
|
|
1141
1157
|
compat: mergeCompat(m.compat, providerOverride.compat),
|
|
1158
|
+
cacheRetention: m.cacheRetention ?? providerOverride.cacheRetention,
|
|
1142
1159
|
};
|
|
1143
1160
|
});
|
|
1144
1161
|
});
|
|
@@ -1295,6 +1312,7 @@ export class ModelRegistry {
|
|
|
1295
1312
|
requestTransform: providerConfig.requestTransform
|
|
1296
1313
|
? mergeRequestTransform(undefined, providerConfig.requestTransform)
|
|
1297
1314
|
: undefined,
|
|
1315
|
+
cacheRetention: normalized.cacheRetention ?? providerConfig.cacheRetention,
|
|
1298
1316
|
};
|
|
1299
1317
|
});
|
|
1300
1318
|
}
|
|
@@ -1383,7 +1401,8 @@ export class ModelRegistry {
|
|
|
1383
1401
|
providerConfig.compat ||
|
|
1384
1402
|
providerConfig.disableStrictTools ||
|
|
1385
1403
|
providerConfig.requestTransform ||
|
|
1386
|
-
providerConfig.transport
|
|
1404
|
+
providerConfig.transport ||
|
|
1405
|
+
providerConfig.cacheRetention
|
|
1387
1406
|
) {
|
|
1388
1407
|
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
1389
1408
|
overrides.set(providerName, {
|
|
@@ -1394,6 +1413,7 @@ export class ModelRegistry {
|
|
|
1394
1413
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1395
1414
|
transport: providerConfig.transport,
|
|
1396
1415
|
requestTransform: providerConfig.requestTransform,
|
|
1416
|
+
cacheRetention: providerConfig.cacheRetention,
|
|
1397
1417
|
});
|
|
1398
1418
|
}
|
|
1399
1419
|
|
|
@@ -1410,6 +1430,7 @@ export class ModelRegistry {
|
|
|
1410
1430
|
headers: providerConfig.headers,
|
|
1411
1431
|
compat: providerConfig.compat,
|
|
1412
1432
|
requestTransform: providerConfig.requestTransform,
|
|
1433
|
+
cacheRetention: providerConfig.cacheRetention,
|
|
1413
1434
|
discovery: providerConfig.discovery,
|
|
1414
1435
|
optional: false,
|
|
1415
1436
|
});
|
|
@@ -2101,13 +2122,16 @@ export class ModelRegistry {
|
|
|
2101
2122
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
2102
2123
|
transport: override.transport ?? baseOverride?.transport,
|
|
2103
2124
|
requestTransform: mergeRequestTransform(baseOverride?.requestTransform, override.requestTransform),
|
|
2125
|
+
cacheRetention: override.cacheRetention ?? baseOverride?.cacheRetention,
|
|
2104
2126
|
};
|
|
2105
2127
|
}
|
|
2106
|
-
#applyProviderTransportOverride<
|
|
2128
|
+
#applyProviderTransportOverride<
|
|
2129
|
+
T extends { baseUrl?: string; headers?: Record<string, string>; cacheRetention?: CacheRetention },
|
|
2130
|
+
>(
|
|
2107
2131
|
entry: T,
|
|
2108
2132
|
override: Pick<
|
|
2109
2133
|
ProviderOverride,
|
|
2110
|
-
"baseUrl" | "headers" | "authHeader" | "apiKey" | "transport" | "requestTransform"
|
|
2134
|
+
"baseUrl" | "headers" | "authHeader" | "apiKey" | "transport" | "requestTransform" | "cacheRetention"
|
|
2111
2135
|
>,
|
|
2112
2136
|
): T {
|
|
2113
2137
|
const headers = mergeAuthHeader(
|
|
@@ -2126,6 +2150,7 @@ export class ModelRegistry {
|
|
|
2126
2150
|
(entry as { requestTransform?: ModelRequestTransform }).requestTransform,
|
|
2127
2151
|
override.requestTransform,
|
|
2128
2152
|
),
|
|
2153
|
+
cacheRetention: entry.cacheRetention ?? override.cacheRetention,
|
|
2129
2154
|
};
|
|
2130
2155
|
}
|
|
2131
2156
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
@@ -2214,6 +2239,7 @@ export class ModelRegistry {
|
|
|
2214
2239
|
providerCompat,
|
|
2215
2240
|
providerConfig.requestTransform,
|
|
2216
2241
|
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
2242
|
+
providerConfig.cacheRetention,
|
|
2217
2243
|
modelDef as CustomModelDefinitionLike,
|
|
2218
2244
|
);
|
|
2219
2245
|
if (!model) continue;
|
|
@@ -2576,6 +2602,7 @@ export class ModelRegistry {
|
|
|
2576
2602
|
config.compat,
|
|
2577
2603
|
config.requestTransform,
|
|
2578
2604
|
undefined,
|
|
2605
|
+
undefined,
|
|
2579
2606
|
modelDef as CustomModelDefinitionLike,
|
|
2580
2607
|
);
|
|
2581
2608
|
if (!overlay) {
|
|
@@ -16,6 +16,7 @@ const ReasoningEffortMapSchema = z.object({
|
|
|
16
16
|
medium: z.string().optional(),
|
|
17
17
|
high: z.string().optional(),
|
|
18
18
|
xhigh: z.string().optional(),
|
|
19
|
+
max: z.string().optional(),
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
export const OpenAICompatSchema = z.object({
|
|
@@ -45,7 +46,8 @@ export const OpenAICompatSchema = z.object({
|
|
|
45
46
|
toolStrictMode: z.enum(["all_strict", "none"]).optional(),
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh"]);
|
|
49
|
+
const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
50
|
+
const CacheRetentionSchema = z.enum(["none", "short", "long"]);
|
|
49
51
|
|
|
50
52
|
const ThinkingControlModeSchema = z.enum([
|
|
51
53
|
"effort",
|
|
@@ -89,7 +91,7 @@ function isValidProfileModelSelector(value: string): boolean {
|
|
|
89
91
|
if (parts.length > 2) return false;
|
|
90
92
|
const [base, suffix] = parts;
|
|
91
93
|
if (!base) return false;
|
|
92
|
-
return suffix === undefined || ["minimal", "low", "medium", "high", "xhigh"].includes(suffix);
|
|
94
|
+
return suffix === undefined || ["minimal", "low", "medium", "high", "xhigh", "max"].includes(suffix);
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
export const ProfileModelSelectorSchema = z
|
|
@@ -149,6 +151,7 @@ const ModelDefinitionSchema = z
|
|
|
149
151
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
150
152
|
wireModelId: z.string().min(1).optional(),
|
|
151
153
|
requestTransform: RequestTransformSchema.optional(),
|
|
154
|
+
cacheRetention: CacheRetentionSchema.optional(),
|
|
152
155
|
})
|
|
153
156
|
.strict();
|
|
154
157
|
|
|
@@ -174,6 +177,7 @@ export const ModelOverrideSchema = z
|
|
|
174
177
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
175
178
|
wireModelId: z.string().min(1).optional(),
|
|
176
179
|
requestTransform: RequestTransformSchema.optional(),
|
|
180
|
+
cacheRetention: CacheRetentionSchema.optional(),
|
|
177
181
|
})
|
|
178
182
|
.strict();
|
|
179
183
|
|
|
@@ -225,6 +229,7 @@ const ProviderConfigSchema = z
|
|
|
225
229
|
* and `apiKey` must carry the gateway bearer.
|
|
226
230
|
*/
|
|
227
231
|
transport: z.literal("pi-native").optional(),
|
|
232
|
+
cacheRetention: CacheRetentionSchema.optional(),
|
|
228
233
|
})
|
|
229
234
|
.strict();
|
|
230
235
|
|