@gajae-code/coding-agent 0.16.1 → 0.16.4
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 +70 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/cli/rows.d.ts +7 -0
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -2
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/dist/types/utils/external-editor.d.ts +5 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +94 -6
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +29 -14
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/acp/adapter.ts +1 -1
- package/src/sdk/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +44 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/rows.ts +47 -2
- package/src/sdk/cli/session-cli.ts +47 -23
- package/src/sdk/host/events.ts +25 -2
- package/src/sdk/host/host.ts +4 -1
- package/src/sdk/host/session-runtime.test.ts +36 -25
- package/src/sdk/host/session-runtime.ts +279 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +60 -37
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -30
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +50 -21
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -2
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +9 -2
- package/src/utils/external-editor.ts +16 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
package/src/config/file-lock.ts
CHANGED
|
@@ -2,8 +2,19 @@ import * as crypto from "node:crypto";
|
|
|
2
2
|
import type { BigIntStats } from "node:fs";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
5
|
+
import type {
|
|
6
|
+
NativeDirectoryTreeResult,
|
|
7
|
+
NativeDirectoryTreeSnapshot,
|
|
8
|
+
NativeExactUnlinkResult,
|
|
9
|
+
NativeNoReplaceResult,
|
|
10
|
+
RecoveryFsPublishResult,
|
|
11
|
+
} from "@gajae-code/natives";
|
|
12
|
+
import {
|
|
13
|
+
openRecoveryFsRoot,
|
|
14
|
+
renameDirectoryNoReplacePathAsync,
|
|
15
|
+
renameNoReplacePathAsync,
|
|
16
|
+
snapshotDirectoryTree,
|
|
17
|
+
} from "@gajae-code/natives";
|
|
7
18
|
import { isEnoent } from "@gajae-code/utils/fs-error";
|
|
8
19
|
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
9
20
|
|
|
@@ -397,11 +408,114 @@ export interface FileLockOwnerToken {
|
|
|
397
408
|
* another process may copy into a new lock generation.
|
|
398
409
|
*/
|
|
399
410
|
const fileLockDirIdentities = new WeakMap<object, GenericFileLockDirIdentity>();
|
|
411
|
+
const pendingDetachedLockCleanups = new WeakMap<object, { path: string; rootDev: string; rootIno: string }>();
|
|
412
|
+
|
|
413
|
+
async function finishDetachedLockCleanup(owner: FileLockOwnerToken): Promise<boolean> {
|
|
414
|
+
const pending = pendingDetachedLockCleanups.get(owner);
|
|
415
|
+
if (!pending) return false;
|
|
416
|
+
try {
|
|
417
|
+
const current = await fs.lstat(pending.path, { bigint: true });
|
|
418
|
+
if (
|
|
419
|
+
!current.isDirectory() ||
|
|
420
|
+
current.isSymbolicLink() ||
|
|
421
|
+
current.dev.toString() !== pending.rootDev ||
|
|
422
|
+
current.ino.toString() !== pending.rootIno
|
|
423
|
+
) {
|
|
424
|
+
throw new Error("Detached file lock cleanup identity changed; refusing removal");
|
|
425
|
+
}
|
|
426
|
+
await fs.rm(pending.path, { recursive: true, force: true });
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (!isEnoent(error)) throw error;
|
|
429
|
+
}
|
|
430
|
+
pendingDetachedLockCleanups.delete(owner);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
400
433
|
|
|
401
434
|
function getLockPath(filePath: string): string {
|
|
402
435
|
return `${filePath}.lock`;
|
|
403
436
|
}
|
|
404
437
|
|
|
438
|
+
function fileLockRemovalTransitionPath(lockPath: string): string {
|
|
439
|
+
return `${lockPath}.removing`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function fileLockRemovalTransitionExists(lockPath: string): Promise<boolean> {
|
|
443
|
+
try {
|
|
444
|
+
await fs.lstat(fileLockRemovalTransitionPath(lockPath));
|
|
445
|
+
return true;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (isEnoent(error)) return false;
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function sameFileLockTreeAfterPublication(
|
|
453
|
+
staged: NativeDirectoryTreeSnapshot,
|
|
454
|
+
published: NativeDirectoryTreeSnapshot,
|
|
455
|
+
): boolean {
|
|
456
|
+
return (
|
|
457
|
+
staged.rootDev === published.rootDev &&
|
|
458
|
+
staged.rootIno === published.rootIno &&
|
|
459
|
+
staged.entries.length === published.entries.length &&
|
|
460
|
+
staged.entries.every((entry, index) => {
|
|
461
|
+
const current = published.entries[index];
|
|
462
|
+
return (
|
|
463
|
+
current !== undefined &&
|
|
464
|
+
entry.relativePath === current.relativePath &&
|
|
465
|
+
entry.kind === current.kind &&
|
|
466
|
+
entry.dev === current.dev &&
|
|
467
|
+
entry.ino === current.ino &&
|
|
468
|
+
entry.nlink === current.nlink &&
|
|
469
|
+
entry.size === current.size &&
|
|
470
|
+
entry.mtimeNs === current.mtimeNs &&
|
|
471
|
+
(entry.relativePath === "" || entry.ctimeNs === current.ctimeNs) &&
|
|
472
|
+
entry.sha256 === current.sha256
|
|
473
|
+
);
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function rollbackPublishedFileLock(
|
|
479
|
+
canonicalParent: string,
|
|
480
|
+
lockPath: string,
|
|
481
|
+
pendingPath: string,
|
|
482
|
+
stagedSnapshot: NativeDirectoryTreeSnapshot,
|
|
483
|
+
): void {
|
|
484
|
+
const authority = openRecoveryFsRoot(canonicalParent);
|
|
485
|
+
let rollback: RecoveryFsPublishResult | undefined;
|
|
486
|
+
const errors: unknown[] = [];
|
|
487
|
+
try {
|
|
488
|
+
rollback = authority.renameManagedTreeNoReplace(
|
|
489
|
+
path.basename(lockPath),
|
|
490
|
+
path.basename(pendingPath),
|
|
491
|
+
stagedSnapshot,
|
|
492
|
+
);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
errors.push(error);
|
|
495
|
+
}
|
|
496
|
+
if (rollback && !rollback.ok) {
|
|
497
|
+
const failure = new Error(
|
|
498
|
+
`Failed to roll back file lock published during removal transition: ${rollback.code ?? "unknown"}.`,
|
|
499
|
+
) as NodeJS.ErrnoException;
|
|
500
|
+
if (rollback.code) failure.code = rollback.code;
|
|
501
|
+
errors.push(failure);
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const closed = authority.close();
|
|
505
|
+
if (!closed.ok && closed.code !== "closed") {
|
|
506
|
+
const failure = new Error(
|
|
507
|
+
`Failed to close file lock rollback authority: ${closed.code ?? "unknown"}.`,
|
|
508
|
+
) as NodeJS.ErrnoException;
|
|
509
|
+
if (closed.code) failure.code = closed.code;
|
|
510
|
+
errors.push(failure);
|
|
511
|
+
}
|
|
512
|
+
} catch (error) {
|
|
513
|
+
errors.push(error);
|
|
514
|
+
}
|
|
515
|
+
if (errors.length > 0) throw new AggregateError(errors, "File lock rollback authority failed");
|
|
516
|
+
if (!rollback) throw new Error("File lock rollback returned no result");
|
|
517
|
+
}
|
|
518
|
+
|
|
405
519
|
async function ensureLockParent(directory: string): Promise<void> {
|
|
406
520
|
const missing: string[] = [];
|
|
407
521
|
let current = path.resolve(directory);
|
|
@@ -704,17 +818,12 @@ export async function removeFileLockDirForGc(
|
|
|
704
818
|
removed.retainedPlaceholderPath === undefined &&
|
|
705
819
|
removed.retainedUnknownPath === undefined;
|
|
706
820
|
if (verifiedDetach && detachedPath !== undefined) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
await fs.rm(detachedPath, { recursive: true, force: true });
|
|
714
|
-
} catch {
|
|
715
|
-
// The canonical pathname is free either way; a retained quarantine is
|
|
716
|
-
// recoverable debris, not a live lock.
|
|
717
|
-
}
|
|
821
|
+
pendingDetachedLockCleanups.set(expected, {
|
|
822
|
+
path: detachedPath,
|
|
823
|
+
rootDev: captured.snapshot.rootDev,
|
|
824
|
+
rootIno: captured.snapshot.rootIno,
|
|
825
|
+
});
|
|
826
|
+
await finishDetachedLockCleanup(expected);
|
|
718
827
|
return "removed";
|
|
719
828
|
}
|
|
720
829
|
if (removed.code === "not_found") return "removed";
|
|
@@ -880,8 +989,10 @@ async function tryAcquireLock(
|
|
|
880
989
|
if (afterParentMkdir) await afterParentMkdir(lockPath);
|
|
881
990
|
const pendingPath = `${lockPath}.pending.${process.pid}.${crypto.randomUUID()}`;
|
|
882
991
|
const owner = lockInfo(ownerHostId, ownerToken);
|
|
992
|
+
let removePending = false;
|
|
883
993
|
try {
|
|
884
994
|
await fs.mkdir(pendingPath, { mode: 0o700 });
|
|
995
|
+
removePending = true;
|
|
885
996
|
await fs.chmod(pendingPath, 0o700);
|
|
886
997
|
await writeLockInfo(pendingPath, owner);
|
|
887
998
|
// A plain POSIX rename replaces an existing empty directory. The legacy
|
|
@@ -896,6 +1007,22 @@ async function tryAcquireLock(
|
|
|
896
1007
|
// component would follow an attacker-replaced symlink before native no-follow
|
|
897
1008
|
// validation gets a chance to reject it.
|
|
898
1009
|
const canonicalPendingPath = path.join(canonicalParent, path.basename(pendingPath));
|
|
1010
|
+
let stagedSnapshot: NativeDirectoryTreeSnapshot | undefined;
|
|
1011
|
+
if (process.platform === "linux") {
|
|
1012
|
+
// Linux exact tree removal owns this deterministic sibling from detach
|
|
1013
|
+
// until cleanup. The outer acquisition loop supplies the existing bounded
|
|
1014
|
+
// contention wait while the predecessor retains that namespace.
|
|
1015
|
+
if (await fileLockRemovalTransitionExists(destinationPath)) return null;
|
|
1016
|
+
const staged = snapshotDirectoryTree(canonicalPendingPath);
|
|
1017
|
+
if (!staged.ok || !staged.snapshot) {
|
|
1018
|
+
const failure = new Error(
|
|
1019
|
+
`Failed to snapshot staged file lock: ${staged.code ?? "unknown"}.`,
|
|
1020
|
+
) as NodeJS.ErrnoException;
|
|
1021
|
+
if (staged.code) failure.code = staged.code;
|
|
1022
|
+
throw failure;
|
|
1023
|
+
}
|
|
1024
|
+
stagedSnapshot = staged.snapshot;
|
|
1025
|
+
}
|
|
899
1026
|
const publication = FileLockTestHooks.nativePublicationBindings?.() ?? {
|
|
900
1027
|
renameNoReplacePathAsync,
|
|
901
1028
|
renameDirectoryNoReplacePathAsync,
|
|
@@ -924,12 +1051,34 @@ async function tryAcquireLock(
|
|
|
924
1051
|
if (published.code) failure.code = published.code;
|
|
925
1052
|
throw failure;
|
|
926
1053
|
}
|
|
927
|
-
|
|
928
|
-
//
|
|
1054
|
+
removePending = false;
|
|
1055
|
+
// The transition can appear after the pre-publication check only when the
|
|
1056
|
+
// predecessor atomically detaches its lock before this no-replace publish.
|
|
1057
|
+
// Roll this exact staged tree back to its UUID path before reporting
|
|
1058
|
+
// contention; never delete or rename the predecessor's `.removing` tree.
|
|
1059
|
+
if (stagedSnapshot && (await fileLockRemovalTransitionExists(destinationPath))) {
|
|
1060
|
+
const publishedSnapshot = snapshotDirectoryTree(destinationPath);
|
|
1061
|
+
if (
|
|
1062
|
+
!publishedSnapshot.ok ||
|
|
1063
|
+
!publishedSnapshot.snapshot ||
|
|
1064
|
+
!sameFileLockTreeAfterPublication(stagedSnapshot, publishedSnapshot.snapshot)
|
|
1065
|
+
) {
|
|
1066
|
+
const failure = new Error(
|
|
1067
|
+
`Failed to verify published file lock before transition rollback: ${publishedSnapshot.code ?? "identity_mismatch"}.`,
|
|
1068
|
+
) as NodeJS.ErrnoException;
|
|
1069
|
+
if (publishedSnapshot.code) failure.code = publishedSnapshot.code;
|
|
1070
|
+
throw failure;
|
|
1071
|
+
}
|
|
1072
|
+
rollbackPublishedFileLock(canonicalParent, destinationPath, canonicalPendingPath, publishedSnapshot.snapshot);
|
|
1073
|
+
removePending = true;
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
// Published and transition-free above, so an onAcquired failure must
|
|
1077
|
+
// propagate instead of retrying an acquisition that already owns the lock.
|
|
929
1078
|
onAcquired?.();
|
|
930
1079
|
return owner;
|
|
931
1080
|
} finally {
|
|
932
|
-
await fs.rm(pendingPath, { recursive: true, force: true }).catch(() => undefined);
|
|
1081
|
+
if (removePending) await fs.rm(pendingPath, { recursive: true, force: true }).catch(() => undefined);
|
|
933
1082
|
}
|
|
934
1083
|
}
|
|
935
1084
|
|
|
@@ -1025,7 +1174,12 @@ async function quarantineReleasedLock(
|
|
|
1025
1174
|
return false;
|
|
1026
1175
|
} catch (error) {
|
|
1027
1176
|
if (isEnoent(error)) {
|
|
1028
|
-
|
|
1177
|
+
pendingDetachedLockCleanups.set(owner, {
|
|
1178
|
+
path: removed.detachedPath,
|
|
1179
|
+
rootDev: captured.snapshot.rootDev,
|
|
1180
|
+
rootIno: captured.snapshot.rootIno,
|
|
1181
|
+
});
|
|
1182
|
+
await finishDetachedLockCleanup(owner);
|
|
1029
1183
|
return true;
|
|
1030
1184
|
}
|
|
1031
1185
|
throw error;
|
|
@@ -1035,6 +1189,7 @@ async function quarantineReleasedLock(
|
|
|
1035
1189
|
}
|
|
1036
1190
|
|
|
1037
1191
|
async function releaseOwnedLock(lockPath: string, owner: FileLockOwnerToken): Promise<void> {
|
|
1192
|
+
if (await finishDetachedLockCleanup(owner)) return;
|
|
1038
1193
|
let preVerdictIdentity: GenericFileLockDirIdentity | undefined;
|
|
1039
1194
|
try {
|
|
1040
1195
|
preVerdictIdentity = (await captureFileLockDirIdentity(lockPath)) ?? undefined;
|
|
@@ -1045,6 +1200,7 @@ async function releaseOwnedLock(lockPath: string, owner: FileLockOwnerToken): Pr
|
|
|
1045
1200
|
let lastTransientError: unknown;
|
|
1046
1201
|
for (let attempt = 0; attempt < FILE_LOCK_RELEASE_RETRY_ATTEMPTS; attempt++) {
|
|
1047
1202
|
try {
|
|
1203
|
+
if (await finishDetachedLockCleanup(owner)) return;
|
|
1048
1204
|
const outcome = await removeFileLockDirForGc(lockPath, owner, preVerdictIdentity);
|
|
1049
1205
|
if (outcome === "removed" || outcome === "missing") {
|
|
1050
1206
|
if (outcome === "missing") throw new Error("Failed to release file lock: missing.");
|
|
@@ -1134,6 +1290,9 @@ async function releaseLock(lockPath: string, owner: FileLockOwnerToken, knownKey
|
|
|
1134
1290
|
*/
|
|
1135
1291
|
async function lockHolderDescription(lockPath: string): Promise<string> {
|
|
1136
1292
|
try {
|
|
1293
|
+
if (process.platform === "linux" && (await fileLockRemovalTransitionExists(lockPath))) {
|
|
1294
|
+
return "blocked by retained removal transition; retry the owning process cleanup or inspect the exact orphan manually; unproven transition ownership is never removed";
|
|
1295
|
+
}
|
|
1137
1296
|
const info = await readLockInfo(lockPath);
|
|
1138
1297
|
if (info) {
|
|
1139
1298
|
// A lock record carrying a foreign owner_host_id belongs to another
|
|
@@ -1190,6 +1349,11 @@ async function acquireLock(filePath: string, options: FileLockOptions = {}): Pro
|
|
|
1190
1349
|
for (let attempt = 0; attempt < opts.retries; attempt++) {
|
|
1191
1350
|
if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("File lock acquisition aborted");
|
|
1192
1351
|
const localKey = await localLockKey(lockPath);
|
|
1352
|
+
const priorRelease = localLockStates.get(localKey);
|
|
1353
|
+
if (priorRelease && priorRelease.status !== "held" && pendingDetachedLockCleanups.has(priorRelease.owner)) {
|
|
1354
|
+
await retryPendingLocalRelease(lockPath, localKey);
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1193
1357
|
const owner = await tryAcquireLock(lockPath, opts.ownerHostId, ownerToken, opts.onAcquired);
|
|
1194
1358
|
if (owner) {
|
|
1195
1359
|
localLockStates.set(localKey, { owner, status: "held" });
|
|
@@ -4,7 +4,7 @@ import * as fs from "node:fs/promises";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { Api, Model } from "@gajae-code/ai/core";
|
|
6
6
|
import type { NativeExactFileIdentity, NativeExactUnlinkResult } from "@gajae-code/natives";
|
|
7
|
-
import { getAgentDir, isEnoent } from "@gajae-code/utils";
|
|
7
|
+
import { $credentialEnv, getAgentDir, isEnoent } from "@gajae-code/utils";
|
|
8
8
|
import * as z from "zod/v4";
|
|
9
9
|
import { splitSelectorThinkingSuffix } from "../thinking";
|
|
10
10
|
import { withFileLock } from "./file-lock";
|
|
@@ -1444,10 +1444,12 @@ function effectiveTrustedKeys(
|
|
|
1444
1444
|
return getModelPresetRegistryTestTrustedKeys(agentDir) ?? MODEL_PRESET_REGISTRY_TRUSTED_KEYS;
|
|
1445
1445
|
}
|
|
1446
1446
|
function effectiveManifestUrl(dependencies: ModelPresetRegistryDependencies): string {
|
|
1447
|
-
return
|
|
1447
|
+
return (
|
|
1448
|
+
dependencies.manifestUrl ?? $credentialEnv("GJC_MODEL_PRESET_REGISTRY_URL") ?? DEFAULT_MODEL_PRESET_REGISTRY_URL
|
|
1449
|
+
);
|
|
1448
1450
|
}
|
|
1449
1451
|
function environmentDisabled(): boolean {
|
|
1450
|
-
return /^(?:1|true|yes|on)$/i.test(
|
|
1452
|
+
return /^(?:1|true|yes|on)$/i.test($credentialEnv("GJC_MODEL_PRESET_REGISTRY_DISABLED") ?? "");
|
|
1451
1453
|
}
|
|
1452
1454
|
function assertHttpsUrl(raw: string, description: string): URL {
|
|
1453
1455
|
let url: URL;
|
|
@@ -100,6 +100,27 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
100
100
|
critic: "openai-codex/gpt-5.6-luna:max",
|
|
101
101
|
architect: "openai-codex/gpt-5.6-luna:max",
|
|
102
102
|
}),
|
|
103
|
+
profile("astra-lite", ["openai-codex"], {
|
|
104
|
+
default: "openai-codex/gpt-6-astra:medium",
|
|
105
|
+
executor: "openai-codex/gpt-5.6-luna:xhigh",
|
|
106
|
+
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
107
|
+
critic: "openai-codex/gpt-5.6-sol:high",
|
|
108
|
+
architect: "openai-codex/gpt-5.6-terra:xhigh",
|
|
109
|
+
}),
|
|
110
|
+
profile("astra-default", ["openai-codex"], {
|
|
111
|
+
default: "openai-codex/gpt-6-astra:medium",
|
|
112
|
+
executor: "openai-codex/gpt-5.6-luna:max",
|
|
113
|
+
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
114
|
+
critic: "openai-codex/gpt-5.6-sol:xhigh",
|
|
115
|
+
architect: "openai-codex/gpt-5.6-terra:xhigh",
|
|
116
|
+
}),
|
|
117
|
+
profile("astra-heavy", ["openai-codex"], {
|
|
118
|
+
default: "openai-codex/gpt-6-astra:medium",
|
|
119
|
+
executor: "openai-codex/gpt-5.6-sol:max",
|
|
120
|
+
planner: "openai-codex/gpt-5.6-sol:max",
|
|
121
|
+
critic: "openai-codex/gpt-6-astra:max",
|
|
122
|
+
architect: "openai-codex/gpt-6-astra:max",
|
|
123
|
+
}),
|
|
103
124
|
profile("macos-omlx-fast", ["omlx"], {
|
|
104
125
|
default: "omlx/Qwen3.6-35B-A3B-4bit:low",
|
|
105
126
|
executor: "omlx/Qwen3.6-35B-A3B-4bit:low",
|
|
@@ -488,6 +509,20 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
488
509
|
critic: "anthropic/claude-opus-5:high",
|
|
489
510
|
architect: "openai-codex/gpt-5.6-sol:xhigh",
|
|
490
511
|
}),
|
|
512
|
+
profile("astra-fable", ["openai-codex", "anthropic"], {
|
|
513
|
+
default: "openai-codex/gpt-6-astra:medium",
|
|
514
|
+
executor: "openai-codex/gpt-5.6-luna:max",
|
|
515
|
+
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
516
|
+
critic: "anthropic/claude-fable-5-1:xhigh",
|
|
517
|
+
architect: "anthropic/claude-fable-5-1:xhigh",
|
|
518
|
+
}),
|
|
519
|
+
profile("astra-fable-opus", ["openai-codex", "anthropic"], {
|
|
520
|
+
default: "openai-codex/gpt-6-astra:medium",
|
|
521
|
+
executor: "openai-codex/gpt-5.6-luna:max",
|
|
522
|
+
planner: "anthropic/claude-opus-5:medium",
|
|
523
|
+
critic: "anthropic/claude-fable-5-1:xhigh",
|
|
524
|
+
architect: "anthropic/claude-fable-5-1:xhigh",
|
|
525
|
+
}),
|
|
491
526
|
];
|
|
492
527
|
|
|
493
528
|
/**
|
|
@@ -521,6 +556,9 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
521
556
|
"codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
|
|
522
557
|
"codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
|
|
523
558
|
lunamaxxing: { displayName: "LunaMaxxing", providerGroup: "CODEX" },
|
|
559
|
+
"astra-lite": { displayName: "ASTRA-Lite", providerGroup: "CODEX" },
|
|
560
|
+
"astra-default": { displayName: "ASTRA-Default", providerGroup: "CODEX" },
|
|
561
|
+
"astra-heavy": { displayName: "ASTRA-Heavy", providerGroup: "CODEX" },
|
|
524
562
|
opencodego: { displayName: "OpenCodeGo", providerGroup: "OPENCODEGO" },
|
|
525
563
|
"commandcode-goat": { displayName: "Command Code GOAT", providerGroup: "COMMAND CODE GOAT" },
|
|
526
564
|
"open-weights-glm": { displayName: "GLM", providerGroup: "OPEN WEIGHT MODELS (PROVIDER AGNOSTIC)" },
|
|
@@ -594,6 +632,8 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
594
632
|
"opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
|
|
595
633
|
"codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
|
|
596
634
|
"fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
|
|
635
|
+
"astra-fable": { displayName: "ASTRA + Fable (5.1)", providerGroup: "COMBOS" },
|
|
636
|
+
"astra-fable-opus": { displayName: "ASTRA + Fable (5.1) + Opus", providerGroup: "COMBOS" },
|
|
597
637
|
"macos-omlx-fast": { displayName: "4-bit Fast (MoE measured 93.5 tok/s)", providerGroup: "macOS Local (oMLX)" },
|
|
598
638
|
"macos-omlx-balanced": {
|
|
599
639
|
displayName: "8-bit Balanced (MoE measured 71.1 tok/s)",
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
unregisterCustomApis,
|
|
41
41
|
} from "@gajae-code/ai/core";
|
|
42
42
|
import { fetchModelsDevPayload } from "@gajae-code/ai/provider-models/openai-compat";
|
|
43
|
+
import { isDirectXaiReasoningEffortModel } from "@gajae-code/ai/providers/openai-completions-compat";
|
|
43
44
|
import {
|
|
44
45
|
detectDiscoveredApiFamily,
|
|
45
46
|
resolveLoopbackOpenAIBaseUrl,
|
|
@@ -279,11 +280,7 @@ export const GJC_MODEL_ASSIGNMENT_TARGETS: Record<GjcModelAssignmentTargetId, Gj
|
|
|
279
280
|
|
|
280
281
|
export function requiresExplicitThinkingChoice(model: Model, role: GjcModelAssignmentTargetId | null): boolean {
|
|
281
282
|
if (!modelSupportsReasoningControl(model)) return false;
|
|
282
|
-
if (
|
|
283
|
-
model.provider === "openai" ||
|
|
284
|
-
model.provider === "openai-codex" ||
|
|
285
|
-
(model.provider === "xai" && (model.id === "grok-4.5" || model.id === "grok-4.6"))
|
|
286
|
-
)
|
|
283
|
+
if (model.provider === "openai" || model.provider === "openai-codex" || isDirectXaiReasoningEffortModel(model))
|
|
287
284
|
return true;
|
|
288
285
|
if (role === null) return false;
|
|
289
286
|
if (role === "default") return true;
|
|
@@ -1835,6 +1832,25 @@ export class ModelRegistry {
|
|
|
1835
1832
|
/**
|
|
1836
1833
|
* Reload models from disk (embedded + accepted registry + custom from models.yml).
|
|
1837
1834
|
*/
|
|
1835
|
+
async refreshStatic(): Promise<void> {
|
|
1836
|
+
if (this.#disposed) return;
|
|
1837
|
+
await this.#enqueueCatalogMutation(async () => {
|
|
1838
|
+
if (this.#disposed) return;
|
|
1839
|
+
this.#catalogRefreshGeneration++;
|
|
1840
|
+
this.#suspendRebuild();
|
|
1841
|
+
try {
|
|
1842
|
+
this.#reloadStaticModels();
|
|
1843
|
+
this.#suppressedSelectors.clear();
|
|
1844
|
+
this.#modelBindingsApplier.apply();
|
|
1845
|
+
} finally {
|
|
1846
|
+
this.#resumeRebuild();
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
/**
|
|
1852
|
+
* Reload models from disk and refresh runtime provider discovery state.
|
|
1853
|
+
*/
|
|
1838
1854
|
async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
1839
1855
|
if (this.#disposed) return;
|
|
1840
1856
|
await this.#enqueueCatalogMutation(async () => {
|
|
@@ -1882,7 +1898,8 @@ export class ModelRegistry {
|
|
|
1882
1898
|
const refreshGeneration = this.#catalogRefreshGeneration;
|
|
1883
1899
|
this.#suspendRebuild();
|
|
1884
1900
|
try {
|
|
1885
|
-
this
|
|
1901
|
+
// A provider refresh only updates discovery for this provider.
|
|
1902
|
+
// Static catalog changes are serialized through refresh().
|
|
1886
1903
|
for (const selector of this.#suppressedSelectors.keys()) {
|
|
1887
1904
|
if (selector.startsWith(`${providerId}/`)) {
|
|
1888
1905
|
this.#suppressedSelectors.delete(selector);
|
|
@@ -3026,6 +3043,7 @@ export class ModelRegistry {
|
|
|
3026
3043
|
refreshGeneration = this.#catalogRefreshGeneration,
|
|
3027
3044
|
providerRefresh?: ProviderRefreshFence,
|
|
3028
3045
|
): Promise<void> {
|
|
3046
|
+
const profileAvailabilityEvidenceBefore = this.#profileAvailabilityEvidenceFingerprint();
|
|
3029
3047
|
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
3030
3048
|
const selectedDiscoverableProviders = (
|
|
3031
3049
|
providerFilter
|
|
@@ -3232,7 +3250,10 @@ export class ModelRegistry {
|
|
|
3232
3250
|
});
|
|
3233
3251
|
}
|
|
3234
3252
|
if (discovered.length === 0) {
|
|
3235
|
-
if (
|
|
3253
|
+
if (
|
|
3254
|
+
clearPublishedModelIds.size > 0 ||
|
|
3255
|
+
profileAvailabilityEvidenceBefore !== this.#profileAvailabilityEvidenceFingerprint()
|
|
3256
|
+
) {
|
|
3236
3257
|
this.#rebuildCanonicalIndex();
|
|
3237
3258
|
}
|
|
3238
3259
|
return;
|
|
@@ -3240,6 +3261,23 @@ export class ModelRegistry {
|
|
|
3240
3261
|
this.#mergeDiscoveredModels(discovered);
|
|
3241
3262
|
}
|
|
3242
3263
|
|
|
3264
|
+
#profileAvailabilityEvidenceFingerprint(): string {
|
|
3265
|
+
return JSON.stringify(
|
|
3266
|
+
[...this.#descriptorDiscoveryEvidence.entries()]
|
|
3267
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
3268
|
+
.map(([provider, evidence]) => [
|
|
3269
|
+
provider,
|
|
3270
|
+
evidence.fresh,
|
|
3271
|
+
evidence.profileFresh,
|
|
3272
|
+
evidence.authGeneration,
|
|
3273
|
+
evidence.endpoint,
|
|
3274
|
+
evidence.profileEndpoint,
|
|
3275
|
+
[...evidence.modelIds].sort(),
|
|
3276
|
+
evidence.profileModelIds === undefined ? undefined : [...evidence.profileModelIds].sort(),
|
|
3277
|
+
]),
|
|
3278
|
+
);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3243
3281
|
#mergeDiscoveredModels(discovered: readonly Model<Api>[]): void {
|
|
3244
3282
|
const discoveredModels = this.#applyHardcodedModelPolicies(
|
|
3245
3283
|
discovered.map(model =>
|
|
@@ -3537,8 +3575,9 @@ export class ModelRegistry {
|
|
|
3537
3575
|
// through discovery.type.
|
|
3538
3576
|
if (this.#configuredProviderIds.has("vllm")) configuredDiscoveryProviders.add("vllm");
|
|
3539
3577
|
if (this.#configuredProviderIds.has("sglang")) configuredDiscoveryProviders.add("sglang");
|
|
3540
|
-
const managerOptions =
|
|
3541
|
-
|
|
3578
|
+
const managerOptions = await this.#collectBuiltInModelManagerOptions(
|
|
3579
|
+
configuredDiscoveryProviders,
|
|
3580
|
+
providerFilter,
|
|
3542
3581
|
);
|
|
3543
3582
|
if (managerOptions.length === 0) {
|
|
3544
3583
|
return [];
|
|
@@ -3551,6 +3590,7 @@ export class ModelRegistry {
|
|
|
3551
3590
|
|
|
3552
3591
|
async #collectBuiltInModelManagerOptions(
|
|
3553
3592
|
excludedProviderIds: ReadonlySet<string> = new Set(),
|
|
3593
|
+
providerFilter?: ReadonlySet<string>,
|
|
3554
3594
|
): Promise<ModelManagerDiscoveryOptions[]> {
|
|
3555
3595
|
const specialProviderDescriptors: Array<{
|
|
3556
3596
|
providerId: string;
|
|
@@ -3594,10 +3634,16 @@ export class ModelRegistry {
|
|
|
3594
3634
|
];
|
|
3595
3635
|
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
3596
3636
|
const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(
|
|
3597
|
-
descriptor =>
|
|
3637
|
+
descriptor =>
|
|
3638
|
+
!disabledProviders.has(descriptor.providerId) &&
|
|
3639
|
+
!excludedProviderIds.has(descriptor.providerId) &&
|
|
3640
|
+
(providerFilter === undefined || providerFilter.has(descriptor.providerId)),
|
|
3598
3641
|
);
|
|
3599
3642
|
const enabledSpecialProviderDescriptors = specialProviderDescriptors.filter(
|
|
3600
|
-
descriptor =>
|
|
3643
|
+
descriptor =>
|
|
3644
|
+
!disabledProviders.has(descriptor.providerId) &&
|
|
3645
|
+
!excludedProviderIds.has(descriptor.providerId) &&
|
|
3646
|
+
(providerFilter === undefined || providerFilter.has(descriptor.providerId)),
|
|
3601
3647
|
);
|
|
3602
3648
|
for (const descriptor of standardProviderDescriptors) {
|
|
3603
3649
|
if (!descriptor.allowUnauthenticated) continue;
|
|
@@ -62,6 +62,54 @@ export function parseModelString(
|
|
|
62
62
|
: { provider, id: modelStr.slice(slashIdx + 1) };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
export function resolveStartupModelRefreshSelectors(
|
|
66
|
+
options: {
|
|
67
|
+
model?: string;
|
|
68
|
+
provider?: string;
|
|
69
|
+
credential?: string;
|
|
70
|
+
continue?: boolean;
|
|
71
|
+
resume?: string | boolean;
|
|
72
|
+
hasStartupProfile: boolean;
|
|
73
|
+
},
|
|
74
|
+
settings: ModelRoleSettings,
|
|
75
|
+
): ModelSelectorValue | undefined {
|
|
76
|
+
if (options.credential) return undefined;
|
|
77
|
+
if (options.model) {
|
|
78
|
+
return options.provider && !options.model.toLowerCase().startsWith(`${options.provider.toLowerCase()}/`)
|
|
79
|
+
? `${options.provider}/${options.model}`
|
|
80
|
+
: options.model;
|
|
81
|
+
}
|
|
82
|
+
if (options.hasStartupProfile) return undefined;
|
|
83
|
+
if (options.continue || options.resume) return undefined;
|
|
84
|
+
return resolveConfiguredModelPatterns(settings.getModelRole("default"), settings);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function refreshMissingQualifiedModelProviders(
|
|
88
|
+
selectors: ModelSelectorValue | undefined,
|
|
89
|
+
modelRegistry: Pick<ModelRegistry, "getAvailable" | "getDiscoverableProviders" | "refreshProvider">,
|
|
90
|
+
): Promise<boolean> {
|
|
91
|
+
const refreshedProviders = new Set<string>();
|
|
92
|
+
for (const selector of normalizeModelSelectorValue(selectors)) {
|
|
93
|
+
const parsedSelector = parseModelString(selector);
|
|
94
|
+
if (!parsedSelector) continue;
|
|
95
|
+
const provider = modelRegistry
|
|
96
|
+
.getDiscoverableProviders()
|
|
97
|
+
.find(candidate => candidate.toLowerCase() === parsedSelector.provider.toLowerCase());
|
|
98
|
+
if (!provider || refreshedProviders.has(provider)) continue;
|
|
99
|
+
const availableModels = modelRegistry.getAvailable();
|
|
100
|
+
const rawModelId = selector.slice(selector.indexOf("/") + 1);
|
|
101
|
+
if (
|
|
102
|
+
resolveProviderModelReference(provider, rawModelId, availableModels) ||
|
|
103
|
+
resolveProviderModelReference(provider, parsedSelector.id, availableModels)
|
|
104
|
+
)
|
|
105
|
+
continue;
|
|
106
|
+
|
|
107
|
+
await modelRegistry.refreshProvider(provider, "online-if-uncached");
|
|
108
|
+
refreshedProviders.add(provider);
|
|
109
|
+
}
|
|
110
|
+
return refreshedProviders.size > 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
65
113
|
/**
|
|
66
114
|
* Format a model as "provider/modelId" string.
|
|
67
115
|
*/
|
|
@@ -111,7 +111,8 @@ export type StatusLineSegmentId =
|
|
|
111
111
|
| "cache_read"
|
|
112
112
|
| "cache_write"
|
|
113
113
|
| "session_name"
|
|
114
|
-
| "usage"
|
|
114
|
+
| "usage"
|
|
115
|
+
| "command";
|
|
115
116
|
|
|
116
117
|
/** Submenu choice metadata. */
|
|
117
118
|
export type SubmenuOption<V extends string = string> = {
|
|
@@ -1242,6 +1243,26 @@ export const SETTINGS_SCHEMA = {
|
|
|
1242
1243
|
},
|
|
1243
1244
|
},
|
|
1244
1245
|
|
|
1246
|
+
reasoningLanguage: {
|
|
1247
|
+
type: "enum",
|
|
1248
|
+
values: ["off", "english"] as const,
|
|
1249
|
+
default: "off",
|
|
1250
|
+
ui: {
|
|
1251
|
+
tab: "model",
|
|
1252
|
+
label: "Reasoning Language",
|
|
1253
|
+
description:
|
|
1254
|
+
"Optionally reason through development work in English while preserving the user's response language",
|
|
1255
|
+
options: [
|
|
1256
|
+
{ value: "off", label: "Off", description: "Do not add reasoning-language guidance" },
|
|
1257
|
+
{
|
|
1258
|
+
value: "english",
|
|
1259
|
+
label: "English",
|
|
1260
|
+
description: "Reason through technical work in English and answer in the user's language",
|
|
1261
|
+
},
|
|
1262
|
+
],
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
|
|
1245
1266
|
hideThinkingBlock: {
|
|
1246
1267
|
type: "boolean",
|
|
1247
1268
|
default: false,
|
|
@@ -1749,6 +1770,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
1749
1770
|
},
|
|
1750
1771
|
},
|
|
1751
1772
|
|
|
1773
|
+
"paseo.autoImport": {
|
|
1774
|
+
type: "boolean",
|
|
1775
|
+
default: false,
|
|
1776
|
+
ui: {
|
|
1777
|
+
tab: "interaction",
|
|
1778
|
+
label: "Announce Sessions to Paseo",
|
|
1779
|
+
description:
|
|
1780
|
+
"Global-only operator opt-in. When enabled, and only when a GJC ACP provider is already registered in Paseo and its daemon is already running, import each interactive session into Paseo so it can be driven from there. Project settings cannot enable it. Never starts the Paseo daemon or removes imported entries; stale entries must be pruned in Paseo. Never runs when Paseo is absent.",
|
|
1781
|
+
},
|
|
1782
|
+
},
|
|
1783
|
+
|
|
1752
1784
|
"crashReport.nudge": {
|
|
1753
1785
|
type: "boolean",
|
|
1754
1786
|
default: true,
|
package/src/config/settings.ts
CHANGED
|
@@ -333,6 +333,7 @@ const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disab
|
|
|
333
333
|
const GLOBAL_ONLY_SETTINGS = new Set<SettingPath>([
|
|
334
334
|
"crashReport.upstream",
|
|
335
335
|
"crashReport.upstreamDsn",
|
|
336
|
+
"paseo.autoImport",
|
|
336
337
|
"ui.language",
|
|
337
338
|
"telemetry.enabled",
|
|
338
339
|
]);
|
|
@@ -73,6 +73,7 @@ const FALLBACK_MODELS: GrokCliModelConfig[] = [
|
|
|
73
73
|
cost: COST_43,
|
|
74
74
|
contextWindow: 1_000_000,
|
|
75
75
|
maxTokens: 30_000,
|
|
76
|
+
maxReasoningEffort: Effort.High,
|
|
76
77
|
},
|
|
77
78
|
{
|
|
78
79
|
// Official metadata and pricing: https://docs.x.ai/developers/models/grok-4.6
|
|
@@ -158,23 +159,22 @@ function matchesModelFamily(name: string, modelId: string): boolean {
|
|
|
158
159
|
return name.startsWith(`${modelId}-`) && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(suffix);
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
const REASONING_EFFORT_CAPS = [
|
|
163
|
+
{ prefix: 'grok-3-mini', max: Effort.High },
|
|
164
|
+
{ prefix: 'grok-4.3', max: Effort.High },
|
|
165
|
+
{ prefix: 'grok-4.5', max: Effort.High },
|
|
166
|
+
{ prefix: 'grok-4.6', max: Effort.XHigh },
|
|
167
|
+
{ prefix: 'grok-4.20-multi-agent', max: Effort.XHigh },
|
|
168
|
+
] as const;
|
|
169
|
+
|
|
161
170
|
export function getMaxReasoningEffort(modelId: string): Effort | undefined {
|
|
162
171
|
const name = getCanonicalModelName(modelId);
|
|
163
|
-
return
|
|
164
|
-
?.maxReasoningEffort;
|
|
172
|
+
return REASONING_EFFORT_CAPS.find(({ prefix }) => matchesModelFamily(name, prefix))?.max;
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
const EFFORT_CAPABLE_PREFIXES = [
|
|
168
|
-
'grok-3-mini',
|
|
169
|
-
'grok-4.20-multi-agent',
|
|
170
|
-
'grok-4.3',
|
|
171
|
-
'grok-4.5',
|
|
172
|
-
'grok-4.6',
|
|
173
|
-
];
|
|
174
|
-
|
|
175
175
|
export function supportsReasoningEffort(modelId: string): boolean {
|
|
176
176
|
const name = getCanonicalModelName(modelId);
|
|
177
|
-
if (!
|
|
177
|
+
if (!REASONING_EFFORT_CAPS.some(({ prefix }) => matchesModelFamily(name, prefix))) {
|
|
178
178
|
return false;
|
|
179
179
|
}
|
|
180
180
|
const model = resolveModels().find((entry) => getCanonicalModelName(entry.id) === name);
|