@gajae-code/coding-agent 0.12.6 → 0.12.8
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 +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
package/src/sdk/bus/index.ts
CHANGED
|
@@ -61,12 +61,13 @@ import { registerAskAnswerSource, registerWorkflowGateEmitterListener } from "..
|
|
|
61
61
|
import { acpFinalTextFromMessage } from "../acp/final-text";
|
|
62
62
|
import { ensureBroker } from "../broker/ensure";
|
|
63
63
|
import { SessionIndex } from "../broker/session-index";
|
|
64
|
-
import {
|
|
64
|
+
import { SessionSdkHost, shouldHostSdk } from "../host";
|
|
65
65
|
import { type ControlSurface, dispatchControl } from "../host/control";
|
|
66
66
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
|
|
67
67
|
import { projectQ10Models } from "../models.js";
|
|
68
68
|
import { PROMPT_CLIENT_REF_MAX_LENGTH, type SdkPromptTerminalOutcome } from "../prompt-status";
|
|
69
69
|
import { OPERATIONS } from "../protocol/operation-registry";
|
|
70
|
+
import { ActiveProviderResolutionError } from "../providers.js";
|
|
70
71
|
import {
|
|
71
72
|
lifecycleStartupCapabilityForApi,
|
|
72
73
|
normalizeSdkStartupFailure,
|
|
@@ -75,13 +76,12 @@ import {
|
|
|
75
76
|
import { registerTelegramFileSink } from "./attachment-registry";
|
|
76
77
|
import { ensureDiscordDaemon, ensureSlackDaemon } from "./chat-daemon-control";
|
|
77
78
|
import {
|
|
79
|
+
getCurrentTelegramActivationMarker,
|
|
78
80
|
getNotificationConfig,
|
|
79
|
-
|
|
80
|
-
isSessionNotificationsEnabled,
|
|
81
|
-
isSlackConfigured,
|
|
82
|
-
isTelegramConfigured,
|
|
81
|
+
isProviderEffectivelyEnabled,
|
|
83
82
|
type NotificationConfig,
|
|
84
83
|
type NotificationSettingsReader,
|
|
84
|
+
resolveGenericNotificationSessionEligibility,
|
|
85
85
|
sessionTag,
|
|
86
86
|
} from "./config";
|
|
87
87
|
import { telegramControlCommandUsage } from "./config-commands";
|
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
import { imageAttachmentsFromMessage, notificationActionPayload, summaryFromMessage, truncate } from "./helpers";
|
|
94
94
|
import { createKindAwareReconciliation } from "./kind-aware-reconciliation";
|
|
95
95
|
import { assertNativeRuntimeCompatibility } from "./native-runtime-compatibility";
|
|
96
|
+
import { proposedTelegramIdentity } from "./notification-orchestration";
|
|
96
97
|
import { createPromptReconciliation, sanitizePromptFailure } from "./prompt-reconciliation";
|
|
97
98
|
import { createReconciliationStore } from "./reconciliation-store";
|
|
98
99
|
import { NotificationSessionController, type NotificationSessionRuntime } from "./session-control";
|
|
@@ -987,7 +988,7 @@ interface SessionRuntime {
|
|
|
987
988
|
serverStopped: boolean;
|
|
988
989
|
brokerRegistrationReleased: boolean;
|
|
989
990
|
/** Managed Telegram root registration released during terminal teardown. */
|
|
990
|
-
notificationRootRegistration?: { settings: Settings; cwd: string; registrationToken
|
|
991
|
+
notificationRootRegistration?: { settings: Settings; cwd: string; registrationToken: string };
|
|
991
992
|
verbosity: "lean" | "verbose";
|
|
992
993
|
sessionTag: string;
|
|
993
994
|
/** Whether the agent loop is currently running (drives the typing indicator). */
|
|
@@ -2101,6 +2102,13 @@ function sdkQuerySurface(
|
|
|
2101
2102
|
promptTerminalOutcomeVersion: 1,
|
|
2102
2103
|
}),
|
|
2103
2104
|
getAuthProviders: () => [...new Set(ctx.modelRegistry.getAll().map(model => model.provider))],
|
|
2105
|
+
getActiveProviders: () => {
|
|
2106
|
+
try {
|
|
2107
|
+
return ctx.modelRegistry.getActiveProviders();
|
|
2108
|
+
} catch {
|
|
2109
|
+
throw new ActiveProviderResolutionError();
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2104
2112
|
getTools: () => {
|
|
2105
2113
|
const tools = typeof (ctx as Partial<ExtensionContext>).getAllTools === "function" ? ctx.getAllTools() : [];
|
|
2106
2114
|
return tools.length > 0 ? tools : (getInstalledDefinitions("host_tools") ?? []);
|
|
@@ -3193,8 +3201,8 @@ export async function ensureConfiguredProviderDaemons(
|
|
|
3193
3201
|
configuredSettings,
|
|
3194
3202
|
) => (provider === "discord" ? ensureDiscordDaemon(configuredSettings) : ensureSlackDaemon(configuredSettings)),
|
|
3195
3203
|
): Promise<void> {
|
|
3196
|
-
if (
|
|
3197
|
-
if (
|
|
3204
|
+
if (isProviderEffectivelyEnabled(cfg, "discord")) await ensureProviderDaemon("discord", settings);
|
|
3205
|
+
if (isProviderEffectivelyEnabled(cfg, "slack")) await ensureProviderDaemon("slack", settings);
|
|
3198
3206
|
}
|
|
3199
3207
|
|
|
3200
3208
|
/**
|
|
@@ -3241,12 +3249,14 @@ export function createNotificationsExtension(
|
|
|
3241
3249
|
new NotificationSessionController({
|
|
3242
3250
|
eligible: true,
|
|
3243
3251
|
getConfig: () => resolveSettings(options.settings).cfg,
|
|
3252
|
+
spawnedByGjc: options.spawnedByGjc,
|
|
3244
3253
|
});
|
|
3245
3254
|
|
|
3246
3255
|
// Failed terminal teardown remains fenced from normal runtime lookup while the
|
|
3247
3256
|
// exact runtime object retains authority for an explicit idempotent retry.
|
|
3248
3257
|
const cleanupRetries = new Map<string, SessionRuntime>();
|
|
3249
3258
|
const sessionStartPromises = new Map<string, Promise<SessionStartResult>>();
|
|
3259
|
+
const forceIsolatedChatSessions = new Set<string>();
|
|
3250
3260
|
const branchStartupTasks = new Set<Promise<void>>();
|
|
3251
3261
|
const sessionLifecycleTasks = new Set<Promise<void>>();
|
|
3252
3262
|
let activeRuntimeId: string | undefined;
|
|
@@ -3258,8 +3268,6 @@ export function createNotificationsExtension(
|
|
|
3258
3268
|
awaitStartup: boolean;
|
|
3259
3269
|
}
|
|
3260
3270
|
| undefined;
|
|
3261
|
-
const MAX_SYNTHETIC_SEAM_ADMISSIONS = 64;
|
|
3262
|
-
let syntheticSeamAdmissions = 0;
|
|
3263
3271
|
let extensionShuttingDown = false;
|
|
3264
3272
|
|
|
3265
3273
|
async function ensureTelegramOwner(
|
|
@@ -3287,18 +3295,34 @@ export function createNotificationsExtension(
|
|
|
3287
3295
|
? "blocked_identity"
|
|
3288
3296
|
: "ready";
|
|
3289
3297
|
}
|
|
3298
|
+
type ConfiguredDaemonOwnerResult = "ready" | "blocked_identity" | "blocked_identity_with_sibling";
|
|
3290
3299
|
async function ensureConfiguredDaemonOwners(
|
|
3291
3300
|
settings: Settings,
|
|
3292
3301
|
cfg: NotificationConfig,
|
|
3293
3302
|
cwd: string,
|
|
3294
3303
|
id: string,
|
|
3295
3304
|
onRegistered?: (registrationToken: string) => void,
|
|
3296
|
-
): Promise<
|
|
3297
|
-
if (
|
|
3298
|
-
|
|
3305
|
+
): Promise<ConfiguredDaemonOwnerResult> {
|
|
3306
|
+
if (isProviderEffectivelyEnabled(cfg, "telegram")) {
|
|
3307
|
+
const telegramMarker = getCurrentTelegramActivationMarker(cfg);
|
|
3308
|
+
if (telegramMarker) {
|
|
3309
|
+
if (!isProviderEffectivelyEnabled(cfg, "discord") && !isProviderEffectivelyEnabled(cfg, "slack")) {
|
|
3310
|
+
return "blocked_identity";
|
|
3311
|
+
}
|
|
3312
|
+
await ensureConfiguredProviderDaemons(settings, cfg, options.ensureProviderDaemon);
|
|
3313
|
+
return "blocked_identity_with_sibling";
|
|
3314
|
+
}
|
|
3315
|
+
const telegram = await ensureTelegramOwner(settings, cwd, id, onRegistered);
|
|
3316
|
+
if (telegram === "blocked_identity") {
|
|
3317
|
+
if (!isProviderEffectivelyEnabled(cfg, "discord") && !isProviderEffectivelyEnabled(cfg, "slack")) {
|
|
3318
|
+
return "blocked_identity";
|
|
3319
|
+
}
|
|
3320
|
+
await ensureConfiguredProviderDaemons(settings, cfg, options.ensureProviderDaemon);
|
|
3321
|
+
return "blocked_identity_with_sibling";
|
|
3322
|
+
}
|
|
3299
3323
|
}
|
|
3300
3324
|
await ensureConfiguredProviderDaemons(settings, cfg, options.ensureProviderDaemon);
|
|
3301
|
-
return
|
|
3325
|
+
return "ready";
|
|
3302
3326
|
}
|
|
3303
3327
|
const identityControlOperations = new Set([
|
|
3304
3328
|
"session.new",
|
|
@@ -3486,7 +3510,7 @@ export function createNotificationsExtension(
|
|
|
3486
3510
|
const id = sessionId(ctx);
|
|
3487
3511
|
const lifecycleRequestId = safeLifecycleRequestId(process.env.GJC_LIFECYCLE_REQUEST_ID);
|
|
3488
3512
|
const { settings, cfg, settingsAvailable } = resolveSettings(options.settings);
|
|
3489
|
-
const notificationsEnabledForSession = controller.query(ctx).
|
|
3513
|
+
const notificationsEnabledForSession = controller.query(ctx).genericSessionEnabled;
|
|
3490
3514
|
const sdkEnabledForSession =
|
|
3491
3515
|
(options.sdkHostModeSupported ?? true) && shouldHostSdk(settings, isNotificationEligibleContext(ctx));
|
|
3492
3516
|
const lifecycleRequired = lifecycleStartupCapability !== undefined;
|
|
@@ -3527,6 +3551,25 @@ export function createNotificationsExtension(
|
|
|
3527
3551
|
}
|
|
3528
3552
|
|
|
3529
3553
|
const stateRoot = path.join(ctx.cwd, ".gjc", "state");
|
|
3554
|
+
let isolateChatEndpoint = forceIsolatedChatSessions.delete(id);
|
|
3555
|
+
if (
|
|
3556
|
+
!isolateChatEndpoint &&
|
|
3557
|
+
notificationsEnabledForSession &&
|
|
3558
|
+
settingsAvailable &&
|
|
3559
|
+
settings &&
|
|
3560
|
+
isProviderEffectivelyEnabled(cfg, "telegram") &&
|
|
3561
|
+
(isProviderEffectivelyEnabled(cfg, "discord") || isProviderEffectivelyEnabled(cfg, "slack")) &&
|
|
3562
|
+
typeof cfg.botToken === "string" &&
|
|
3563
|
+
typeof cfg.chatId === "string"
|
|
3564
|
+
) {
|
|
3565
|
+
const marker = getCurrentTelegramActivationMarker(cfg);
|
|
3566
|
+
if (marker) isolateChatEndpoint = true;
|
|
3567
|
+
else {
|
|
3568
|
+
const identity = await proposedTelegramIdentity({ settings, botToken: cfg.botToken, chatId: cfg.chatId });
|
|
3569
|
+
isolateChatEndpoint = identity.status === "foreign" || identity.status === "unknown";
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
const endpointStateRoot = isolateChatEndpoint ? path.join(stateRoot, "chat") : stateRoot;
|
|
3530
3573
|
const lifecycleAgentDir = lifecycleRequired ? settings?.getAgentDir?.() : undefined;
|
|
3531
3574
|
if (lifecycleRequired && !lifecycleAgentDir)
|
|
3532
3575
|
return failLifecycleStartup("failed", "Lifecycle SDK startup requires an agent directory.");
|
|
@@ -3548,7 +3591,7 @@ export function createNotificationsExtension(
|
|
|
3548
3591
|
nativeVersion: nativeBuildInfo().version,
|
|
3549
3592
|
notificationServer: NotificationServer.prototype,
|
|
3550
3593
|
});
|
|
3551
|
-
server = new NotificationServer(id, token,
|
|
3594
|
+
server = new NotificationServer(id, token, endpointStateRoot, true);
|
|
3552
3595
|
} catch (error) {
|
|
3553
3596
|
if (lifecycleRequired) return failLifecycleStartup("failed", error);
|
|
3554
3597
|
throw error;
|
|
@@ -3569,64 +3612,8 @@ export function createNotificationsExtension(
|
|
|
3569
3612
|
const revisions = new RevisionStore(id, Date.now, { storageDir: stateRoot });
|
|
3570
3613
|
let host: SessionSdkHost | undefined;
|
|
3571
3614
|
let disposeUiAnswerSource: (() => void) | undefined;
|
|
3572
|
-
|
|
3573
|
-
let terminalClientBridge: ClientBridge | undefined;
|
|
3574
|
-
let terminalPublicationFailedLeaseId: string | undefined;
|
|
3575
|
-
const MAX_PENDING_TERMINAL_CREATIONS = 32;
|
|
3576
|
-
let pendingTerminalCreations = 0;
|
|
3577
|
-
let terminalCreationTail = Promise.resolve();
|
|
3578
|
-
const withTerminalCreationAdmission = async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
3579
|
-
if (pendingTerminalCreations >= MAX_PENDING_TERMINAL_CREATIONS)
|
|
3580
|
-
throw new Error("terminal creation admission limit reached");
|
|
3581
|
-
pendingTerminalCreations++;
|
|
3582
|
-
const previous = terminalCreationTail;
|
|
3583
|
-
const release = Promise.withResolvers<void>();
|
|
3584
|
-
terminalCreationTail = previous.then(() => release.promise);
|
|
3585
|
-
try {
|
|
3586
|
-
await previous;
|
|
3587
|
-
return await operation();
|
|
3588
|
-
} finally {
|
|
3589
|
-
pendingTerminalCreations--;
|
|
3590
|
-
release.resolve();
|
|
3591
|
-
}
|
|
3592
|
-
};
|
|
3593
|
-
const providerLeaseIdentities = new Map<
|
|
3594
|
-
string,
|
|
3595
|
-
{ connectionId: string; connectionGeneration: number; fence: string }
|
|
3596
|
-
>();
|
|
3597
|
-
const refreshSdkClientBridge = () => {
|
|
3598
|
-
if (!fsClientBridge && !terminalClientBridge) {
|
|
3599
|
-
ctx.setSdkClientBridge?.(undefined);
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
const bridge: ClientBridge = {
|
|
3603
|
-
capabilities: {
|
|
3604
|
-
readTextFile: fsClientBridge?.capabilities.readTextFile,
|
|
3605
|
-
writeTextFile: fsClientBridge?.capabilities.writeTextFile,
|
|
3606
|
-
terminal: terminalClientBridge?.capabilities.terminal,
|
|
3607
|
-
},
|
|
3608
|
-
deferAgentInitiatedTurns: true,
|
|
3609
|
-
};
|
|
3610
|
-
if (fsClientBridge?.readTextFile) bridge.readTextFile = fsClientBridge.readTextFile;
|
|
3611
|
-
if (fsClientBridge?.writeTextFile) bridge.writeTextFile = fsClientBridge.writeTextFile;
|
|
3612
|
-
if (terminalClientBridge?.quarantineProviderLease)
|
|
3613
|
-
bridge.quarantineProviderLease = terminalClientBridge.quarantineProviderLease;
|
|
3614
|
-
if (terminalClientBridge?.createTerminal) bridge.createTerminal = terminalClientBridge.createTerminal;
|
|
3615
|
-
ctx.setSdkClientBridge?.(bridge);
|
|
3616
|
-
};
|
|
3617
|
-
const installProviderDefinitions = (
|
|
3618
|
-
capability: string,
|
|
3619
|
-
definitions: unknown,
|
|
3620
|
-
leaseId: string,
|
|
3621
|
-
connectionId: string,
|
|
3622
|
-
connectionGeneration: number,
|
|
3623
|
-
) => {
|
|
3615
|
+
const installProviderDefinitions = (capability: string, definitions: unknown) => {
|
|
3624
3616
|
validateProviderDefinitions(capability, definitions);
|
|
3625
|
-
providerLeaseIdentities.set(leaseId, {
|
|
3626
|
-
connectionId,
|
|
3627
|
-
connectionGeneration,
|
|
3628
|
-
fence: `${connectionId}:${connectionGeneration}:${leaseId}`,
|
|
3629
|
-
});
|
|
3630
3617
|
if (capability === "permission") {
|
|
3631
3618
|
ctx.setSdkPermissionProvider?.(async (toolCall, permissionOptions, signal) => {
|
|
3632
3619
|
const result = await host!.reverse.request(
|
|
@@ -3664,179 +3651,6 @@ export function createNotificationsExtension(
|
|
|
3664
3651
|
);
|
|
3665
3652
|
return;
|
|
3666
3653
|
}
|
|
3667
|
-
if (capability === "terminal") {
|
|
3668
|
-
const terminalLeaseId = leaseId;
|
|
3669
|
-
if (!terminalLeaseId) throw new Error("terminal provider lease is unavailable");
|
|
3670
|
-
const terminalProviderIdentity = providerLeaseIdentities.get(terminalLeaseId);
|
|
3671
|
-
if (!terminalProviderIdentity) throw new Error("terminal provider generation is unavailable");
|
|
3672
|
-
if (terminalPublicationFailedLeaseId !== terminalLeaseId) terminalPublicationFailedLeaseId = undefined;
|
|
3673
|
-
const requestTerminal = (
|
|
3674
|
-
method: string,
|
|
3675
|
-
payload: Record<string, unknown>,
|
|
3676
|
-
signal?: AbortSignal,
|
|
3677
|
-
cleanup = false,
|
|
3678
|
-
) =>
|
|
3679
|
-
host!.reverse.request(
|
|
3680
|
-
"terminal",
|
|
3681
|
-
method,
|
|
3682
|
-
payload,
|
|
3683
|
-
signal,
|
|
3684
|
-
terminalLeaseId,
|
|
3685
|
-
cleanup,
|
|
3686
|
-
terminalProviderIdentity.connectionId,
|
|
3687
|
-
);
|
|
3688
|
-
const settleTerminalCleanup = async (
|
|
3689
|
-
method: "terminal.kill" | "terminal.release",
|
|
3690
|
-
terminalId: string,
|
|
3691
|
-
): Promise<boolean> =>
|
|
3692
|
-
await Promise.race([
|
|
3693
|
-
Promise.resolve()
|
|
3694
|
-
.then(() => requestTerminal(method, { terminalId }, AbortSignal.timeout(500), true))
|
|
3695
|
-
.then(
|
|
3696
|
-
() => true,
|
|
3697
|
-
() => false,
|
|
3698
|
-
),
|
|
3699
|
-
Bun.sleep(500).then(() => false),
|
|
3700
|
-
]);
|
|
3701
|
-
terminalClientBridge = {
|
|
3702
|
-
capabilities: { terminal: true },
|
|
3703
|
-
deferAgentInitiatedTurns: true,
|
|
3704
|
-
async quarantineProviderLease(identity, _reason) {
|
|
3705
|
-
if (typeof identity === "string") throw new Error("terminal provider generation is required");
|
|
3706
|
-
if (
|
|
3707
|
-
identity.leaseId !== terminalLeaseId ||
|
|
3708
|
-
identity.connectionId !== terminalProviderIdentity.connectionId ||
|
|
3709
|
-
identity.connectionGeneration !== terminalProviderIdentity.connectionGeneration ||
|
|
3710
|
-
identity.fence !== terminalProviderIdentity.fence
|
|
3711
|
-
)
|
|
3712
|
-
throw new Error("terminal provider lease mismatch");
|
|
3713
|
-
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3714
|
-
host!.quarantineProviderLease(identity, "terminal cleanup ownership uncertain");
|
|
3715
|
-
},
|
|
3716
|
-
async createTerminal(params) {
|
|
3717
|
-
return withTerminalCreationAdmission(async () => {
|
|
3718
|
-
if (terminalPublicationFailedLeaseId === terminalLeaseId)
|
|
3719
|
-
throw new Error("terminal provider quarantined after publication failure");
|
|
3720
|
-
if (params.signal?.aborted) throw new Error("terminal creation aborted");
|
|
3721
|
-
const creationAbort = new AbortController();
|
|
3722
|
-
const creationSignal = params.signal
|
|
3723
|
-
? AbortSignal.any([params.signal, creationAbort.signal])
|
|
3724
|
-
: creationAbort.signal;
|
|
3725
|
-
let created: unknown;
|
|
3726
|
-
let creationTimedOut = false;
|
|
3727
|
-
try {
|
|
3728
|
-
created = await Promise.race([
|
|
3729
|
-
requestTerminal(
|
|
3730
|
-
"terminal.create",
|
|
3731
|
-
{
|
|
3732
|
-
command: params.command,
|
|
3733
|
-
...(params.args ? { args: params.args } : {}),
|
|
3734
|
-
...(params.env ? { env: params.env } : {}),
|
|
3735
|
-
...(params.cwd ? { cwd: params.cwd } : {}),
|
|
3736
|
-
...(typeof params.outputByteLimit === "number"
|
|
3737
|
-
? { outputByteLimit: params.outputByteLimit }
|
|
3738
|
-
: {}),
|
|
3739
|
-
},
|
|
3740
|
-
creationSignal,
|
|
3741
|
-
),
|
|
3742
|
-
Bun.sleep(500).then(() => {
|
|
3743
|
-
creationTimedOut = true;
|
|
3744
|
-
creationAbort.abort();
|
|
3745
|
-
throw new Error("terminal creation did not settle within 500ms");
|
|
3746
|
-
}),
|
|
3747
|
-
]);
|
|
3748
|
-
} catch (error) {
|
|
3749
|
-
if (
|
|
3750
|
-
creationSignal.aborted ||
|
|
3751
|
-
(error instanceof ReverseLeaseError && error.code === "payload_too_large")
|
|
3752
|
-
)
|
|
3753
|
-
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3754
|
-
if (creationTimedOut) throw new Error("terminal creation did not settle within 500ms");
|
|
3755
|
-
throw error;
|
|
3756
|
-
} finally {
|
|
3757
|
-
creationAbort.abort();
|
|
3758
|
-
}
|
|
3759
|
-
const terminalId =
|
|
3760
|
-
created &&
|
|
3761
|
-
typeof created === "object" &&
|
|
3762
|
-
typeof (created as { terminalId?: unknown }).terminalId === "string"
|
|
3763
|
-
? (created as { terminalId: string }).terminalId
|
|
3764
|
-
: undefined;
|
|
3765
|
-
if (!terminalId) {
|
|
3766
|
-
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3767
|
-
throw new Error("terminal provider returned an invalid create response");
|
|
3768
|
-
}
|
|
3769
|
-
const publicationAbort = new AbortController();
|
|
3770
|
-
const publicationSignal = params.signal
|
|
3771
|
-
? AbortSignal.any([params.signal, publicationAbort.signal])
|
|
3772
|
-
: publicationAbort.signal;
|
|
3773
|
-
try {
|
|
3774
|
-
await Promise.race([
|
|
3775
|
-
requestTerminal(
|
|
3776
|
-
"terminal.publish",
|
|
3777
|
-
{ toolCallId: params.toolCallId, terminalId },
|
|
3778
|
-
publicationSignal,
|
|
3779
|
-
),
|
|
3780
|
-
Bun.sleep(500).then(() => {
|
|
3781
|
-
publicationAbort.abort();
|
|
3782
|
-
throw new Error("terminal publication did not settle within 500ms");
|
|
3783
|
-
}),
|
|
3784
|
-
]);
|
|
3785
|
-
} catch (error) {
|
|
3786
|
-
const killed = await settleTerminalCleanup("terminal.kill", terminalId);
|
|
3787
|
-
const released = await settleTerminalCleanup("terminal.release", terminalId);
|
|
3788
|
-
if (!params.signal?.aborted || !killed || !released)
|
|
3789
|
-
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3790
|
-
throw error;
|
|
3791
|
-
} finally {
|
|
3792
|
-
publicationAbort.abort();
|
|
3793
|
-
}
|
|
3794
|
-
return {
|
|
3795
|
-
terminalId,
|
|
3796
|
-
providerLeaseId: terminalLeaseId,
|
|
3797
|
-
providerConnectionId: terminalProviderIdentity.connectionId,
|
|
3798
|
-
providerConnectionGeneration: terminalProviderIdentity.connectionGeneration,
|
|
3799
|
-
providerFence: terminalProviderIdentity.fence,
|
|
3800
|
-
async currentOutput() {
|
|
3801
|
-
const output = await requestTerminal("terminal.output", { terminalId });
|
|
3802
|
-
if (
|
|
3803
|
-
!output ||
|
|
3804
|
-
typeof output !== "object" ||
|
|
3805
|
-
typeof (output as { output?: unknown }).output !== "string" ||
|
|
3806
|
-
typeof (output as { truncated?: unknown }).truncated !== "boolean"
|
|
3807
|
-
) {
|
|
3808
|
-
throw new Error("terminal provider returned an invalid output response");
|
|
3809
|
-
}
|
|
3810
|
-
const response = output as { output: string; truncated: boolean; exitStatus?: unknown };
|
|
3811
|
-
return {
|
|
3812
|
-
output: response.output,
|
|
3813
|
-
truncated: response.truncated,
|
|
3814
|
-
exitStatus:
|
|
3815
|
-
response.exitStatus && typeof response.exitStatus === "object"
|
|
3816
|
-
? (response.exitStatus as { exitCode?: number | null; signal?: string | null })
|
|
3817
|
-
: null,
|
|
3818
|
-
};
|
|
3819
|
-
},
|
|
3820
|
-
async waitForExit() {
|
|
3821
|
-
const status = await requestTerminal("terminal.waitForExit", { terminalId });
|
|
3822
|
-
if (!status || typeof status !== "object")
|
|
3823
|
-
throw new Error("terminal provider returned an invalid exit response");
|
|
3824
|
-
const response = status as { exitCode?: number | null; signal?: string | null };
|
|
3825
|
-
return { exitCode: response.exitCode ?? null, signal: response.signal ?? null };
|
|
3826
|
-
},
|
|
3827
|
-
async kill() {
|
|
3828
|
-
await requestTerminal("terminal.kill", { terminalId }, undefined, true);
|
|
3829
|
-
},
|
|
3830
|
-
async release() {
|
|
3831
|
-
await requestTerminal("terminal.release", { terminalId }, undefined, true);
|
|
3832
|
-
},
|
|
3833
|
-
};
|
|
3834
|
-
});
|
|
3835
|
-
},
|
|
3836
|
-
};
|
|
3837
|
-
refreshSdkClientBridge();
|
|
3838
|
-
return;
|
|
3839
|
-
}
|
|
3840
3654
|
if (capability !== "fs") return;
|
|
3841
3655
|
// Only advertise the methods the client actually declared; a read-only client
|
|
3842
3656
|
// must keep using the local write path instead of failing against the bridge.
|
|
@@ -3868,17 +3682,11 @@ export function createNotificationsExtension(
|
|
|
3868
3682
|
};
|
|
3869
3683
|
if (!canRead) delete bridge.readTextFile;
|
|
3870
3684
|
if (!canWrite) delete bridge.writeTextFile;
|
|
3871
|
-
|
|
3872
|
-
refreshSdkClientBridge();
|
|
3685
|
+
ctx.setSdkClientBridge?.(bridge);
|
|
3873
3686
|
};
|
|
3874
3687
|
const removeProviderDefinitions = (capability: string) => {
|
|
3875
3688
|
if (capability === "permission") ctx.setSdkPermissionProvider?.(undefined);
|
|
3876
|
-
if (capability === "fs")
|
|
3877
|
-
if (capability === "terminal") {
|
|
3878
|
-
terminalClientBridge = undefined;
|
|
3879
|
-
terminalPublicationFailedLeaseId = undefined;
|
|
3880
|
-
}
|
|
3881
|
-
if (capability === "fs" || capability === "terminal") refreshSdkClientBridge();
|
|
3689
|
+
if (capability === "fs") ctx.setSdkClientBridge?.(undefined);
|
|
3882
3690
|
if (capability === "ui") {
|
|
3883
3691
|
disposeUiAnswerSource?.();
|
|
3884
3692
|
disposeUiAnswerSource = undefined;
|
|
@@ -3886,12 +3694,6 @@ export function createNotificationsExtension(
|
|
|
3886
3694
|
};
|
|
3887
3695
|
|
|
3888
3696
|
const hostCapCache = new Map<string, ReadonlySet<string>>();
|
|
3889
|
-
const MAX_PENDING_PROVIDER_RECLAIMS = 64;
|
|
3890
|
-
const pendingProviderReclaims = new Map<
|
|
3891
|
-
string,
|
|
3892
|
-
Array<{ connectionId: string; connectionGeneration: number; frame: Record<string, unknown> }>
|
|
3893
|
-
>();
|
|
3894
|
-
let pendingProviderReclaimCount = 0;
|
|
3895
3697
|
|
|
3896
3698
|
const configOverrides = new Map<string, unknown>();
|
|
3897
3699
|
const configRevision = { current: 0 };
|
|
@@ -4445,8 +4247,7 @@ export function createNotificationsExtension(
|
|
|
4445
4247
|
};
|
|
4446
4248
|
|
|
4447
4249
|
const sendSdkFrame = (connectionId: string, frame: Record<string, unknown>) => {
|
|
4448
|
-
|
|
4449
|
-
if (!cancellation && (extensionShuttingDown || runtime?.stopping || runtimes.get(id) !== runtime)) {
|
|
4250
|
+
if (extensionShuttingDown || runtime?.stopping || runtimes.get(id) !== runtime) {
|
|
4450
4251
|
abandonPromptResponse(connectionId, frame);
|
|
4451
4252
|
return;
|
|
4452
4253
|
}
|
|
@@ -4465,15 +4266,13 @@ export function createNotificationsExtension(
|
|
|
4465
4266
|
abandonPromptResponse(connectionId, frame);
|
|
4466
4267
|
throw error;
|
|
4467
4268
|
}
|
|
4468
|
-
syntheticSeamAdmissions = Math.max(0, syntheticSeamAdmissions - 1);
|
|
4469
|
-
host?.handleDisconnect(connectionId);
|
|
4470
4269
|
return;
|
|
4471
4270
|
}
|
|
4472
4271
|
try {
|
|
4473
4272
|
server.sendTo(connectionId, json);
|
|
4474
4273
|
} catch (error) {
|
|
4274
|
+
logger.warn(`sdk: directed response delivery failed for ${connectionId}: ${String(error)}`);
|
|
4475
4275
|
abandonPromptResponse(connectionId, frame);
|
|
4476
|
-
logger.warn(`sdk: directed response delivery rejected for ${connectionId}: ${String(error)}`);
|
|
4477
4276
|
throw error;
|
|
4478
4277
|
}
|
|
4479
4278
|
};
|
|
@@ -4486,13 +4285,6 @@ export function createNotificationsExtension(
|
|
|
4486
4285
|
connectionCapabilities: connectionId => hostCapCache.get(connectionId),
|
|
4487
4286
|
installProviderDefinitions,
|
|
4488
4287
|
onProviderDefinitionsRemoved: removeProviderDefinitions,
|
|
4489
|
-
onProviderLeaseRegistered: (lease, connectionGeneration) => {
|
|
4490
|
-
providerLeaseIdentities.set(lease.leaseId, {
|
|
4491
|
-
connectionId: lease.connectionId,
|
|
4492
|
-
connectionGeneration,
|
|
4493
|
-
fence: `${lease.connectionId}:${connectionGeneration}:${lease.leaseId}`,
|
|
4494
|
-
});
|
|
4495
|
-
},
|
|
4496
4288
|
onFrame: handler => {
|
|
4497
4289
|
inboundSdkFrame = handler;
|
|
4498
4290
|
return () => {
|
|
@@ -4500,11 +4292,6 @@ export function createNotificationsExtension(
|
|
|
4500
4292
|
};
|
|
4501
4293
|
},
|
|
4502
4294
|
onRequest: options.onSdkRequest,
|
|
4503
|
-
onFrameOverflow: connectionId => {
|
|
4504
|
-
fencedConnections.add(connectionId);
|
|
4505
|
-
controlSurface.cancelPendingPreflightsForConnection(connectionId);
|
|
4506
|
-
host?.handleDisconnect(connectionId);
|
|
4507
|
-
},
|
|
4508
4295
|
beforeControlResponse: async (_connectionId, request, response, sendTerminal) => {
|
|
4509
4296
|
if (typeof request.operation !== "string" || !identityControlOperations.has(request.operation)) return;
|
|
4510
4297
|
const pending = deferredIdentityRotation;
|
|
@@ -4796,47 +4583,6 @@ export function createNotificationsExtension(
|
|
|
4796
4583
|
server.sendTo(connectionId, JSON.stringify({ type: responseType, id, ok: false, error }));
|
|
4797
4584
|
} catch {}
|
|
4798
4585
|
};
|
|
4799
|
-
const queueExpectedProviderReclaim = (connectionId: string, frame: Record<string, unknown>): boolean => {
|
|
4800
|
-
if (frame.type !== "register_provider") return false;
|
|
4801
|
-
const capability = typeof frame.capability === "string" ? frame.capability : undefined;
|
|
4802
|
-
const expectedLeaseId = typeof frame.expectedLeaseId === "string" ? frame.expectedLeaseId : undefined;
|
|
4803
|
-
if (!capability || !expectedLeaseId) return false;
|
|
4804
|
-
const lease = host?.reverse.getLease(capability);
|
|
4805
|
-
if (!lease || lease.connectionId === connectionId || lease.leaseId !== expectedLeaseId) return false;
|
|
4806
|
-
if (pendingProviderReclaimCount >= MAX_PENDING_PROVIDER_RECLAIMS) return false;
|
|
4807
|
-
const queued = pendingProviderReclaims.get(lease.connectionId) ?? [];
|
|
4808
|
-
if (typeof frame.id === "string" && queued.some(entry => entry.frame.id === frame.id)) return true;
|
|
4809
|
-
const currentGeneration = host?.connectionGeneration(connectionId) ?? 0;
|
|
4810
|
-
if (currentGeneration <= 0) return false;
|
|
4811
|
-
queued.push({ connectionId, connectionGeneration: currentGeneration, frame });
|
|
4812
|
-
pendingProviderReclaims.set(lease.connectionId, queued);
|
|
4813
|
-
pendingProviderReclaimCount++;
|
|
4814
|
-
return true;
|
|
4815
|
-
};
|
|
4816
|
-
const purgeProviderReclaimsForClaimant = (connectionId: string): void => {
|
|
4817
|
-
for (const [incumbentId, queued] of pendingProviderReclaims) {
|
|
4818
|
-
const retained = queued.filter(entry => entry.connectionId !== connectionId);
|
|
4819
|
-
pendingProviderReclaimCount = Math.max(0, pendingProviderReclaimCount - (queued.length - retained.length));
|
|
4820
|
-
if (retained.length === 0) pendingProviderReclaims.delete(incumbentId);
|
|
4821
|
-
else pendingProviderReclaims.set(incumbentId, retained);
|
|
4822
|
-
}
|
|
4823
|
-
};
|
|
4824
|
-
const drainProviderReclaims = (closedConnectionId: string): void => {
|
|
4825
|
-
const queued = pendingProviderReclaims.get(closedConnectionId);
|
|
4826
|
-
if (!queued) return;
|
|
4827
|
-
pendingProviderReclaims.delete(closedConnectionId);
|
|
4828
|
-
pendingProviderReclaimCount = Math.max(0, pendingProviderReclaimCount - queued.length);
|
|
4829
|
-
for (const entry of queued) {
|
|
4830
|
-
if (
|
|
4831
|
-
initializedRuntime.inboundFenced ||
|
|
4832
|
-
initializedRuntime.stopping ||
|
|
4833
|
-
runtimes.get(id) !== initializedRuntime
|
|
4834
|
-
)
|
|
4835
|
-
continue;
|
|
4836
|
-
if (!host?.isConnectionOpen(entry.connectionId, entry.connectionGeneration)) continue;
|
|
4837
|
-
inboundSdkFrame?.(entry.connectionId, entry.frame);
|
|
4838
|
-
}
|
|
4839
|
-
};
|
|
4840
4586
|
try {
|
|
4841
4587
|
server.onSdkFrame((err, inbound) => {
|
|
4842
4588
|
if (err || !inbound) return;
|
|
@@ -4844,13 +4590,6 @@ export function createNotificationsExtension(
|
|
|
4844
4590
|
const frame = JSON.parse(inbound.json) as unknown;
|
|
4845
4591
|
if (!frame || typeof frame !== "object") return;
|
|
4846
4592
|
const typedFrame = frame as Record<string, unknown>;
|
|
4847
|
-
if (!host.isConnectionOpen(inbound.connectionId)) {
|
|
4848
|
-
if (host.connectionGeneration(inbound.connectionId) > 0) {
|
|
4849
|
-
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4850
|
-
return;
|
|
4851
|
-
}
|
|
4852
|
-
host.handleConnect(inbound.connectionId);
|
|
4853
|
-
}
|
|
4854
4593
|
if (inbound.connectionId && fencedConnections.has(inbound.connectionId)) {
|
|
4855
4594
|
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4856
4595
|
return;
|
|
@@ -4859,7 +4598,6 @@ export function createNotificationsExtension(
|
|
|
4859
4598
|
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4860
4599
|
return;
|
|
4861
4600
|
}
|
|
4862
|
-
if (queueExpectedProviderReclaim(inbound.connectionId, typedFrame)) return;
|
|
4863
4601
|
if (typedFrame.type === "ephemeral_turn" || typedFrame.type === "ephemeral_turn_cancel") return;
|
|
4864
4602
|
if (typedFrame.type === "event_replay") {
|
|
4865
4603
|
const capabilities = Array.isArray(typedFrame.capabilities) ? typedFrame.capabilities : [];
|
|
@@ -4887,16 +4625,11 @@ export function createNotificationsExtension(
|
|
|
4887
4625
|
server.onConnectionClose((_err, connectionId) => {
|
|
4888
4626
|
if (!connectionId) return;
|
|
4889
4627
|
controlSurface.cancelPendingPreflightsForConnection(connectionId);
|
|
4890
|
-
|
|
4891
|
-
host.handleDisconnect(connectionId);
|
|
4892
|
-
} finally {
|
|
4893
|
-
drainProviderReclaims(connectionId);
|
|
4894
|
-
}
|
|
4628
|
+
host.handleDisconnect(connectionId);
|
|
4895
4629
|
hostCapCache.delete(connectionId);
|
|
4896
4630
|
// The socket is gone, so its fence has nothing left to refuse. Dropping the
|
|
4897
4631
|
// entry keeps the set bounded by live connections instead of growing forever.
|
|
4898
4632
|
fencedConnections.delete(connectionId);
|
|
4899
|
-
purgeProviderReclaimsForClaimant(connectionId);
|
|
4900
4633
|
// Deliberate deviation from the plan's "claim prompt_failed on old-owner
|
|
4901
4634
|
// disconnect": that would break the shipped Q26 reconnect contract, where a
|
|
4902
4635
|
// client may drop its socket and reconcile the still-running prompt without
|
|
@@ -5177,9 +4910,7 @@ export function createNotificationsExtension(
|
|
|
5177
4910
|
!suspendedRuntime.stopping &&
|
|
5178
4911
|
!suspendedRuntime.policySuspended
|
|
5179
4912
|
)
|
|
5180
|
-
|
|
5181
|
-
syntheticSeamAdmissions++;
|
|
5182
|
-
inboundSdkFrame?.(`seam:${inbound.requestId ?? "notification"}`, frame);
|
|
4913
|
+
inboundSdkFrame?.(`seam:${inbound.requestId ?? "notification"}`, frame);
|
|
5183
4914
|
});
|
|
5184
4915
|
}
|
|
5185
4916
|
}
|
|
@@ -5188,8 +4919,6 @@ export function createNotificationsExtension(
|
|
|
5188
4919
|
if (inbound.kind === "control_command") {
|
|
5189
4920
|
const frame = sdkInboundFrame(inbound.commandJson);
|
|
5190
4921
|
if (frame) {
|
|
5191
|
-
if (syntheticSeamAdmissions >= MAX_SYNTHETIC_SEAM_ADMISSIONS) return;
|
|
5192
|
-
syntheticSeamAdmissions++;
|
|
5193
4922
|
inboundSdkFrame?.(`seam:${inbound.requestId ?? "notification"}`, frame);
|
|
5194
4923
|
return;
|
|
5195
4924
|
}
|
|
@@ -5322,18 +5051,26 @@ export function createNotificationsExtension(
|
|
|
5322
5051
|
if (notificationsEnabledForSession && settingsAvailable && settings) {
|
|
5323
5052
|
try {
|
|
5324
5053
|
let registrationToken: string | undefined;
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
) {
|
|
5054
|
+
const ownership = await ensureConfiguredDaemonOwners(settings, cfg, ctx.cwd, id, token => {
|
|
5055
|
+
registrationToken = token;
|
|
5056
|
+
});
|
|
5057
|
+
if (ownership === "blocked_identity") {
|
|
5330
5058
|
const result = failLifecycleStartup("failed", "Telegram daemon ownership is blocked.");
|
|
5331
5059
|
finishStartup(result);
|
|
5332
5060
|
await cleanupAbandonedStartup();
|
|
5333
5061
|
return result;
|
|
5334
5062
|
}
|
|
5335
|
-
if (
|
|
5063
|
+
if (ownership === "blocked_identity_with_sibling" && !isolateChatEndpoint) {
|
|
5064
|
+
await cleanupAbandonedStartup();
|
|
5065
|
+
if (sessionStartPromises.get(id) === startSettled.promise) sessionStartPromises.delete(id);
|
|
5066
|
+
forceIsolatedChatSessions.add(id);
|
|
5067
|
+
const result = await startSession(ctx);
|
|
5068
|
+
finishStartup(result);
|
|
5069
|
+
return result;
|
|
5070
|
+
}
|
|
5071
|
+
if (registrationToken !== undefined) {
|
|
5336
5072
|
runtime.notificationRootRegistration = { settings, cwd: ctx.cwd, registrationToken };
|
|
5073
|
+
}
|
|
5337
5074
|
} catch (error) {
|
|
5338
5075
|
const result = failLifecycleStartup("failed", error);
|
|
5339
5076
|
finishStartup(result);
|
|
@@ -5375,8 +5112,8 @@ export function createNotificationsExtension(
|
|
|
5375
5112
|
throwIfLifecycleStopped();
|
|
5376
5113
|
const index = await new SessionIndex(agentDir).open();
|
|
5377
5114
|
throwIfLifecycleStopped();
|
|
5378
|
-
const locator = { repo: path.resolve(ctx.cwd), stateRoot };
|
|
5379
|
-
const endpointMtimeMs = fs.statSync(path.join(
|
|
5115
|
+
const locator = { repo: path.resolve(ctx.cwd), stateRoot: endpointStateRoot };
|
|
5116
|
+
const endpointMtimeMs = fs.statSync(path.join(endpointStateRoot, "sdk", `${id}.json`)).mtimeMs;
|
|
5380
5117
|
await host.registerWithBroker({
|
|
5381
5118
|
// The endpoint is written before registration. Its exact mtime
|
|
5382
5119
|
// binds this index generation to that discovery record.
|
|
@@ -5610,6 +5347,19 @@ export function createNotificationsExtension(
|
|
|
5610
5347
|
return result.status === "started" || result.status === "already" ? "started" : result.status;
|
|
5611
5348
|
},
|
|
5612
5349
|
stop: async binding => await stopSession(binding.sessionId, "notifications"),
|
|
5350
|
+
isolateTelegram: async binding => {
|
|
5351
|
+
const runtime = runtimes.get(binding.sessionId);
|
|
5352
|
+
if (runtime) {
|
|
5353
|
+
const stopped = await stopSession(binding.sessionId, "session", runtime);
|
|
5354
|
+
if (!stopped || runtimes.has(binding.sessionId) || cleanupRetries.has(binding.sessionId)) return "failed";
|
|
5355
|
+
}
|
|
5356
|
+
forceIsolatedChatSessions.add(binding.sessionId);
|
|
5357
|
+
const result = await startSession(binding.context);
|
|
5358
|
+
if (result.status !== "started" && result.status !== "already") {
|
|
5359
|
+
forceIsolatedChatSessions.delete(binding.sessionId);
|
|
5360
|
+
}
|
|
5361
|
+
return result.status === "started" || result.status === "already" ? "started" : result.status;
|
|
5362
|
+
},
|
|
5613
5363
|
refreshPolicy: (binding, policy) => {
|
|
5614
5364
|
const runtime = runtimes.get(binding.sessionId);
|
|
5615
5365
|
if (!runtime) return;
|
|
@@ -5655,8 +5405,8 @@ export function createNotificationsExtension(
|
|
|
5655
5405
|
registrationToken = token;
|
|
5656
5406
|
});
|
|
5657
5407
|
const runtime = runtimes.get(binding.sessionId);
|
|
5658
|
-
const configured =
|
|
5659
|
-
if (result === "ready" && runtime && !runtime.stopping && configured) {
|
|
5408
|
+
const configured = isProviderEffectivelyEnabled(resolveSettings(options.settings).cfg, "telegram");
|
|
5409
|
+
if (result === "ready" && runtime && !runtime.stopping && configured && registrationToken !== undefined) {
|
|
5660
5410
|
runtime.notificationRootRegistration = { settings, cwd: binding.cwd, registrationToken };
|
|
5661
5411
|
} else if (registrationToken !== undefined) {
|
|
5662
5412
|
await unregisterNotificationRoot({
|
|
@@ -5680,11 +5430,14 @@ export function createNotificationsExtension(
|
|
|
5680
5430
|
const id = sessionId(ctx);
|
|
5681
5431
|
const command = args.trim().split(/\s+/, 1)[0]?.toLowerCase() || "status";
|
|
5682
5432
|
const resolved = resolveSettings(options.settings);
|
|
5683
|
-
const
|
|
5433
|
+
const manualEligibilityEnv =
|
|
5434
|
+
process.env.GJC_NOTIFICATIONS === "0" ? { ...process.env, GJC_NOTIFICATIONS: undefined } : process.env;
|
|
5435
|
+
const enabledWithoutLocalOff = resolveGenericNotificationSessionEligibility({
|
|
5684
5436
|
cfg: resolved.cfg,
|
|
5685
|
-
env:
|
|
5437
|
+
env: manualEligibilityEnv,
|
|
5686
5438
|
sessionDisabled: false,
|
|
5687
|
-
|
|
5439
|
+
spawnedByGjc: options.spawnedByGjc,
|
|
5440
|
+
}).enabled;
|
|
5688
5441
|
|
|
5689
5442
|
if (command === "off") {
|
|
5690
5443
|
const result = await controller.setLocalEnabled(ctx, false);
|
|
@@ -5702,13 +5455,6 @@ export function createNotificationsExtension(
|
|
|
5702
5455
|
ctx.ui.notify("Notifications are disabled for subagent sessions.", "warning");
|
|
5703
5456
|
return;
|
|
5704
5457
|
}
|
|
5705
|
-
if (process.env.GJC_NOTIFICATIONS === "0") {
|
|
5706
|
-
ctx.ui.notify(
|
|
5707
|
-
"Notifications remain disabled: GJC_NOTIFICATIONS=0 is an authoritative opt-out.",
|
|
5708
|
-
"warning",
|
|
5709
|
-
);
|
|
5710
|
-
return;
|
|
5711
|
-
}
|
|
5712
5458
|
if (!enabledWithoutLocalOff) {
|
|
5713
5459
|
ctx.ui.notify(
|
|
5714
5460
|
"Notifications are not configured. Run `gjc notify setup` or set GJC_NOTIFICATIONS=1.",
|
|
@@ -5717,7 +5463,7 @@ export function createNotificationsExtension(
|
|
|
5717
5463
|
return;
|
|
5718
5464
|
}
|
|
5719
5465
|
const result = await controller.setLocalEnabled(ctx, true);
|
|
5720
|
-
const enabled = result.status.running && result.status.
|
|
5466
|
+
const enabled = result.status.running && result.status.genericSessionEnabled;
|
|
5721
5467
|
const rotated = sessionId(ctx) !== id;
|
|
5722
5468
|
if (rotated) await stopSession(id);
|
|
5723
5469
|
const failed = result.outcome === "failed" || (!enabled && !rotated && activeRuntimeId !== id);
|
|
@@ -5742,7 +5488,7 @@ export function createNotificationsExtension(
|
|
|
5742
5488
|
const status = controller.query(ctx);
|
|
5743
5489
|
const runtime = runtimes.get(id);
|
|
5744
5490
|
ctx.ui.notify(
|
|
5745
|
-
`Notifications ${status.running ? "running" : status.
|
|
5491
|
+
`Notifications ${status.running ? "running" : status.genericSessionEnabled ? "enabled" : "disabled"} for this session; admission ${status.genericEligibilitySource}; redaction ${(runtime?.redact ?? resolved.cfg.redact) ? "on" : "off"}; verbosity ${runtime?.verbosity ?? resolved.cfg.verbosity}${status.locallyEnabled ? "" : "; locally off"}.`,
|
|
5746
5492
|
"info",
|
|
5747
5493
|
);
|
|
5748
5494
|
},
|