@gajae-code/coding-agent 0.18.5 → 0.18.6
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 +101 -0
- package/dist/types/cli-commands.d.ts +10 -0
- package/dist/types/cli-ordinary.d.ts +13 -0
- package/dist/types/cli.d.ts +12 -3
- package/dist/types/commands/contribution-prep.d.ts +0 -3
- package/dist/types/commands/sdk.d.ts +3 -0
- package/dist/types/commands/update.d.ts +1 -0
- package/dist/types/config/model-profile-activation.d.ts +19 -0
- package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
- package/dist/types/edit/modes/replace.d.ts +3 -3
- package/dist/types/edit/streaming.d.ts +2 -2
- package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
- package/dist/types/sdk/broker/discovery.d.ts +8 -0
- package/dist/types/sdk/broker/ensure.d.ts +1 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +1 -0
- package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
- package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +5 -1
- package/dist/types/tools/ask-contract.d.ts +75 -254
- package/dist/types/tools/bash.d.ts +1 -1
- package/package.json +7 -7
- package/scripts/build-sdk-package-smoke.ts +5 -0
- package/src/cli/public-command-entry.ts +5 -0
- package/src/cli/ssh-cli.ts +6 -3
- package/src/cli/web-search-cli.ts +9 -3
- package/src/cli-commands.ts +14 -0
- package/src/cli-ordinary.ts +197 -0
- package/src/cli.ts +25 -182
- package/src/commands/contribution-prep.ts +1 -3
- package/src/commands/sdk.ts +59 -0
- package/src/commands/update.ts +15 -1
- package/src/config/file-lock.ts +91 -2
- package/src/config/model-profile-activation.ts +70 -4
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +23 -31
- package/src/config/model-resolver.ts +9 -1
- package/src/coordinator-mcp/event-webhook.ts +19 -14
- package/src/coordinator-mcp/policy.ts +14 -4
- package/src/coordinator-mcp/server.ts +142 -7
- package/src/coordinator-mcp/session-reaper.ts +9 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/edit/modes/replace.ts +6 -6
- package/src/edit/streaming.ts +15 -15
- package/src/exec/bash-shell-supervisor.ts +60 -3
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/memories/index.ts +8 -2
- package/src/modes/acp/acp-agent.ts +649 -367
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/prompts/tools/ask.md +2 -0
- package/src/sdk/broker/broker.ts +46 -0
- package/src/sdk/broker/daemon-entry.ts +67 -6
- package/src/sdk/broker/discovery.ts +8 -0
- package/src/sdk/broker/ensure.ts +156 -20
- package/src/sdk/broker/lifecycle-ledger.ts +110 -12
- package/src/sdk/broker/lifecycle.ts +225 -341
- package/src/sdk/broker/runtime.ts +5 -1
- package/src/sdk/broker/transport.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +15 -12
- package/src/sdk/bus/telegram-daemon.ts +34 -1
- package/src/sdk/cli/diagnostics-cli.ts +247 -0
- package/src/sdk/diagnostics/observe-broker.ts +654 -0
- package/src/sdk/host/control/dispatch.ts +52 -4
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +40 -2
- package/src/sdk/host/session-runtime.ts +13 -5
- package/src/sdk/index.ts +1 -0
- package/src/sdk/lifecycle-session.ts +1 -0
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/router/session-router.ts +7 -1
- package/src/sdk/session.ts +37 -8
- package/src/session/agent-session.ts +114 -39
- package/src/session/blob-store.ts +78 -33
- package/src/session/internal/managed-session-storage.ts +96 -3
- package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
- package/src/setup/hermes-setup.ts +4 -1
- package/src/slash-commands/builtin-registry.ts +8 -5
- package/src/tools/ask-contract.test.ts +76 -1
- package/src/tools/ask-contract.ts +57 -6
- package/src/tools/ask.ts +16 -7
- package/src/tools/bash.ts +38 -29
- package/src/tools/descriptors.test.ts +29 -0
- package/src/tools/tool-catalog.generated.ts +7 -4
- package/src/web/search/index.ts +2 -2
package/src/config/file-lock.ts
CHANGED
|
@@ -610,7 +610,7 @@ async function classifyFileLockRemovalTransition(
|
|
|
610
610
|
if (!observation) return "active";
|
|
611
611
|
const info = parseLockInfoBytes(observation.bytes);
|
|
612
612
|
if (info) {
|
|
613
|
-
const stale = await staleLockSnapshot(transitionPath, 0, ownerHostId, previousOwnerHostIds);
|
|
613
|
+
const stale = await staleLockSnapshot(transitionPath, 0, ownerHostId, previousOwnerHostIds, undefined, true);
|
|
614
614
|
return stale.stale ? "abandoned" : "active";
|
|
615
615
|
}
|
|
616
616
|
|
|
@@ -698,6 +698,74 @@ async function adoptOrphanedFileLockRemovalTransition(lockPath: string, orphanAg
|
|
|
698
698
|
return false;
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
async function adoptAbandonedFileLockRemovalTransition(
|
|
702
|
+
lockPath: string,
|
|
703
|
+
ownerHostId?: string,
|
|
704
|
+
previousOwnerHostIds: readonly string[] = [],
|
|
705
|
+
): Promise<boolean> {
|
|
706
|
+
const transitionPath = fileLockRemovalTransitionPath(lockPath);
|
|
707
|
+
const stale = await staleLockSnapshot(transitionPath, 0, ownerHostId, previousOwnerHostIds, undefined, true);
|
|
708
|
+
if (!stale.stale || !stale.identity) return false;
|
|
709
|
+
if (!(await isNativeExactRemovalUsable())) return false;
|
|
710
|
+
|
|
711
|
+
let captured: NativeDirectoryTreeResult;
|
|
712
|
+
try {
|
|
713
|
+
captured = nativeFileLockBindings().snapshotDirectoryTree(transitionPath);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
if (isTransientReleaseError(error)) return false;
|
|
716
|
+
throw error;
|
|
717
|
+
}
|
|
718
|
+
if (!captured.ok || !captured.snapshot) return false;
|
|
719
|
+
const snapshot = captured.snapshot;
|
|
720
|
+
const root = snapshot.entries.find(entry => entry.relativePath === "");
|
|
721
|
+
const infoEntry = snapshot.entries.find(entry => entry.relativePath === "info");
|
|
722
|
+
if (
|
|
723
|
+
root?.kind !== "directory" ||
|
|
724
|
+
infoEntry?.kind !== "file" ||
|
|
725
|
+
snapshot.entries.some(
|
|
726
|
+
entry =>
|
|
727
|
+
entry.relativePath !== "" &&
|
|
728
|
+
entry.relativePath !== "info" &&
|
|
729
|
+
entry.kind !== "directory" &&
|
|
730
|
+
(entry.kind !== "file" || entry.size !== "0"),
|
|
731
|
+
)
|
|
732
|
+
)
|
|
733
|
+
return false;
|
|
734
|
+
const currentIdentity = await captureFileLockDirIdentity(transitionPath);
|
|
735
|
+
if (!currentIdentity || !sameStableFileLockIdentity(currentIdentity, stale.identity)) return false;
|
|
736
|
+
if (
|
|
737
|
+
!nativeFileLockInfoMatchesStableIdentity(snapshot.rootDev, snapshot.rootIno, infoEntry, stale.identity) ||
|
|
738
|
+
!nativeFileLockInfoMatchesContentEvidence(infoEntry, stale.identity)
|
|
739
|
+
)
|
|
740
|
+
return false;
|
|
741
|
+
|
|
742
|
+
let removal: NativeExactUnlinkResult;
|
|
743
|
+
try {
|
|
744
|
+
removal = nativeFileLockBindings().exactRemoveDirectoryTree(transitionPath, snapshot);
|
|
745
|
+
} catch (error) {
|
|
746
|
+
if (isTransientReleaseError(error)) return false;
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
if (removal.ok === true) return removal.code === undefined && Object.keys(removal).every(key => key === "ok");
|
|
750
|
+
if (
|
|
751
|
+
removal.ok === false &&
|
|
752
|
+
removal.code === "cleanup_pending" &&
|
|
753
|
+
removal.payloadDurable === true &&
|
|
754
|
+
removal.detachedPath !== undefined &&
|
|
755
|
+
path.resolve(removal.detachedPath) === path.resolve(transitionPath) &&
|
|
756
|
+
removal.retainedSuccessorPath === undefined &&
|
|
757
|
+
removal.retainedPlaceholderPath === undefined &&
|
|
758
|
+
removal.retainedUnknownPath === undefined &&
|
|
759
|
+
Object.keys(removal).every(key => ["ok", "code", "payloadDurable", "detachedPath"].includes(key))
|
|
760
|
+
)
|
|
761
|
+
return await removeDetachedLockQuarantineOnDisk(transitionPath, snapshot.rootDev, snapshot.rootIno);
|
|
762
|
+
return (
|
|
763
|
+
removal.ok === false &&
|
|
764
|
+
removal.code === "not_found" &&
|
|
765
|
+
Object.keys(removal).every(key => key === "ok" || key === "code")
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
701
769
|
function sameFileLockTreeAfterPublication(
|
|
702
770
|
staged: NativeDirectoryTreeSnapshot,
|
|
703
771
|
published: NativeDirectoryTreeSnapshot,
|
|
@@ -1488,6 +1556,14 @@ async function staleLockSnapshot(
|
|
|
1488
1556
|
ownerHostId?: string,
|
|
1489
1557
|
previousOwnerHostIds: readonly string[] = [],
|
|
1490
1558
|
startTimeCache?: Map<string, string | null>,
|
|
1559
|
+
// `.removing` transitions are internal cleanup state this codebase's own removal
|
|
1560
|
+
// machinery produced, never a live holder's public identity: a hostless transition
|
|
1561
|
+
// record predates host-id stamping and is reclaimable by local liveness proof alone,
|
|
1562
|
+
// regardless of the acquirer's own host id. Active `<file>.lock` holders keep the
|
|
1563
|
+
// stricter fail-closed rule (see `lockRecordIsForeignHost`) because that record can
|
|
1564
|
+
// belong to a genuinely foreign process; only the two transition-specific callers
|
|
1565
|
+
// below set this.
|
|
1566
|
+
hostlessTransitionIsLocal = false,
|
|
1491
1567
|
): Promise<LockStaleSnapshot> {
|
|
1492
1568
|
// Capture the root and info inode BEFORE asking whether the owner is stale. A later
|
|
1493
1569
|
// snapshot alone would let a copied successor inherit the stale verdict's authority.
|
|
@@ -1521,7 +1597,8 @@ async function staleLockSnapshot(
|
|
|
1521
1597
|
// A host-qualified lock may only be reclaimed after proving that its owner is
|
|
1522
1598
|
// local. Foreign and malformed host-qualified records fail closed: PID values
|
|
1523
1599
|
// and clocks are not meaningful across hosts.
|
|
1524
|
-
|
|
1600
|
+
const foreignCheckHostId = hostlessTransitionIsLocal && info.owner_host_id === undefined ? undefined : ownerHostId;
|
|
1601
|
+
if (lockRecordIsForeignHost(info, foreignCheckHostId, previousOwnerHostIds)) return { stale: false };
|
|
1525
1602
|
if (ownerIncarnationChanged(info, startTimeCache)) {
|
|
1526
1603
|
if (!judgedIdentity) return { stale: false };
|
|
1527
1604
|
let currentIdentity: GenericFileLockDirIdentity | null;
|
|
@@ -1752,6 +1829,18 @@ async function tryAcquireLock(
|
|
|
1752
1829
|
}
|
|
1753
1830
|
if (transitionState === "orphan_transition")
|
|
1754
1831
|
return { kind: "orphan_transition", path: fileLockRemovalTransitionPath(destinationPath) };
|
|
1832
|
+
if (transitionState === "abandoned") {
|
|
1833
|
+
if (await adoptAbandonedFileLockRemovalTransition(destinationPath, ownerHostId, previousOwnerHostIds)) {
|
|
1834
|
+
transitionState = await classifyFileLockRemovalTransition(
|
|
1835
|
+
destinationPath,
|
|
1836
|
+
orphanTransitionAgeMs,
|
|
1837
|
+
ownerHostId,
|
|
1838
|
+
previousOwnerHostIds,
|
|
1839
|
+
);
|
|
1840
|
+
} else {
|
|
1841
|
+
return null;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1755
1844
|
if (transitionState !== null) return null;
|
|
1756
1845
|
const staged = snapshotDirectoryTree(canonicalPendingPath);
|
|
1757
1846
|
if (!staged.ok || !staged.snapshot) {
|
|
@@ -115,6 +115,7 @@ export interface PrepareModelProfileActivationOptions {
|
|
|
115
115
|
>
|
|
116
116
|
> & {
|
|
117
117
|
getError?: ModelRegistry["getError"];
|
|
118
|
+
authStorage?: ModelRegistry["authStorage"];
|
|
118
119
|
};
|
|
119
120
|
settings: Pick<Settings, "get" | "getGlobal" | "getOverride">;
|
|
120
121
|
profileName: string;
|
|
@@ -475,6 +476,53 @@ export class ModelProfileCredentialError extends Error {
|
|
|
475
476
|
}
|
|
476
477
|
}
|
|
477
478
|
|
|
479
|
+
interface SessionPinRegistry {
|
|
480
|
+
authStorage?: {
|
|
481
|
+
hasRuntimeApiKey(provider: string): boolean;
|
|
482
|
+
hasLiteralConfigApiKey(provider: string, owner?: object): boolean;
|
|
483
|
+
hasSessionCredentialUnavailable(provider: string, scopeId?: string): boolean;
|
|
484
|
+
};
|
|
485
|
+
getAuthStorageOwner?(): object;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Whether an unavailable session credential pin blocks `provider`.
|
|
490
|
+
*
|
|
491
|
+
* Mirrors `AuthStorage.getApiKey` precedence: a runtime `--api-key` override or a literal
|
|
492
|
+
* `models.yml` `apiKey` registered for this registry's owner is resolved before the
|
|
493
|
+
* unavailable-pin marker, so either one keeps the provider usable. An `apiKeyEnv` key does
|
|
494
|
+
* not: `getApiKey` prefers another stored api_key account over it, which would silently
|
|
495
|
+
* retarget the unavailable pin.
|
|
496
|
+
*/
|
|
497
|
+
export function isSessionCredentialPinBlocking(
|
|
498
|
+
registry: SessionPinRegistry,
|
|
499
|
+
provider: string,
|
|
500
|
+
sessionId: string | undefined,
|
|
501
|
+
): boolean {
|
|
502
|
+
const authStorage = registry.authStorage;
|
|
503
|
+
if (!authStorage) return false;
|
|
504
|
+
if (authStorage.hasRuntimeApiKey(provider)) return false;
|
|
505
|
+
if (authStorage.hasLiteralConfigApiKey(provider, registry.getAuthStorageOwner?.())) return false;
|
|
506
|
+
return authStorage.hasSessionCredentialUnavailable(provider, sessionId);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function getProfileProviderApiKey(
|
|
510
|
+
registry: PrepareModelProfileActivationOptions["modelRegistry"],
|
|
511
|
+
provider: string,
|
|
512
|
+
sessionId: string,
|
|
513
|
+
profileLabel: string,
|
|
514
|
+
): Promise<string | undefined> {
|
|
515
|
+
const pinBlocking = () => isSessionCredentialPinBlocking(registry, provider, sessionId);
|
|
516
|
+
if (pinBlocking()) throw new ModelProfileCredentialError(profileLabel, [provider]);
|
|
517
|
+
try {
|
|
518
|
+
return await registry.getApiKeyForProvider(provider, sessionId);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
// OAuth selection can invalidate a pin while the credential probe is running.
|
|
521
|
+
if (pinBlocking()) throw new ModelProfileCredentialError(profileLabel, [provider]);
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
478
526
|
export function formatModelProfileCredentialError(profileLabel: string, providers: readonly string[]): string {
|
|
479
527
|
return `Model profile "${profileLabel}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
|
|
480
528
|
}
|
|
@@ -762,7 +810,12 @@ export async function resolveModelProfileDefaultChain(options: {
|
|
|
762
810
|
])) {
|
|
763
811
|
let apiKey: string | undefined;
|
|
764
812
|
try {
|
|
765
|
-
apiKey = await
|
|
813
|
+
apiKey = await getProfileProviderApiKey(
|
|
814
|
+
options.modelRegistry,
|
|
815
|
+
provider,
|
|
816
|
+
options.credentialSessionId,
|
|
817
|
+
profileLabel,
|
|
818
|
+
);
|
|
766
819
|
} catch (error) {
|
|
767
820
|
if (requiredProviderSet.has(provider) && !alternativeSet.has(provider)) throw error;
|
|
768
821
|
continue;
|
|
@@ -787,7 +840,12 @@ export async function resolveModelProfileDefaultChain(options: {
|
|
|
787
840
|
const proxyApiKey =
|
|
788
841
|
proxyProvider === undefined
|
|
789
842
|
? undefined
|
|
790
|
-
: await
|
|
843
|
+
: await getProfileProviderApiKey(
|
|
844
|
+
options.modelRegistry,
|
|
845
|
+
proxyProvider,
|
|
846
|
+
options.credentialSessionId,
|
|
847
|
+
profileLabel,
|
|
848
|
+
);
|
|
791
849
|
if (
|
|
792
850
|
proxyProvider !== undefined &&
|
|
793
851
|
!isModelProfileProxyConfigured(proxyProvider, configuredProviderIds, proxyApiKey === kNoAuth)
|
|
@@ -886,6 +944,8 @@ export async function resolveModelProfileDefaultChain(options: {
|
|
|
886
944
|
aliasIntent: "preset-equivalent",
|
|
887
945
|
canonicalSessionId: null,
|
|
888
946
|
credentialSessionId: options.credentialSessionId,
|
|
947
|
+
isCredentialUnavailable: provider =>
|
|
948
|
+
isSessionCredentialPinBlocking(options.modelRegistry, provider, options.credentialSessionId),
|
|
889
949
|
},
|
|
890
950
|
);
|
|
891
951
|
return { profileName, entries: defaultChain, ...resolution };
|
|
@@ -1083,6 +1143,8 @@ async function resolveAndClampSelectorValue(
|
|
|
1083
1143
|
aliasIntent: options.aliasIntent,
|
|
1084
1144
|
canonicalSessionId: options.sessionId,
|
|
1085
1145
|
credentialSessionId: options.credentialSessionId,
|
|
1146
|
+
isCredentialUnavailable: provider =>
|
|
1147
|
+
isSessionCredentialPinBlocking(options.modelRegistry, provider, options.credentialSessionId),
|
|
1086
1148
|
},
|
|
1087
1149
|
);
|
|
1088
1150
|
resolved = {
|
|
@@ -1213,6 +1275,8 @@ async function concretizeProfileSelectorValue(
|
|
|
1213
1275
|
aliasIntent: "preset-equivalent",
|
|
1214
1276
|
canonicalSessionId: prepared.session.sessionId,
|
|
1215
1277
|
credentialSessionId,
|
|
1278
|
+
isCredentialUnavailable: provider =>
|
|
1279
|
+
isSessionCredentialPinBlocking(prepared.modelRegistry, provider, credentialSessionId),
|
|
1216
1280
|
},
|
|
1217
1281
|
)
|
|
1218
1282
|
: resolveModelRoleValue(selector, candidates, {
|
|
@@ -1322,7 +1386,7 @@ export async function prepareModelProfileActivation(
|
|
|
1322
1386
|
for (const provider of authenticationProbeProviders) {
|
|
1323
1387
|
let apiKey: string | undefined;
|
|
1324
1388
|
try {
|
|
1325
|
-
apiKey = await options.modelRegistry
|
|
1389
|
+
apiKey = await getProfileProviderApiKey(options.modelRegistry, provider, credentialSessionId, profileLabel);
|
|
1326
1390
|
} catch (error) {
|
|
1327
1391
|
if (requiredProviderSet.has(provider) && !alternativeSet.has(provider)) throw error;
|
|
1328
1392
|
continue;
|
|
@@ -1357,7 +1421,7 @@ export async function prepareModelProfileActivation(
|
|
|
1357
1421
|
const proxyApiKey =
|
|
1358
1422
|
proxyProvider === undefined
|
|
1359
1423
|
? undefined
|
|
1360
|
-
: await options.modelRegistry
|
|
1424
|
+
: await getProfileProviderApiKey(options.modelRegistry, proxyProvider, credentialSessionId, profileLabel);
|
|
1361
1425
|
if (proxyProvider !== undefined) {
|
|
1362
1426
|
const configuredProxyProviders = options.modelRegistry.getConfiguredProviderIds?.();
|
|
1363
1427
|
if (!isModelProfileProxyConfigured(proxyProvider, configuredProxyProviders, proxyApiKey === kNoAuth)) {
|
|
@@ -1462,6 +1526,8 @@ export async function prepareModelProfileActivation(
|
|
|
1462
1526
|
aliasIntent: "preset-equivalent",
|
|
1463
1527
|
canonicalSessionId: options.session.sessionId,
|
|
1464
1528
|
credentialSessionId,
|
|
1529
|
+
isCredentialUnavailable: provider =>
|
|
1530
|
+
isSessionCredentialPinBlocking(options.modelRegistry, provider, credentialSessionId),
|
|
1465
1531
|
},
|
|
1466
1532
|
);
|
|
1467
1533
|
const defaultModel = defaultResolution.model;
|
|
@@ -4,7 +4,10 @@ export const MODEL_PROFILE_DISCOVERY_QUERY = "models.profiles.list";
|
|
|
4
4
|
export const MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES = 2048;
|
|
5
5
|
const REQUESTED_PROFILE_MAX_BYTES = 256;
|
|
6
6
|
|
|
7
|
-
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([
|
|
7
|
+
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([
|
|
8
|
+
["codex-standard", "codex-medium"],
|
|
9
|
+
["codex-sol61", "codex-pro"],
|
|
10
|
+
]);
|
|
8
11
|
|
|
9
12
|
export interface ModelProfileCatalogItem {
|
|
10
13
|
id: string;
|
|
@@ -73,32 +73,25 @@ const profile = (
|
|
|
73
73
|
|
|
74
74
|
export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
75
75
|
profile("codex-eco", ["openai-codex"], {
|
|
76
|
-
default: "openai-codex/gpt-
|
|
76
|
+
default: "openai-codex/gpt-6-luna:low",
|
|
77
77
|
executor: "openai-codex/gpt-6-luna:low",
|
|
78
78
|
planner: "openai-codex/gpt-6-luna:high",
|
|
79
|
-
critic: "openai-codex/gpt-
|
|
80
|
-
architect: "openai-codex/gpt-
|
|
79
|
+
critic: "openai-codex/gpt-6-luna:xhigh",
|
|
80
|
+
architect: "openai-codex/gpt-6-luna:high",
|
|
81
81
|
}),
|
|
82
82
|
profile("codex-medium", ["openai-codex"], {
|
|
83
|
-
default: "openai-codex/gpt-6-sol:low",
|
|
84
|
-
executor: "openai-codex/gpt-
|
|
85
|
-
planner: "openai-codex/gpt-
|
|
86
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
87
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
83
|
+
default: "openai-codex/gpt-6.1-sol:low",
|
|
84
|
+
executor: "openai-codex/gpt-6.1-sol:low",
|
|
85
|
+
planner: "openai-codex/gpt-6.1-sol:high",
|
|
86
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
87
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
88
88
|
}),
|
|
89
89
|
profile("codex-pro", ["openai-codex"], {
|
|
90
|
-
default: "openai-codex/gpt-6-sol:medium",
|
|
91
|
-
executor: "openai-codex/gpt-5.6-terra:medium",
|
|
92
|
-
planner: "openai-codex/gpt-6-sol:high",
|
|
93
|
-
critic: "openai-codex/gpt-6-sol:max",
|
|
94
|
-
architect: "openai-codex/gpt-6-sol:xhigh",
|
|
95
|
-
}),
|
|
96
|
-
profile("codex-sol61", ["openai-codex"], {
|
|
97
90
|
default: "openai-codex/gpt-6.1-sol:medium",
|
|
98
91
|
executor: "openai-codex/gpt-6.1-sol:medium",
|
|
99
92
|
planner: "openai-codex/gpt-6.1-sol:high",
|
|
100
|
-
critic: "openai-codex/gpt-6.1-sol:
|
|
101
|
-
architect: "openai-codex/gpt-6-
|
|
93
|
+
critic: "openai-codex/gpt-6.1-sol:max",
|
|
94
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
102
95
|
}),
|
|
103
96
|
profile("lunamaxxing", ["openai-codex"], {
|
|
104
97
|
default: "openai-codex/gpt-6-luna:medium",
|
|
@@ -111,20 +104,20 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
111
104
|
default: "openai-codex/gpt-6-astra:medium",
|
|
112
105
|
executor: "openai-codex/gpt-6-luna:xhigh",
|
|
113
106
|
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
114
|
-
critic: "openai-codex/gpt-6-sol:high",
|
|
115
|
-
architect: "openai-codex/gpt-
|
|
107
|
+
critic: "openai-codex/gpt-6.1-sol:high",
|
|
108
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
116
109
|
}),
|
|
117
110
|
profile("astra-default", ["openai-codex"], {
|
|
118
111
|
default: "openai-codex/gpt-6-astra:medium",
|
|
119
112
|
executor: "openai-codex/gpt-6-luna:max",
|
|
120
113
|
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
121
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
122
|
-
architect: "openai-codex/gpt-
|
|
114
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
115
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
123
116
|
}),
|
|
124
117
|
profile("astra-heavy", ["openai-codex"], {
|
|
125
118
|
default: "openai-codex/gpt-6-astra:medium",
|
|
126
|
-
executor: "openai-codex/gpt-6-sol:max",
|
|
127
|
-
planner: "openai-codex/gpt-6-sol:max",
|
|
119
|
+
executor: "openai-codex/gpt-6.1-sol:max",
|
|
120
|
+
planner: "openai-codex/gpt-6.1-sol:max",
|
|
128
121
|
critic: "openai-codex/gpt-6-astra:max",
|
|
129
122
|
architect: "openai-codex/gpt-6-astra:max",
|
|
130
123
|
}),
|
|
@@ -497,24 +490,24 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
497
490
|
}),
|
|
498
491
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
499
492
|
default: "anthropic/claude-opus-5-5:medium",
|
|
500
|
-
executor: "openai-codex/gpt-
|
|
493
|
+
executor: "openai-codex/gpt-6.1-sol:low",
|
|
501
494
|
planner: "anthropic/claude-sonnet-5-5",
|
|
502
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
503
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
495
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
496
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
504
497
|
}),
|
|
505
498
|
profile("codex-opencodego", ["openai-codex", "opencode-go"], {
|
|
506
|
-
default: "openai-codex/gpt-6-sol:low",
|
|
499
|
+
default: "openai-codex/gpt-6.1-sol:low",
|
|
507
500
|
executor: "opencode-go/deepseek-v4-pro",
|
|
508
501
|
planner: "opencode-go/kimi-k3",
|
|
509
502
|
critic: "opencode-go/mimo-v2.5-pro",
|
|
510
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
503
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
511
504
|
}),
|
|
512
505
|
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
513
506
|
default: "anthropic/claude-fable-5-1:high",
|
|
514
|
-
executor: "openai-codex/gpt-
|
|
507
|
+
executor: "openai-codex/gpt-6.1-sol:medium",
|
|
515
508
|
planner: "anthropic/claude-opus-5-5:medium",
|
|
516
509
|
critic: "anthropic/claude-opus-5-5:high",
|
|
517
|
-
architect: "openai-codex/gpt-6-sol:xhigh",
|
|
510
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
518
511
|
}),
|
|
519
512
|
profile("astra-fable", ["openai-codex", "anthropic"], {
|
|
520
513
|
default: "openai-codex/gpt-6-astra:medium",
|
|
@@ -562,7 +555,6 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
562
555
|
"codex-eco": { displayName: "Codex Eco", providerGroup: "CODEX" },
|
|
563
556
|
"codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
|
|
564
557
|
"codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
|
|
565
|
-
"codex-sol61": { displayName: "Codex Sol 6.1", providerGroup: "CODEX" },
|
|
566
558
|
lunamaxxing: { displayName: "LunaMaxxing", providerGroup: "CODEX" },
|
|
567
559
|
"astra-lite": { displayName: "ASTRA-Lite", providerGroup: "CODEX" },
|
|
568
560
|
"astra-default": { displayName: "ASTRA-Default", providerGroup: "CODEX" },
|
|
@@ -1189,7 +1189,15 @@ async function resolveModelChainEntries(
|
|
|
1189
1189
|
skips.push({ selector, reason: "credential_unavailable" });
|
|
1190
1190
|
break;
|
|
1191
1191
|
}
|
|
1192
|
-
|
|
1192
|
+
let key: string | undefined;
|
|
1193
|
+
try {
|
|
1194
|
+
key = await modelRegistry.getApiKey(candidate.model, credentialSessionId, { signal: options?.signal });
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
// OAuth selection can invalidate a pin after the preflight check.
|
|
1197
|
+
if (!options?.isCredentialUnavailable?.(candidate.model.provider)) throw error;
|
|
1198
|
+
skips.push({ selector, reason: "credential_unavailable" });
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1193
1201
|
if (isAuthenticatedOrKeyless(key)) {
|
|
1194
1202
|
return { ...candidate, activeIndex, skips };
|
|
1195
1203
|
}
|
|
@@ -116,6 +116,17 @@ function webhookEnvValue(explicitEnv: NodeJS.ProcessEnv | undefined, name: strin
|
|
|
116
116
|
return $credentialEnv(name)?.trim() || undefined;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Number.parseInt reads a leading digit prefix ("1.5" -> 1, "1e4" -> 1,
|
|
120
|
+
// "10s" -> 10), which would silently turn a timeout into one millisecond or a
|
|
121
|
+
// retry budget into a single attempt. Only plain decimal digits count; anything
|
|
122
|
+
// else falls back to the default, matching the coordinator policy env values.
|
|
123
|
+
function boundedWebhookEnvInt(raw: string | undefined, fallback: number, max: number): number {
|
|
124
|
+
if (raw === undefined || !/^\d+$/.test(raw)) return fallback;
|
|
125
|
+
const parsed = Number.parseInt(raw, 10);
|
|
126
|
+
// An oversized digit string still means "as large as allowed", so it caps.
|
|
127
|
+
return parsed > 0 ? Math.min(parsed, max) : fallback;
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
export function parseEventWebhookConfig(explicitEnv?: NodeJS.ProcessEnv): EventWebhookConfig | null {
|
|
120
131
|
const rawUrl = webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_URL");
|
|
121
132
|
if (!rawUrl) return null;
|
|
@@ -139,22 +150,16 @@ export function parseEventWebhookConfig(explicitEnv?: NodeJS.ProcessEnv): EventW
|
|
|
139
150
|
: null;
|
|
140
151
|
const tokenFile = webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TOKEN_FILE") ?? "";
|
|
141
152
|
if (tokenFile !== "" && !path.isAbsolute(tokenFile)) throw new Error("coordinator_event_webhook_token_file_invalid");
|
|
142
|
-
const
|
|
143
|
-
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TIMEOUT_MS")
|
|
144
|
-
|
|
153
|
+
const timeoutMs = boundedWebhookEnvInt(
|
|
154
|
+
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TIMEOUT_MS"),
|
|
155
|
+
DEFAULT_EVENT_WEBHOOK_TIMEOUT_MS,
|
|
156
|
+
MAX_EVENT_WEBHOOK_TIMEOUT_MS,
|
|
145
157
|
);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const attemptsRaw = Number.parseInt(
|
|
151
|
-
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_MAX_ATTEMPTS") ?? "",
|
|
152
|
-
10,
|
|
158
|
+
const maxAttempts = boundedWebhookEnvInt(
|
|
159
|
+
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_MAX_ATTEMPTS"),
|
|
160
|
+
DEFAULT_EVENT_WEBHOOK_MAX_ATTEMPTS,
|
|
161
|
+
MAX_EVENT_WEBHOOK_MAX_ATTEMPTS,
|
|
153
162
|
);
|
|
154
|
-
const maxAttempts =
|
|
155
|
-
Number.isFinite(attemptsRaw) && attemptsRaw > 0
|
|
156
|
-
? Math.min(attemptsRaw, MAX_EVENT_WEBHOOK_MAX_ATTEMPTS)
|
|
157
|
-
: DEFAULT_EVENT_WEBHOOK_MAX_ATTEMPTS;
|
|
158
163
|
return { url: rawUrl, tokenFile: tokenFile || null, sessionIds, timeoutMs, maxAttempts };
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -86,9 +86,19 @@ const LEGACY_MUTATION_CLASS_ALIASES = new Map<string, CoordinatorMutationClass>(
|
|
|
86
86
|
["report", "reports"],
|
|
87
87
|
]);
|
|
88
88
|
|
|
89
|
+
// Number.parseInt reads a leading digit prefix ("64KB" -> 64, "1e6" -> 1,
|
|
90
|
+
// "1.5" -> 1), which would silently shrink a cap or TTL to a tiny value. Only
|
|
91
|
+
// plain decimal digits count; anything else falls back to the default.
|
|
92
|
+
function parsePositiveInt(value: string | undefined): number | undefined {
|
|
93
|
+
const trimmed = (value ?? "").trim();
|
|
94
|
+
if (!/^\d+$/.test(trimmed)) return undefined;
|
|
95
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
96
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
function parsePositiveIntMs(value: string | undefined, fallback: number, floor: number): number {
|
|
90
|
-
const parsed =
|
|
91
|
-
if (
|
|
100
|
+
const parsed = parsePositiveInt(value);
|
|
101
|
+
if (parsed === undefined) return fallback;
|
|
92
102
|
return Math.max(floor, parsed);
|
|
93
103
|
}
|
|
94
104
|
|
|
@@ -133,8 +143,8 @@ function parseMutationClasses(value: string | undefined): Set<CoordinatorMutatio
|
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
function parseByteCap(value: string | undefined): number {
|
|
136
|
-
const parsed =
|
|
137
|
-
if (
|
|
146
|
+
const parsed = parsePositiveInt(value);
|
|
147
|
+
if (parsed === undefined) return DEFAULT_ARTIFACT_BYTE_CAP;
|
|
138
148
|
return Math.min(parsed, MAX_ARTIFACT_BYTE_CAP);
|
|
139
149
|
}
|
|
140
150
|
|