@gajae-code/coding-agent 0.17.6 → 0.17.7
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 +34 -0
- package/dist/types/cli/public-command-errors.d.ts +2 -0
- package/dist/types/commands/sdk.d.ts +7 -1
- package/dist/types/config/model-profile-ownership.d.ts +74 -0
- package/dist/types/config/model-registry.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +4 -1
- package/dist/types/sdk/lifecycle/service.d.ts +2 -0
- package/dist/types/sdk/lifecycle-session.d.ts +5 -1
- package/dist/types/sdk/session-directory.d.ts +1 -0
- package/dist/types/sdk/session-list.d.ts +11 -0
- package/dist/types/sdk/startup-capability.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +18 -0
- package/dist/types/task/delegation-hint.d.ts +53 -0
- package/examples/README.md +3 -3
- package/examples/custom-tools/README.md +1 -1
- package/examples/hooks/README.md +1 -1
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +6 -0
- package/src/cli/config-cli.ts +2 -0
- package/src/cli/public-command-errors.ts +6 -0
- package/src/commands/sdk.ts +361 -117
- package/src/config/model-profile-ownership.ts +303 -0
- package/src/config/model-registry.ts +53 -14
- package/src/config/settings-schema.ts +81 -0
- package/src/internal-urls/docs-index.generated.ts +18 -17
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/selector-controller.ts +3 -29
- package/src/sdk/broker/broker.ts +120 -23
- package/src/sdk/broker/lifecycle.ts +89 -13
- package/src/sdk/cli/session-cli.ts +45 -9
- package/src/sdk/host/control/dispatch.ts +6 -0
- package/src/sdk/lifecycle/service.ts +186 -3
- package/src/sdk/lifecycle-session.ts +35 -8
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/protocol/operation-registry.ts +5 -1
- package/src/sdk/session-directory.ts +1 -1
- package/src/sdk/session-list.ts +47 -0
- package/src/sdk/session.ts +290 -114
- package/src/sdk/startup-capability.ts +2 -1
- package/src/session/agent-session.ts +102 -4
- package/src/session/internal/managed-session-scope.ts +81 -17
- package/src/session/session-manager.ts +70 -11
- package/src/setup/model-onboarding-guidance.ts +26 -0
- package/src/task/delegation-hint.ts +578 -0
package/src/sdk/session.ts
CHANGED
|
@@ -236,6 +236,9 @@ import {
|
|
|
236
236
|
|
|
237
237
|
export type { AutomationToolName, AutomationTools } from "../tools";
|
|
238
238
|
|
|
239
|
+
const MAX_STARTUP_CREDENTIAL_DISABLED_EVENT_BUFFER = 32;
|
|
240
|
+
const startupCredentialDisabledEventsByAuthStorage = new WeakMap<AuthStorage, CredentialDisabledEvent[]>();
|
|
241
|
+
|
|
239
242
|
type AsyncResultEntry = {
|
|
240
243
|
jobId: string;
|
|
241
244
|
generation: string;
|
|
@@ -753,17 +756,7 @@ export async function discoverAuthStorage(
|
|
|
753
756
|
sourceLabel: `broker ${brokerConfig.url}`,
|
|
754
757
|
credentialRankingMode,
|
|
755
758
|
});
|
|
756
|
-
|
|
757
|
-
await storage.reload();
|
|
758
|
-
} catch (error) {
|
|
759
|
-
try {
|
|
760
|
-
storage.close();
|
|
761
|
-
} catch {
|
|
762
|
-
// Preserve the initial reload failure.
|
|
763
|
-
}
|
|
764
|
-
throw error;
|
|
765
|
-
}
|
|
766
|
-
return storage;
|
|
759
|
+
return await reloadDiscoveredAuthStorage(storage);
|
|
767
760
|
}
|
|
768
761
|
const dbPath = getAgentDbPath(agentDir);
|
|
769
762
|
const storage = await AuthStorage.create(dbPath, {
|
|
@@ -771,13 +764,17 @@ export async function discoverAuthStorage(
|
|
|
771
764
|
sourceLabel: `local ${dbPath}`,
|
|
772
765
|
credentialRankingMode,
|
|
773
766
|
});
|
|
767
|
+
return await reloadDiscoveredAuthStorage(storage);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function reloadDiscoveredAuthStorage(storage: AuthStorage): Promise<AuthStorage> {
|
|
774
771
|
try {
|
|
775
772
|
await storage.reload();
|
|
776
773
|
} catch (error) {
|
|
777
774
|
try {
|
|
778
775
|
storage.close();
|
|
779
|
-
} catch {
|
|
780
|
-
|
|
776
|
+
} catch (cleanupError) {
|
|
777
|
+
throw attachStartupCleanupDiagnostic(error, cleanupError);
|
|
781
778
|
}
|
|
782
779
|
throw error;
|
|
783
780
|
}
|
|
@@ -1396,6 +1393,31 @@ function attachMcpCleanupDiagnostic(primary: unknown, cleanup: unknown): unknown
|
|
|
1396
1393
|
return new McpManagerCleanupDiagnosticError(primary, cleanup);
|
|
1397
1394
|
}
|
|
1398
1395
|
|
|
1396
|
+
function attachStartupCleanupDiagnostic(primary: unknown, cleanup: unknown): unknown {
|
|
1397
|
+
const diagnostic = { code: "STARTUP_RESOURCE_CLEANUP_FAILED" as const, cause: cleanup };
|
|
1398
|
+
if (primary && (typeof primary === "object" || typeof primary === "function")) {
|
|
1399
|
+
try {
|
|
1400
|
+
Object.defineProperty(primary, "startupCleanupDiagnostic", {
|
|
1401
|
+
value: diagnostic,
|
|
1402
|
+
enumerable: false,
|
|
1403
|
+
configurable: true,
|
|
1404
|
+
});
|
|
1405
|
+
if ((primary as { startupCleanupDiagnostic?: unknown }).startupCleanupDiagnostic === diagnostic)
|
|
1406
|
+
if ((primary as { startupCleanupDiagnostic?: unknown }).startupCleanupDiagnostic === diagnostic)
|
|
1407
|
+
return primary;
|
|
1408
|
+
} catch {
|
|
1409
|
+
// Frozen or hostile proxy errors retain their original message in this typed wrapper.
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
const wrapped = new AggregateError([primary, cleanup], safeErrorDescription(primary), { cause: primary });
|
|
1413
|
+
Object.defineProperty(wrapped, "startupCleanupDiagnostic", {
|
|
1414
|
+
value: diagnostic,
|
|
1415
|
+
enumerable: false,
|
|
1416
|
+
configurable: true,
|
|
1417
|
+
});
|
|
1418
|
+
return wrapped;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1399
1421
|
function findExactMcpToolNameCollisions(
|
|
1400
1422
|
exactMcpToolNames: readonly string[],
|
|
1401
1423
|
catalogToolNames: Iterable<string>,
|
|
@@ -1540,29 +1562,161 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1540
1562
|
// / session would silently miss credential_disabled events.
|
|
1541
1563
|
// Injected auth is the caller's authority. Only the CLI root can hand off its
|
|
1542
1564
|
// explicit startup snapshot; never discover global config for injected SDK auth.
|
|
1543
|
-
const startupAuthConfig = hasInjectedAuth
|
|
1544
|
-
? options.modelRegistryStartupMutation?.owner === "cli-root"
|
|
1545
|
-
? options.startupAuthConfig
|
|
1546
|
-
: undefined
|
|
1547
|
-
: (options.startupAuthConfig ?? (await resolveStartupAuthConfig(agentDir)));
|
|
1548
1565
|
const ownsModelRegistry = options.modelRegistry === undefined;
|
|
1549
1566
|
const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
new ModelRegistry(
|
|
1553
|
-
options.authStorage ??
|
|
1554
|
-
(await logger.time("discoverModels", () => discoverAuthStorage(agentDir, startupAuthConfig))),
|
|
1555
|
-
path.join(agentDir, "models.yml"),
|
|
1556
|
-
undefined,
|
|
1557
|
-
{ agentDir },
|
|
1558
|
-
);
|
|
1559
|
-
const authStorage = modelRegistry.authStorage;
|
|
1560
|
-
const authStorageOwner = modelRegistry.getAuthStorageOwner();
|
|
1561
|
-
if (options.authStorage && options.authStorage !== authStorage) {
|
|
1567
|
+
const ownsScopedSettings = options.settings === undefined;
|
|
1568
|
+
if (options.modelRegistry && options.authStorage && options.authStorage !== options.modelRegistry.authStorage) {
|
|
1562
1569
|
throw new Error(
|
|
1563
1570
|
"options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
|
|
1564
1571
|
);
|
|
1565
1572
|
}
|
|
1573
|
+
const settingsPromise =
|
|
1574
|
+
options.settings === undefined
|
|
1575
|
+
? logger.time("settings", Settings.loadForScope, { cwd, agentDir })
|
|
1576
|
+
: Promise.resolve(options.settings);
|
|
1577
|
+
type StartupValue<T> = { ok: true; value: T } | { ok: false; error: unknown };
|
|
1578
|
+
const captureStartupValue = <T>(pending: Promise<T>): Promise<StartupValue<T>> =>
|
|
1579
|
+
pending.then(
|
|
1580
|
+
value => ({ ok: true as const, value }),
|
|
1581
|
+
error => ({ ok: false as const, error }),
|
|
1582
|
+
);
|
|
1583
|
+
const settingsOutcome = captureStartupValue(settingsPromise);
|
|
1584
|
+
const startupAuthConfigPromise = Promise.resolve(
|
|
1585
|
+
hasInjectedAuth
|
|
1586
|
+
? options.modelRegistryStartupMutation?.owner === "cli-root"
|
|
1587
|
+
? options.startupAuthConfig
|
|
1588
|
+
: undefined
|
|
1589
|
+
: (options.startupAuthConfig ?? resolveStartupAuthConfig(agentDir)),
|
|
1590
|
+
);
|
|
1591
|
+
const startupAuthConfigOutcome = captureStartupValue(startupAuthConfigPromise);
|
|
1592
|
+
const authStoragePromise = options.modelRegistry
|
|
1593
|
+
? Promise.resolve(options.modelRegistry.authStorage)
|
|
1594
|
+
: options.authStorage
|
|
1595
|
+
? Promise.resolve(options.authStorage)
|
|
1596
|
+
: startupAuthConfigPromise.then(startupAuthConfig =>
|
|
1597
|
+
logger.time("discoverModels", () => discoverAuthStorage(agentDir, startupAuthConfig)),
|
|
1598
|
+
);
|
|
1599
|
+
const authStorageOutcome = captureStartupValue(authStoragePromise);
|
|
1600
|
+
const discoveredAuthStorage = await authStorageOutcome;
|
|
1601
|
+
if (!discoveredAuthStorage.ok) {
|
|
1602
|
+
const loadedSettings = await settingsOutcome;
|
|
1603
|
+
const cleanupErrors: unknown[] = [];
|
|
1604
|
+
if (loadedSettings.ok && ownsScopedSettings) {
|
|
1605
|
+
try {
|
|
1606
|
+
await loadedSettings.value.close();
|
|
1607
|
+
} catch (cleanupError) {
|
|
1608
|
+
cleanupErrors.push(cleanupError);
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
releaseSettingsScope(loadedSettings.value);
|
|
1612
|
+
} catch (cleanupError) {
|
|
1613
|
+
cleanupErrors.push(cleanupError);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (cleanupErrors.length > 0)
|
|
1617
|
+
throw attachStartupCleanupDiagnostic(
|
|
1618
|
+
discoveredAuthStorage.error,
|
|
1619
|
+
new AggregateError(cleanupErrors, "Failed to release scoped settings after auth storage setup failure."),
|
|
1620
|
+
);
|
|
1621
|
+
throw discoveredAuthStorage.error;
|
|
1622
|
+
}
|
|
1623
|
+
const authStorage = discoveredAuthStorage.value;
|
|
1624
|
+
const startupCredentialDisabledEvents = startupCredentialDisabledEventsByAuthStorage.get(authStorage) ?? [];
|
|
1625
|
+
startupCredentialDisabledEventsByAuthStorage.delete(authStorage);
|
|
1626
|
+
const preserveCredentialDisabledEventsForRetry = (): void => {
|
|
1627
|
+
if (ownsAuthStorage || startupCredentialDisabledEvents.length === 0) return;
|
|
1628
|
+
const pending = startupCredentialDisabledEventsByAuthStorage.get(authStorage) ?? [];
|
|
1629
|
+
pending.push(...startupCredentialDisabledEvents.splice(0));
|
|
1630
|
+
if (pending.length > MAX_STARTUP_CREDENTIAL_DISABLED_EVENT_BUFFER)
|
|
1631
|
+
pending.splice(0, pending.length - MAX_STARTUP_CREDENTIAL_DISABLED_EVENT_BUFFER);
|
|
1632
|
+
startupCredentialDisabledEventsByAuthStorage.set(authStorage, pending);
|
|
1633
|
+
};
|
|
1634
|
+
let credentialDisabledTarget: ExtensionRunner | undefined;
|
|
1635
|
+
let unsubscribeCredentialDisabled: (() => void) | undefined;
|
|
1636
|
+
const releaseCredentialDisabledSubscription = (): void => {
|
|
1637
|
+
const unsubscribe = unsubscribeCredentialDisabled;
|
|
1638
|
+
unsubscribeCredentialDisabled = undefined;
|
|
1639
|
+
unsubscribe?.();
|
|
1640
|
+
};
|
|
1641
|
+
const failInitialSetup = async (primary: unknown, scopedSettings?: Settings): Promise<never> => {
|
|
1642
|
+
const cleanupErrors: unknown[] = [];
|
|
1643
|
+
preserveCredentialDisabledEventsForRetry();
|
|
1644
|
+
try {
|
|
1645
|
+
releaseCredentialDisabledSubscription();
|
|
1646
|
+
} catch (cleanupError) {
|
|
1647
|
+
cleanupErrors.push(cleanupError);
|
|
1648
|
+
}
|
|
1649
|
+
if (scopedSettings && ownsScopedSettings) {
|
|
1650
|
+
try {
|
|
1651
|
+
await scopedSettings.close();
|
|
1652
|
+
} catch (cleanupError) {
|
|
1653
|
+
cleanupErrors.push(cleanupError);
|
|
1654
|
+
}
|
|
1655
|
+
try {
|
|
1656
|
+
releaseSettingsScope(scopedSettings);
|
|
1657
|
+
} catch (cleanupError) {
|
|
1658
|
+
cleanupErrors.push(cleanupError);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (ownsAuthStorage) {
|
|
1662
|
+
try {
|
|
1663
|
+
authStorage.close();
|
|
1664
|
+
} catch (cleanupError) {
|
|
1665
|
+
cleanupErrors.push(cleanupError);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (cleanupErrors.length > 0)
|
|
1669
|
+
throw attachStartupCleanupDiagnostic(
|
|
1670
|
+
primary,
|
|
1671
|
+
new AggregateError(cleanupErrors, "Failed to release resources after initial session setup failure."),
|
|
1672
|
+
);
|
|
1673
|
+
throw primary;
|
|
1674
|
+
};
|
|
1675
|
+
// Subscribe before owned-registry construction as its first catalog pass may
|
|
1676
|
+
// probe credentials. Preserve the listener cleanup contract if scoped settings
|
|
1677
|
+
// loading fails after the storage has been acquired.
|
|
1678
|
+
try {
|
|
1679
|
+
unsubscribeCredentialDisabled = authStorage.onCredentialDisabled(event => {
|
|
1680
|
+
if (credentialDisabledTarget) {
|
|
1681
|
+
void credentialDisabledTarget.emitCredentialDisabled(event);
|
|
1682
|
+
} else {
|
|
1683
|
+
startupCredentialDisabledEvents.push(event);
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
const settingsAfterSubscriptionFailure = await settingsOutcome;
|
|
1688
|
+
if (!settingsAfterSubscriptionFailure.ok)
|
|
1689
|
+
return await failInitialSetup(
|
|
1690
|
+
new AggregateError(
|
|
1691
|
+
[error, settingsAfterSubscriptionFailure.error],
|
|
1692
|
+
"Credential listener registration and scoped settings initialization both failed.",
|
|
1693
|
+
),
|
|
1694
|
+
);
|
|
1695
|
+
return await failInitialSetup(error, settingsAfterSubscriptionFailure.value);
|
|
1696
|
+
}
|
|
1697
|
+
const settingsResult = await settingsOutcome;
|
|
1698
|
+
if (!settingsResult.ok) return await failInitialSetup(settingsResult.error);
|
|
1699
|
+
const settings = settingsResult.value;
|
|
1700
|
+
const closeOwnedSettings = async (): Promise<void> => {
|
|
1701
|
+
if (!ownsScopedSettings) return;
|
|
1702
|
+
try {
|
|
1703
|
+
await settings.close();
|
|
1704
|
+
} finally {
|
|
1705
|
+
releaseSettingsScope(settings);
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
const startupAuthConfigResult = await startupAuthConfigOutcome;
|
|
1709
|
+
if (!startupAuthConfigResult.ok) return await failInitialSetup(startupAuthConfigResult.error, settings);
|
|
1710
|
+
const startupAuthConfig = startupAuthConfigResult.value;
|
|
1711
|
+
let modelRegistry: ModelRegistry;
|
|
1712
|
+
try {
|
|
1713
|
+
modelRegistry =
|
|
1714
|
+
options.modelRegistry ??
|
|
1715
|
+
new ModelRegistry(authStorage, path.join(agentDir, "models.yml"), settings, { agentDir });
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
return await failInitialSetup(error, settings);
|
|
1718
|
+
}
|
|
1719
|
+
const authStorageOwner = modelRegistry.getAuthStorageOwner();
|
|
1566
1720
|
|
|
1567
1721
|
let agent: Agent;
|
|
1568
1722
|
let sessionAgent: Agent | undefined;
|
|
@@ -1622,22 +1776,34 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1622
1776
|
let authStorageClosed = false;
|
|
1623
1777
|
let credentialScopeId: string | undefined;
|
|
1624
1778
|
let credentialScopeLeased = false;
|
|
1625
|
-
let closeOwnedSettings: () => Promise<void> = async () => {};
|
|
1626
1779
|
const closeOwnedAuthStorage = async (): Promise<void> => {
|
|
1627
|
-
|
|
1780
|
+
const cleanupErrors: unknown[] = [];
|
|
1781
|
+
if (ownsModelRegistry) {
|
|
1782
|
+
try {
|
|
1783
|
+
await modelRegistry.dispose();
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
cleanupErrors.push(error);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1628
1788
|
if (!hasSession && credentialScopeLeased && credentialScopeId) {
|
|
1629
|
-
|
|
1630
|
-
|
|
1789
|
+
try {
|
|
1790
|
+
authStorage.releaseCredentialScope(credentialScopeId);
|
|
1791
|
+
credentialScopeLeased = false;
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
cleanupErrors.push(error);
|
|
1794
|
+
}
|
|
1631
1795
|
}
|
|
1632
|
-
if (
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1796
|
+
if (ownsAuthStorage && !authStorageClosed) {
|
|
1797
|
+
authStorageClosed = true;
|
|
1798
|
+
try {
|
|
1799
|
+
authStorage.close();
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
cleanupErrors.push(error);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
1805
|
+
if (cleanupErrors.length > 1)
|
|
1806
|
+
throw new AggregateError(cleanupErrors, "Failed to close owned model registry and auth storage.");
|
|
1641
1807
|
};
|
|
1642
1808
|
let inheritedMcpToolsPublisher: ((tools: readonly CustomTool[]) => void) | undefined;
|
|
1643
1809
|
let inheritedMcpToolsUnsubscribe: (() => void) | undefined;
|
|
@@ -1649,36 +1815,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1649
1815
|
};
|
|
1650
1816
|
|
|
1651
1817
|
try {
|
|
1652
|
-
// Subscribe before any getApiKey() call so startup model probes can't fire a
|
|
1653
|
-
// credential_disabled event past us. An embedder's constructor handler makes the
|
|
1654
|
-
// listener set non-empty from construction, which defeats AuthStorage's no-listener
|
|
1655
|
-
// buffer — so we can't rely on it to catch startup events for the extension runner.
|
|
1656
|
-
const startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];
|
|
1657
|
-
let credentialDisabledTarget: ExtensionRunner | undefined;
|
|
1658
|
-
unsubscribeCredentialDisabled = authStorage.onCredentialDisabled(event => {
|
|
1659
|
-
if (credentialDisabledTarget) {
|
|
1660
|
-
// Discard return: any handler error is routed through runner.onError listeners.
|
|
1661
|
-
void credentialDisabledTarget.emitCredentialDisabled(event);
|
|
1662
|
-
} else {
|
|
1663
|
-
startupCredentialDisabledEvents.push(event);
|
|
1664
|
-
}
|
|
1665
|
-
});
|
|
1666
1818
|
const applyCredentialSelector = (scopeId: string, provider: string, selector: AuthCredentialSelector): void => {
|
|
1667
1819
|
authStorage.setSessionCredentialSelector(scopeId, provider, selector, authStorageOwner);
|
|
1668
1820
|
};
|
|
1669
|
-
const ownsScopedSettings = options.settings === undefined;
|
|
1670
|
-
const settings = options.settings ?? (await logger.time("settings", Settings.loadForScope, { cwd, agentDir }));
|
|
1671
|
-
if (ownsModelRegistry) modelRegistry.setScopedSettings(settings);
|
|
1672
1821
|
const autoroutingInactive =
|
|
1673
1822
|
settings.get("task.autorouting.enabled") === true && !settings.getEffectiveAutorouting().active;
|
|
1674
|
-
closeOwnedSettings = async (): Promise<void> => {
|
|
1675
|
-
if (!ownsScopedSettings) return;
|
|
1676
|
-
try {
|
|
1677
|
-
await settings.close();
|
|
1678
|
-
} finally {
|
|
1679
|
-
releaseSettingsScope(settings);
|
|
1680
|
-
}
|
|
1681
|
-
};
|
|
1682
1823
|
// Cwd-derived runtime state must follow a rescope (`move_session`, `/move`),
|
|
1683
1824
|
// so services resolve the LIVE session cwd per activation instead of
|
|
1684
1825
|
// capturing the launch root. Before the manager exists the launch cwd is the
|
|
@@ -5825,67 +5966,102 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
5825
5966
|
gjcRuntimeSnapshot: gjcRuntimeStore,
|
|
5826
5967
|
};
|
|
5827
5968
|
} catch (error) {
|
|
5828
|
-
// Release the subscription if the throw happened after install but before the
|
|
5829
|
-
// dispose-wrap took ownership.
|
|
5830
|
-
stopInheritedMcpToolsSubscription();
|
|
5831
|
-
releaseCredentialDisabledSubscription();
|
|
5832
5969
|
let cleanupDiagnostic: unknown;
|
|
5833
|
-
|
|
5834
|
-
|
|
5970
|
+
let ownedMcpCleanupFailed = false;
|
|
5971
|
+
let ownedMcpCleanupError: unknown;
|
|
5972
|
+
const recordCleanupFailure = (cleanupError: unknown): void => {
|
|
5973
|
+
cleanupDiagnostic =
|
|
5974
|
+
cleanupDiagnostic === undefined
|
|
5975
|
+
? cleanupError
|
|
5976
|
+
: new AggregateError([cleanupDiagnostic, cleanupError], "Multiple startup cleanup operations failed.");
|
|
5977
|
+
};
|
|
5978
|
+
const attemptCleanup = async (
|
|
5979
|
+
cleanup: () => void | Promise<void>,
|
|
5980
|
+
onFailure?: (cleanupError: unknown) => void,
|
|
5981
|
+
): Promise<void> => {
|
|
5982
|
+
try {
|
|
5983
|
+
await cleanup();
|
|
5984
|
+
} catch (cleanupError) {
|
|
5985
|
+
recordCleanupFailure(cleanupError);
|
|
5986
|
+
onFailure?.(cleanupError);
|
|
5987
|
+
}
|
|
5988
|
+
};
|
|
5989
|
+
// Release the subscription if the throw happened after install but before the
|
|
5990
|
+
// dispose-wrap took ownership. Each independent cleanup still runs if another
|
|
5991
|
+
// teardown reports an error, and every failure is retained for lifecycle proof.
|
|
5992
|
+
if (!hasSession) preserveCredentialDisabledEventsForRetry();
|
|
5993
|
+
await attemptCleanup(stopInheritedMcpToolsSubscription);
|
|
5994
|
+
await attemptCleanup(releaseCredentialDisabledSubscription);
|
|
5995
|
+
if (hasSession) {
|
|
5996
|
+
await attemptCleanup(async () => {
|
|
5835
5997
|
try {
|
|
5836
5998
|
await session.dispose();
|
|
5837
5999
|
} catch (disposeError) {
|
|
5838
|
-
if (!isSessionDisposalIncompleteError(disposeError))
|
|
5839
|
-
throw disposeError;
|
|
5840
|
-
}
|
|
6000
|
+
if (!isSessionDisposalIncompleteError(disposeError)) throw disposeError;
|
|
5841
6001
|
// The first call is intentionally bounded for startup callers. Join the
|
|
5842
6002
|
// retained teardown before releasing the resources it still owns.
|
|
5843
6003
|
await session.awaitDisposeCompletion();
|
|
5844
6004
|
}
|
|
5845
|
-
}
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
6005
|
+
});
|
|
6006
|
+
} else {
|
|
6007
|
+
if (hasRegistered)
|
|
6008
|
+
await attemptCleanup(() => {
|
|
6009
|
+
agentRegistry.unregister(resolvedAgentId);
|
|
6010
|
+
});
|
|
6011
|
+
// Admission happens before session construction. Any later startup failure
|
|
6012
|
+
// must release this manager's endpoint mapping; a failed restoration cannot
|
|
6013
|
+
// prevent disposal of the owned manager or other startup resources.
|
|
6014
|
+
if (asyncJobManagerOwned && asyncJobManager) {
|
|
6015
|
+
const ownedAsyncJobManager = asyncJobManager;
|
|
6016
|
+
if (asyncJobManagerAdmitted)
|
|
6017
|
+
await attemptCleanup(() => {
|
|
6018
|
+
if (AsyncJobManager.instance() === ownedAsyncJobManager)
|
|
5856
6019
|
AsyncJobManager.setInstance(priorAsyncJobManager);
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
await
|
|
5860
|
-
}
|
|
5861
|
-
await cleanupOwnedMcpManager?.();
|
|
5862
|
-
const [{ disposeKernelSessionsByOwner }, { disposeVmContextsByOwner }] = await Promise.all([
|
|
5863
|
-
import("../eval/py/executor"),
|
|
5864
|
-
import("../eval/js/context-manager"),
|
|
5865
|
-
]);
|
|
5866
|
-
await disposeKernelSessionsByOwner(evalKernelOwnerId);
|
|
5867
|
-
await disposeVmContextsByOwner(evalKernelOwnerId);
|
|
5868
|
-
await closeOwnedSettings();
|
|
6020
|
+
});
|
|
6021
|
+
await attemptCleanup(async () => {
|
|
6022
|
+
await ownedAsyncJobManager.dispose({ timeoutMs: 100 });
|
|
6023
|
+
});
|
|
5869
6024
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
6025
|
+
if (cleanupOwnedMcpManager)
|
|
6026
|
+
await attemptCleanup(cleanupOwnedMcpManager, cleanupError => {
|
|
6027
|
+
ownedMcpCleanupFailed = true;
|
|
6028
|
+
ownedMcpCleanupError = cleanupError;
|
|
6029
|
+
});
|
|
6030
|
+
const evalCleanup = Promise.all([import("../eval/py/executor"), import("../eval/js/context-manager")]);
|
|
6031
|
+
let evalCleanupModules: Awaited<typeof evalCleanup> | undefined;
|
|
6032
|
+
try {
|
|
6033
|
+
evalCleanupModules = await evalCleanup;
|
|
6034
|
+
} catch (cleanupError) {
|
|
6035
|
+
recordCleanupFailure(cleanupError);
|
|
6036
|
+
}
|
|
6037
|
+
if (evalCleanupModules) {
|
|
6038
|
+
const [kernelExecutor, contextManager] = evalCleanupModules;
|
|
6039
|
+
await attemptCleanup(() => kernelExecutor.disposeKernelSessionsByOwner(evalKernelOwnerId));
|
|
6040
|
+
await attemptCleanup(() => contextManager.disposeVmContextsByOwner(evalKernelOwnerId));
|
|
6041
|
+
}
|
|
6042
|
+
await attemptCleanup(closeOwnedSettings);
|
|
6043
|
+
}
|
|
6044
|
+
if (processCwdClaimed)
|
|
6045
|
+
await attemptCleanup(() => {
|
|
6046
|
+
SessionManager.releaseProcessCwdOwnership(sessionManager);
|
|
6047
|
+
processCwdClaimed = false;
|
|
6048
|
+
});
|
|
6049
|
+
await attemptCleanup(releaseLocalProtocolOverride);
|
|
6050
|
+
await attemptCleanup(closeOwnedAuthStorage);
|
|
6051
|
+
if (cleanupDiagnostic !== undefined) {
|
|
5872
6052
|
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
5873
6053
|
error: safeErrorForLog(error),
|
|
5874
|
-
cleanupDiagnostic:
|
|
6054
|
+
cleanupDiagnostic: ownedMcpCleanupFailed
|
|
6055
|
+
? safeCleanupDiagnosticForLog({ code: "MCP_MANAGER_CLEANUP_FAILED", cause: ownedMcpCleanupError })
|
|
6056
|
+
: safeErrorForLog(cleanupDiagnostic),
|
|
5875
6057
|
});
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
}
|
|
5881
|
-
releaseLocalProtocolOverride();
|
|
5882
|
-
try {
|
|
5883
|
-
await closeOwnedAuthStorage();
|
|
5884
|
-
} catch (authCleanupError) {
|
|
5885
|
-
logger.warn("Failed to close owned auth storage after startup error", { error: authCleanupError });
|
|
6058
|
+
if (ownedMcpCleanupFailed) {
|
|
6059
|
+
const withMcpCleanupDiagnostic = attachMcpCleanupDiagnostic(error, ownedMcpCleanupError);
|
|
6060
|
+
if (cleanupDiagnostic === ownedMcpCleanupError) throw withMcpCleanupDiagnostic;
|
|
6061
|
+
throw attachStartupCleanupDiagnostic(withMcpCleanupDiagnostic, cleanupDiagnostic);
|
|
5886
6062
|
}
|
|
6063
|
+
throw attachStartupCleanupDiagnostic(error, cleanupDiagnostic);
|
|
5887
6064
|
}
|
|
5888
|
-
if (cleanupDiagnostic !== undefined) throw attachMcpCleanupDiagnostic(error, cleanupDiagnostic);
|
|
5889
6065
|
throw error;
|
|
5890
6066
|
}
|
|
5891
6067
|
}
|