@gajae-code/coding-agent 0.11.10 → 0.12.0
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 +86 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +86 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +18 -2
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
- package/dist/types/sdk/host/control/operations.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +51 -19
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +59 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +49 -2
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +91 -7
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +24 -0
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +84 -3
- package/src/config/settings.ts +254 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -921
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +979 -71
- package/src/gjc-runtime/state-runtime.ts +50 -12
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +10 -11
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +597 -67
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +76 -5
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +62 -6
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +127 -29
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/lifecycle.ts +107 -7
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +503 -60
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
- package/src/sdk/bus/telegram-daemon.ts +531 -53
- package/src/sdk/host/control/dispatch.ts +7 -1
- package/src/sdk/host/control/operations.ts +2 -1
- package/src/sdk/host/host.ts +25 -3
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +76 -0
- package/src/sdk/protocol/operation-registry.ts +8 -2
- package/src/sdk/session.ts +25 -2
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +720 -190
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +40 -10
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +1372 -42
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +2 -0
- package/src/task/types.ts +22 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -193,8 +193,20 @@ export const CLIENT_PING_PONG_CAPABILITY = "client_ping_pong";
|
|
|
193
193
|
/** Capability required for typed controls and semantic Selected acknowledgement frames. */
|
|
194
194
|
export const ASK_SELECTED_ACK_CAPABILITY = "ask_selected_ack_v1";
|
|
195
195
|
export const ASK_CONTROLS_CAPABILITY = "ask_controls_v1";
|
|
196
|
-
/** Capability
|
|
197
|
-
export const TOOL_ACTIVITY_CAPABILITY = "
|
|
196
|
+
/** Capability for the closed tool phase set: started, completed, failed, and cancelled. */
|
|
197
|
+
export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
|
|
198
|
+
/** Receive-only compatibility capability for pre-v2 hosts. */
|
|
199
|
+
export const LEGACY_TOOL_ACTIVITY_CAPABILITY = "tool_activity_v1";
|
|
200
|
+
type ToolActivityCapability = "v1" | "v2";
|
|
201
|
+
|
|
202
|
+
function negotiateToolActivityCapability(
|
|
203
|
+
current: ToolActivityCapability | undefined,
|
|
204
|
+
capabilities: unknown[],
|
|
205
|
+
): ToolActivityCapability | undefined {
|
|
206
|
+
if (current === "v2" || capabilities.includes(TOOL_ACTIVITY_CAPABILITY)) return "v2";
|
|
207
|
+
if (current === "v1" || capabilities.includes(LEGACY_TOOL_ACTIVITY_CAPABILITY)) return "v1";
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
198
210
|
|
|
199
211
|
const nodeFs: TelegramDaemonFs = {
|
|
200
212
|
...(fs.promises as unknown as TelegramDaemonFs),
|
|
@@ -3834,7 +3846,7 @@ export interface TelegramDaemonOptions {
|
|
|
3834
3846
|
rich?: { enabled: boolean };
|
|
3835
3847
|
/** Opt-in rich-draft streaming of live turn previews (off by default; see rich-draft.ts). */
|
|
3836
3848
|
richDraft?: { enabled: boolean };
|
|
3837
|
-
/** Tool start/completion messages (
|
|
3849
|
+
/** Tool start/completion messages (off by default; explicit opt-in only). */
|
|
3838
3850
|
toolActivity?: { enabled: boolean };
|
|
3839
3851
|
/**
|
|
3840
3852
|
* Per-session Telegram forum-topic naming. `nameTemplate` supports the
|
|
@@ -3863,6 +3875,8 @@ interface SessionSocket {
|
|
|
3863
3875
|
/** True once the server advertised the `client_ping_pong` capability. */
|
|
3864
3876
|
capable: boolean;
|
|
3865
3877
|
ephemeralCapable: boolean;
|
|
3878
|
+
/** Tool activity contract advertised by the connected host. */
|
|
3879
|
+
toolActivityCapability?: "v1" | "v2";
|
|
3866
3880
|
/** Timestamp (via opts.now) of the last received pong; seeds the TTL window. */
|
|
3867
3881
|
lastPongAt: number;
|
|
3868
3882
|
/** Nonce of the most recent in-flight ping, if any. */
|
|
@@ -3913,6 +3927,19 @@ interface ToolActivityOwner {
|
|
|
3913
3927
|
session: SessionSocket;
|
|
3914
3928
|
phase: "started" | "terminal";
|
|
3915
3929
|
policyEpoch?: number;
|
|
3930
|
+
summaryFreeSend?: ThreadedSend;
|
|
3931
|
+
}
|
|
3932
|
+
type LegacyToolStartOutcome = "visible" | "failed" | "cancelled" | "terminal";
|
|
3933
|
+
interface LegacyToolStartSettlement {
|
|
3934
|
+
key: string;
|
|
3935
|
+
owner: ToolActivityOwner;
|
|
3936
|
+
policyEpoch: number;
|
|
3937
|
+
phase: "admitted" | "pending_identity" | "queued" | "dispatching" | LegacyToolStartOutcome;
|
|
3938
|
+
itemId?: string;
|
|
3939
|
+
settled: Promise<LegacyToolStartOutcome>;
|
|
3940
|
+
resolve: (outcome: LegacyToolStartOutcome) => void;
|
|
3941
|
+
settledOutcome?: LegacyToolStartOutcome;
|
|
3942
|
+
terminalClaimed?: boolean;
|
|
3916
3943
|
}
|
|
3917
3944
|
|
|
3918
3945
|
interface PendingThreadedFrame {
|
|
@@ -3988,6 +4015,8 @@ interface TelegramQueuePayload {
|
|
|
3988
4015
|
selectedAck?: SelectedAckQueueItem;
|
|
3989
4016
|
btwDelivery?: BtwQueuedDelivery;
|
|
3990
4017
|
toolActivity?: ToolActivityOwner;
|
|
4018
|
+
/** Exact admitted legacy-v1 start authorized for this terminal delivery. */
|
|
4019
|
+
legacyToolStart?: LegacyToolStartSettlement;
|
|
3991
4020
|
}
|
|
3992
4021
|
|
|
3993
4022
|
interface PendingBtwTurn {
|
|
@@ -4085,6 +4114,9 @@ export class TelegramNotificationDaemon {
|
|
|
4085
4114
|
/** Endpoint-bound ownership for visible or dispatching tool bubbles. */
|
|
4086
4115
|
private readonly toolActivityOwners = new Map<string, ToolActivityOwner>();
|
|
4087
4116
|
private readonly revokedToolEndpoints = new Set<string>();
|
|
4117
|
+
/** Exact settlement of each admitted legacy-v1 start; retained only while visible. */
|
|
4118
|
+
private readonly legacyToolStarts = new Map<string, LegacyToolStartSettlement>();
|
|
4119
|
+
private nextLegacyToolStartId = 1;
|
|
4088
4120
|
private readonly unresolvedToolTerminalizations = new Map<string, { messageId: number; owner: ToolActivityOwner }>();
|
|
4089
4121
|
private toolTerminalizationChain: Promise<void> = Promise.resolve();
|
|
4090
4122
|
private toolActivityPolicyEpoch = 0;
|
|
@@ -4654,6 +4686,10 @@ export class TelegramNotificationDaemon {
|
|
|
4654
4686
|
matches: msg => msg.type === "hello",
|
|
4655
4687
|
handle: (session, msg) => {
|
|
4656
4688
|
const caps = Array.isArray(msg.capabilities) ? msg.capabilities : [];
|
|
4689
|
+
const previousToolActivityCapability = session.toolActivityCapability;
|
|
4690
|
+
session.toolActivityCapability = negotiateToolActivityCapability(previousToolActivityCapability, caps);
|
|
4691
|
+
if (previousToolActivityCapability === "v1" && session.toolActivityCapability === "v2")
|
|
4692
|
+
this.cleanLegacyToolStartsForCapabilityUpgrade(session);
|
|
4657
4693
|
if (caps.includes("ephemeral_turn_v1")) session.ephemeralCapable = true;
|
|
4658
4694
|
if (caps.includes(CLIENT_PING_PONG_CAPABILITY)) {
|
|
4659
4695
|
session.capable = true;
|
|
@@ -5186,6 +5222,7 @@ export class TelegramNotificationDaemon {
|
|
|
5186
5222
|
ASK_CONTROLS_CAPABILITY,
|
|
5187
5223
|
ASK_SELECTED_ACK_CAPABILITY,
|
|
5188
5224
|
TOOL_ACTIVITY_CAPABILITY,
|
|
5225
|
+
LEGACY_TOOL_ACTIVITY_CAPABILITY,
|
|
5189
5226
|
"ephemeral_turn_v1",
|
|
5190
5227
|
],
|
|
5191
5228
|
}),
|
|
@@ -5279,12 +5316,12 @@ export class TelegramNotificationDaemon {
|
|
|
5279
5316
|
for (const item of claimedItems) {
|
|
5280
5317
|
const { messageId, owner } = item;
|
|
5281
5318
|
const backlogKey = `${owner.endpointDigest}\0${owner.sessionId}\0${owner.toolCallId}\0${messageId}`;
|
|
5282
|
-
const send = renderThreadedFrame({
|
|
5319
|
+
const send = this.renderThreadedFrame({
|
|
5283
5320
|
type: "tool_activity",
|
|
5284
5321
|
sessionId: owner.sessionId,
|
|
5285
5322
|
toolCallId: owner.toolCallId,
|
|
5286
5323
|
toolName: owner.toolName,
|
|
5287
|
-
phase: "
|
|
5324
|
+
phase: "cancelled",
|
|
5288
5325
|
});
|
|
5289
5326
|
if (!send?.text) continue;
|
|
5290
5327
|
let failure: unknown;
|
|
@@ -5342,6 +5379,9 @@ export class TelegramNotificationDaemon {
|
|
|
5342
5379
|
this.toolActivityOwners.delete(key);
|
|
5343
5380
|
const messageId = this.liveMessages.get(key);
|
|
5344
5381
|
this.liveMessages.delete(key);
|
|
5382
|
+
const legacyStart = this.legacyToolStarts.get(key);
|
|
5383
|
+
if (legacyStart !== undefined && legacyStart.owner === owner)
|
|
5384
|
+
this.settleLegacyToolStart(legacyStart, "terminal");
|
|
5345
5385
|
if (messageId !== undefined) {
|
|
5346
5386
|
const backlogKey = `${owner.endpointDigest}\0${owner.sessionId}\0${owner.toolCallId}\0${messageId}`;
|
|
5347
5387
|
claimedByKey.set(backlogKey, { messageId, owner });
|
|
@@ -5369,6 +5409,7 @@ export class TelegramNotificationDaemon {
|
|
|
5369
5409
|
this.toolActivityStopping = true;
|
|
5370
5410
|
this.toolActivityPolicyEpoch++;
|
|
5371
5411
|
this.toolShutdownBarrier = (async () => {
|
|
5412
|
+
await this.cancelLegacyToolStartsForPolicyTransition();
|
|
5372
5413
|
await this.flushChain;
|
|
5373
5414
|
const failures: Error[] = [];
|
|
5374
5415
|
if (this.toolActivityAmbiguous) {
|
|
@@ -5393,6 +5434,7 @@ export class TelegramNotificationDaemon {
|
|
|
5393
5434
|
if (session.recoveryLease) session.recoveryLease = { ...session.recoveryLease, state: "rejected" };
|
|
5394
5435
|
const isCurrentSession = this.sessions.get(session.sessionId) === session;
|
|
5395
5436
|
if (isCurrentSession) this.droppedSessions.add(session);
|
|
5437
|
+
if (isCurrentSession) this.cancelLegacyToolStartsForSession(session);
|
|
5396
5438
|
if (isCurrentSession) this.scheduleVisibleToolTerminalization(session.endpointDigest).catch(() => undefined);
|
|
5397
5439
|
const clearIntervalImpl = this.opts.clearIntervalImpl ?? clearInterval;
|
|
5398
5440
|
if (session.pingTimer) {
|
|
@@ -5923,7 +5965,7 @@ export class TelegramNotificationDaemon {
|
|
|
5923
5965
|
typeof msg.sessionId !== "string" ||
|
|
5924
5966
|
!msg.sessionId.trim() ||
|
|
5925
5967
|
msg.sessionId === this.#logicalSessionId(session) ||
|
|
5926
|
-
(msg.type !== "config_update" && !renderThreadedFrame(msg))
|
|
5968
|
+
(msg.type !== "config_update" && !this.renderThreadedFrame(msg))
|
|
5927
5969
|
)
|
|
5928
5970
|
return;
|
|
5929
5971
|
if (!session.logicalSessionIdTrusted) return;
|
|
@@ -6185,12 +6227,151 @@ export class TelegramNotificationDaemon {
|
|
|
6185
6227
|
return undefined;
|
|
6186
6228
|
}
|
|
6187
6229
|
|
|
6230
|
+
private admitLegacyToolStart(owner: ToolActivityOwner): LegacyToolStartSettlement | undefined {
|
|
6231
|
+
if (owner.session.toolActivityCapability !== "v1") return undefined;
|
|
6232
|
+
const key = `${owner.sessionId}:tool:${owner.toolCallId}`;
|
|
6233
|
+
if (this.legacyToolStarts.has(key)) return undefined;
|
|
6234
|
+
const deferred = Promise.withResolvers<LegacyToolStartOutcome>();
|
|
6235
|
+
const state: LegacyToolStartSettlement = {
|
|
6236
|
+
key,
|
|
6237
|
+
owner,
|
|
6238
|
+
policyEpoch: owner.policyEpoch ?? this.toolActivityPolicyEpoch,
|
|
6239
|
+
phase: "admitted",
|
|
6240
|
+
settled: deferred.promise,
|
|
6241
|
+
resolve: deferred.resolve,
|
|
6242
|
+
};
|
|
6243
|
+
this.legacyToolStarts.set(key, state);
|
|
6244
|
+
return state;
|
|
6245
|
+
}
|
|
6246
|
+
private legacyToolStartForTerminal(owner: ToolActivityOwner): LegacyToolStartSettlement | undefined {
|
|
6247
|
+
if (owner.phase !== "terminal") return undefined;
|
|
6248
|
+
const key = `${owner.sessionId}:tool:${owner.toolCallId}`;
|
|
6249
|
+
const state = this.legacyToolStarts.get(key);
|
|
6250
|
+
if (
|
|
6251
|
+
state === undefined ||
|
|
6252
|
+
state.key !== key ||
|
|
6253
|
+
state.owner.session !== owner.session ||
|
|
6254
|
+
state.owner.endpointDigest !== owner.endpointDigest ||
|
|
6255
|
+
state.owner.toolName !== owner.toolName ||
|
|
6256
|
+
state.policyEpoch !== owner.policyEpoch
|
|
6257
|
+
)
|
|
6258
|
+
return undefined;
|
|
6259
|
+
return state;
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6262
|
+
private settleLegacyToolStart(state: LegacyToolStartSettlement, outcome: LegacyToolStartOutcome): void {
|
|
6263
|
+
if (state.settledOutcome === undefined) {
|
|
6264
|
+
state.settledOutcome = outcome;
|
|
6265
|
+
state.resolve(outcome);
|
|
6266
|
+
}
|
|
6267
|
+
state.phase = outcome;
|
|
6268
|
+
if (outcome !== "visible" && this.legacyToolStarts.get(state.key) === state)
|
|
6269
|
+
this.legacyToolStarts.delete(state.key);
|
|
6270
|
+
if (outcome !== "visible" && !this.liveMessages.has(state.key)) {
|
|
6271
|
+
const owner = this.toolActivityOwners.get(state.key);
|
|
6272
|
+
if (owner === state.owner) this.toolActivityOwners.delete(state.key);
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
|
|
6276
|
+
private cancelUnsentLegacyToolStart(state: LegacyToolStartSettlement): boolean {
|
|
6277
|
+
if (state.phase !== "admitted" && state.phase !== "pending_identity" && state.phase !== "queued") return false;
|
|
6278
|
+
if (state.itemId) this.pool.removeById(state.itemId);
|
|
6279
|
+
for (const [sessionId, frames] of this.pendingThreadedFrames) {
|
|
6280
|
+
const retained = frames.filter(frame => frame.toolActivity !== state.owner);
|
|
6281
|
+
if (retained.length === 0) this.pendingThreadedFrames.delete(sessionId);
|
|
6282
|
+
else if (retained.length !== frames.length) this.pendingThreadedFrames.set(sessionId, retained);
|
|
6283
|
+
}
|
|
6284
|
+
this.settleLegacyToolStart(state, "cancelled");
|
|
6285
|
+
return true;
|
|
6286
|
+
}
|
|
6287
|
+
|
|
6288
|
+
private failLegacyToolStart(toolActivity: ToolActivityOwner | undefined): void {
|
|
6289
|
+
if (toolActivity?.phase !== "started") return;
|
|
6290
|
+
const state = this.legacyToolStarts.get(`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`);
|
|
6291
|
+
if (state !== undefined && state.owner === toolActivity && state.phase !== "visible")
|
|
6292
|
+
this.settleLegacyToolStart(state, "failed");
|
|
6293
|
+
}
|
|
6294
|
+
|
|
6295
|
+
private settleRejectedLegacyToolSubmission(
|
|
6296
|
+
toolActivity: ToolActivityOwner | undefined,
|
|
6297
|
+
legacyToolStart: LegacyToolStartSettlement | undefined,
|
|
6298
|
+
): void {
|
|
6299
|
+
if (toolActivity?.phase === "started") {
|
|
6300
|
+
this.failLegacyToolStart(toolActivity);
|
|
6301
|
+
return;
|
|
6302
|
+
}
|
|
6303
|
+
if (
|
|
6304
|
+
toolActivity?.phase !== "terminal" ||
|
|
6305
|
+
legacyToolStart === undefined ||
|
|
6306
|
+
this.legacyToolStarts.get(legacyToolStart.key) !== legacyToolStart
|
|
6307
|
+
)
|
|
6308
|
+
return;
|
|
6309
|
+
this.settleLegacyToolStart(legacyToolStart, "terminal");
|
|
6310
|
+
this.liveMessages.delete(legacyToolStart.key);
|
|
6311
|
+
if (this.toolActivityOwners.get(legacyToolStart.key) === legacyToolStart.owner)
|
|
6312
|
+
this.toolActivityOwners.delete(legacyToolStart.key);
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
private cancelLegacyToolStartsForSession(session: SessionSocket): void {
|
|
6316
|
+
for (const state of [...this.legacyToolStarts.values()]) {
|
|
6317
|
+
if (state.owner.session !== session) continue;
|
|
6318
|
+
if (!this.cancelUnsentLegacyToolStart(state)) this.settleLegacyToolStart(state, "cancelled");
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
|
|
6322
|
+
private cleanLegacyToolStartsForCapabilityUpgrade(session: SessionSocket): void {
|
|
6323
|
+
for (const state of [...this.legacyToolStarts.values()]) {
|
|
6324
|
+
if (state.owner.session !== session || state.phase === "dispatching") continue;
|
|
6325
|
+
if (state.phase === "visible") this.settleLegacyToolStart(state, "terminal");
|
|
6326
|
+
else this.cancelUnsentLegacyToolStart(state);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
private cancelLegacyToolStartsForPolicyTransition(): Promise<LegacyToolStartOutcome[]> {
|
|
6330
|
+
const dispatching: Promise<LegacyToolStartOutcome>[] = [];
|
|
6331
|
+
const visibleTerminalizations: Array<{ messageId: number; owner: ToolActivityOwner }> = [];
|
|
6332
|
+
for (const state of [...this.legacyToolStarts.values()]) {
|
|
6333
|
+
if (state.phase === "dispatching")
|
|
6334
|
+
dispatching.push(
|
|
6335
|
+
state.settled.then(outcome => {
|
|
6336
|
+
if (outcome === "visible" && this.legacyToolStarts.get(state.key) === state) {
|
|
6337
|
+
const messageId = this.liveMessages.get(state.key);
|
|
6338
|
+
this.liveMessages.delete(state.key);
|
|
6339
|
+
if (this.toolActivityOwners.get(state.key) === state.owner)
|
|
6340
|
+
this.toolActivityOwners.delete(state.key);
|
|
6341
|
+
this.settleLegacyToolStart(state, "terminal");
|
|
6342
|
+
if (messageId !== undefined) {
|
|
6343
|
+
void this.enqueueToolTerminalization([{ messageId, owner: state.owner }], false);
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
return outcome;
|
|
6347
|
+
}),
|
|
6348
|
+
);
|
|
6349
|
+
else if (state.phase === "visible") {
|
|
6350
|
+
const messageId = this.liveMessages.get(state.key);
|
|
6351
|
+
this.liveMessages.delete(state.key);
|
|
6352
|
+
if (this.toolActivityOwners.get(state.key) === state.owner) this.toolActivityOwners.delete(state.key);
|
|
6353
|
+
this.settleLegacyToolStart(state, "terminal");
|
|
6354
|
+
if (messageId !== undefined) {
|
|
6355
|
+
visibleTerminalizations.push({ messageId, owner: state.owner });
|
|
6356
|
+
}
|
|
6357
|
+
} else this.cancelUnsentLegacyToolStart(state);
|
|
6358
|
+
}
|
|
6359
|
+
if (visibleTerminalizations.length > 0) {
|
|
6360
|
+
void this.enqueueToolTerminalization(visibleTerminalizations, false);
|
|
6361
|
+
}
|
|
6362
|
+
return Promise.all(dispatching);
|
|
6363
|
+
}
|
|
6188
6364
|
private toolActivityOwner(session: SessionSocket, msg: Record<string, unknown>): ToolActivityOwner | undefined {
|
|
6189
6365
|
if (msg.type !== "tool_activity") return undefined;
|
|
6190
6366
|
const toolCallId = typeof msg.toolCallId === "string" ? msg.toolCallId : undefined;
|
|
6191
6367
|
const toolName = typeof msg.toolName === "string" ? msg.toolName : undefined;
|
|
6192
6368
|
const phase = typeof msg.phase === "string" ? msg.phase : undefined;
|
|
6193
|
-
if (
|
|
6369
|
+
if (
|
|
6370
|
+
!toolCallId ||
|
|
6371
|
+
!toolName ||
|
|
6372
|
+
(phase !== "started" && phase !== "completed" && phase !== "failed" && phase !== "cancelled")
|
|
6373
|
+
)
|
|
6374
|
+
return undefined;
|
|
6194
6375
|
return {
|
|
6195
6376
|
sessionId: this.#logicalSessionId(session),
|
|
6196
6377
|
toolCallId,
|
|
@@ -6200,6 +6381,30 @@ export class TelegramNotificationDaemon {
|
|
|
6200
6381
|
phase: phase === "started" ? "started" : "terminal",
|
|
6201
6382
|
};
|
|
6202
6383
|
}
|
|
6384
|
+
private renderThreadedFrame(frame: Record<string, unknown>): ThreadedSend | undefined {
|
|
6385
|
+
if (
|
|
6386
|
+
frame.type === "tool_activity" &&
|
|
6387
|
+
frame.phase !== "started" &&
|
|
6388
|
+
frame.phase !== "completed" &&
|
|
6389
|
+
frame.phase !== "failed" &&
|
|
6390
|
+
frame.phase !== "cancelled"
|
|
6391
|
+
)
|
|
6392
|
+
return undefined;
|
|
6393
|
+
return renderThreadedFrame(frame);
|
|
6394
|
+
}
|
|
6395
|
+
private toolActivityFrameWithoutSummaries(frame: Record<string, unknown>): Record<string, unknown> {
|
|
6396
|
+
const summaryFree = { ...frame };
|
|
6397
|
+
delete summaryFree.argsSummary;
|
|
6398
|
+
delete summaryFree.resultSummary;
|
|
6399
|
+
return summaryFree;
|
|
6400
|
+
}
|
|
6401
|
+
private toolActivitySummariesAreCurrent(toolActivity: ToolActivityOwner): boolean {
|
|
6402
|
+
return (
|
|
6403
|
+
this.opts.toolActivity?.enabled === true &&
|
|
6404
|
+
toolActivity.policyEpoch === this.toolActivityPolicyEpoch &&
|
|
6405
|
+
this.toolActivityAuthorityIsCurrent(toolActivity)
|
|
6406
|
+
);
|
|
6407
|
+
}
|
|
6203
6408
|
|
|
6204
6409
|
private toolActivityAuthorityIsCurrent(toolActivity: ToolActivityOwner): boolean {
|
|
6205
6410
|
if (this.revokedToolEndpoints.has(toolActivity.endpointDigest)) return false;
|
|
@@ -6208,6 +6413,34 @@ export class TelegramNotificationDaemon {
|
|
|
6208
6413
|
if (toolActivity.endpointDigest === undefined) return session === undefined;
|
|
6209
6414
|
return session === toolActivity.session && session.endpointDigest === toolActivity.endpointDigest;
|
|
6210
6415
|
}
|
|
6416
|
+
private toolActivityDeliveryIsCurrent(toolActivity: ToolActivityOwner): boolean {
|
|
6417
|
+
const key = `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`;
|
|
6418
|
+
const owner = this.toolActivityOwners.get(key);
|
|
6419
|
+
if (owner && owner.session !== toolActivity.session) return false;
|
|
6420
|
+
if (toolActivity.phase === "started") {
|
|
6421
|
+
const legacyStart =
|
|
6422
|
+
toolActivity.session.toolActivityCapability === "v1" ? this.legacyToolStarts.get(key) : undefined;
|
|
6423
|
+
return (
|
|
6424
|
+
(toolActivity.session.toolActivityCapability !== "v1" ||
|
|
6425
|
+
(legacyStart !== undefined &&
|
|
6426
|
+
legacyStart.owner === toolActivity &&
|
|
6427
|
+
legacyStart.phase !== "cancelled" &&
|
|
6428
|
+
legacyStart.phase !== "failed" &&
|
|
6429
|
+
legacyStart.phase !== "terminal")) &&
|
|
6430
|
+
this.toolActivityAuthorityIsCurrent(toolActivity) &&
|
|
6431
|
+
!this.toolActivityStopping &&
|
|
6432
|
+
this.opts.toolActivity?.enabled === true &&
|
|
6433
|
+
toolActivity.policyEpoch === this.toolActivityPolicyEpoch
|
|
6434
|
+
);
|
|
6435
|
+
}
|
|
6436
|
+
if (
|
|
6437
|
+
this.toolActivityAuthorityIsCurrent(toolActivity) &&
|
|
6438
|
+
this.opts.toolActivity?.enabled === true &&
|
|
6439
|
+
toolActivity.policyEpoch === this.toolActivityPolicyEpoch
|
|
6440
|
+
)
|
|
6441
|
+
return true;
|
|
6442
|
+
return this.liveMessages.has(key) && owner?.session === toolActivity.session;
|
|
6443
|
+
}
|
|
6211
6444
|
private async submitThreadedFrame(
|
|
6212
6445
|
sessionId: string,
|
|
6213
6446
|
send: ThreadedSend,
|
|
@@ -6215,17 +6448,45 @@ export class TelegramNotificationDaemon {
|
|
|
6215
6448
|
toolActivity?: ToolActivityOwner,
|
|
6216
6449
|
socketLease?: { session: SessionSocket; token: number; logicalSessionId: string },
|
|
6217
6450
|
): Promise<void> {
|
|
6218
|
-
|
|
6451
|
+
const legacyStart =
|
|
6452
|
+
toolActivity?.phase === "started"
|
|
6453
|
+
? this.legacyToolStarts.get(`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`)
|
|
6454
|
+
: toolActivity?.phase === "terminal"
|
|
6455
|
+
? this.legacyToolStartForTerminal(toolActivity)
|
|
6456
|
+
: undefined;
|
|
6457
|
+
if (legacyStart !== undefined && legacyStart.owner === toolActivity) {
|
|
6458
|
+
if (toolActivity!.phase === "started") {
|
|
6459
|
+
legacyStart.phase = "queued";
|
|
6460
|
+
legacyStart.itemId ??= `legacy-tool-start:${this.nextLegacyToolStartId++}`;
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
// Terminal frames get a distinct pool item identity so they cannot collide
|
|
6464
|
+
// with an in-flight start for the same legacy settlement.
|
|
6465
|
+
const poolItemId =
|
|
6466
|
+
legacyStart !== undefined && legacyStart.owner === toolActivity
|
|
6467
|
+
? toolActivity!.phase === "terminal"
|
|
6468
|
+
? `legacy-tool-terminal:${this.nextLegacyToolStartId++}`
|
|
6469
|
+
: legacyStart.itemId
|
|
6470
|
+
: undefined;
|
|
6471
|
+
const submitted = this.submitPool({
|
|
6219
6472
|
sessionId,
|
|
6220
6473
|
lane: send.lane,
|
|
6221
6474
|
coalesceKey: send.coalesceKey,
|
|
6475
|
+
...(poolItemId !== undefined ? { itemId: poolItemId } : {}),
|
|
6222
6476
|
payload: {
|
|
6223
6477
|
send,
|
|
6224
6478
|
topicLease,
|
|
6225
6479
|
...(socketLease ? { socketLease } : {}),
|
|
6226
6480
|
...(toolActivity ? { toolActivity } : {}),
|
|
6481
|
+
...(legacyStart !== undefined && toolActivity?.phase === "terminal"
|
|
6482
|
+
? { legacyToolStart: legacyStart }
|
|
6483
|
+
: {}),
|
|
6227
6484
|
},
|
|
6228
6485
|
});
|
|
6486
|
+
if (!submitted) {
|
|
6487
|
+
this.settleRejectedLegacyToolSubmission(toolActivity, legacyStart);
|
|
6488
|
+
return;
|
|
6489
|
+
}
|
|
6229
6490
|
await this.flushPool();
|
|
6230
6491
|
}
|
|
6231
6492
|
|
|
@@ -6295,8 +6556,16 @@ export class TelegramNotificationDaemon {
|
|
|
6295
6556
|
): void {
|
|
6296
6557
|
const logicalSessionId = this.#logicalSessionId(session);
|
|
6297
6558
|
const socketLease = this.#socketLease(session, logicalSessionId);
|
|
6298
|
-
if (!socketLease)
|
|
6559
|
+
if (!socketLease) {
|
|
6560
|
+
this.failLegacyToolStart(toolActivity);
|
|
6561
|
+
return;
|
|
6562
|
+
}
|
|
6299
6563
|
const frames = this.pendingThreadedFrames.get(logicalSessionId) ?? [];
|
|
6564
|
+
const legacyStart =
|
|
6565
|
+
toolActivity?.phase === "started"
|
|
6566
|
+
? this.legacyToolStarts.get(`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`)
|
|
6567
|
+
: undefined;
|
|
6568
|
+
if (legacyStart !== undefined && legacyStart.owner === toolActivity) legacyStart.phase = "pending_identity";
|
|
6300
6569
|
frames.push({
|
|
6301
6570
|
send,
|
|
6302
6571
|
msg,
|
|
@@ -6304,7 +6573,10 @@ export class TelegramNotificationDaemon {
|
|
|
6304
6573
|
socketLease,
|
|
6305
6574
|
...(toolActivity ? { toolActivity } : {}),
|
|
6306
6575
|
});
|
|
6307
|
-
if (frames.length > PENDING_TOPIC_FRAME_LIMIT)
|
|
6576
|
+
if (frames.length > PENDING_TOPIC_FRAME_LIMIT) {
|
|
6577
|
+
const evicted = frames.shift();
|
|
6578
|
+
this.failLegacyToolStart(evicted?.toolActivity);
|
|
6579
|
+
}
|
|
6308
6580
|
this.pendingThreadedFrames.set(logicalSessionId, frames);
|
|
6309
6581
|
}
|
|
6310
6582
|
|
|
@@ -6313,8 +6585,14 @@ export class TelegramNotificationDaemon {
|
|
|
6313
6585
|
if (!frames || frames.length === 0) return;
|
|
6314
6586
|
this.pendingThreadedFrames.delete(sessionId);
|
|
6315
6587
|
for (const frame of frames) {
|
|
6316
|
-
if (
|
|
6317
|
-
|
|
6588
|
+
if (
|
|
6589
|
+
frame.logicalSessionId !== sessionId ||
|
|
6590
|
+
!this.#leaseTokenAllows(frame.socketLease) ||
|
|
6591
|
+
(frame.msg.type === "tool_activity" && this.opts.toolActivity?.enabled !== true)
|
|
6592
|
+
) {
|
|
6593
|
+
this.failLegacyToolStart(frame.toolActivity);
|
|
6594
|
+
continue;
|
|
6595
|
+
}
|
|
6318
6596
|
await this.submitThreadedFrame(sessionId, frame.send, topicLease, frame.toolActivity, frame.socketLease);
|
|
6319
6597
|
}
|
|
6320
6598
|
}
|
|
@@ -6937,6 +7215,7 @@ export class TelegramNotificationDaemon {
|
|
|
6937
7215
|
this.finishSelectedAck(expiredItem.payload.selectedAck, { status: "failed", reason: "expired" });
|
|
6938
7216
|
}
|
|
6939
7217
|
expiredItem.payload.btwDelivery?.finish("not_delivered");
|
|
7218
|
+
this.failLegacyToolStart(expiredItem.payload.toolActivity);
|
|
6940
7219
|
}
|
|
6941
7220
|
// Within a batch a finalized frame supersedes any still-queued live frame for
|
|
6942
7221
|
// the same streamed message (finalized outranks live), so drop the stale live
|
|
@@ -6961,17 +7240,18 @@ export class TelegramNotificationDaemon {
|
|
|
6961
7240
|
}
|
|
6962
7241
|
for (const item of batch) {
|
|
6963
7242
|
const toolActivity = item.payload.toolActivity;
|
|
6964
|
-
|
|
6965
|
-
toolActivity?.phase === "started"
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
) {
|
|
7243
|
+
const legacyStart =
|
|
7244
|
+
toolActivity?.phase === "started"
|
|
7245
|
+
? this.legacyToolStarts.get(`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`)
|
|
7246
|
+
: undefined;
|
|
7247
|
+
if (legacyStart !== undefined && legacyStart.owner === toolActivity && legacyStart.phase === "queued")
|
|
7248
|
+
legacyStart.phase = "dispatching";
|
|
7249
|
+
if (toolActivity && !this.toolActivityDeliveryIsCurrent(toolActivity)) {
|
|
6971
7250
|
const key = `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`;
|
|
6972
7251
|
const owner = this.toolActivityOwners.get(key);
|
|
6973
7252
|
if (!this.liveMessages.has(key) && owner?.session === toolActivity.session)
|
|
6974
7253
|
this.toolActivityOwners.delete(key);
|
|
7254
|
+
this.failLegacyToolStart(toolActivity);
|
|
6975
7255
|
this.pool.settle(item.itemId!, "removed");
|
|
6976
7256
|
continue;
|
|
6977
7257
|
}
|
|
@@ -7102,17 +7382,20 @@ export class TelegramNotificationDaemon {
|
|
|
7102
7382
|
}
|
|
7103
7383
|
continue;
|
|
7104
7384
|
}
|
|
7105
|
-
|
|
7385
|
+
let { send } = item.payload;
|
|
7386
|
+
const { topicLease, socketLease } = item.payload;
|
|
7106
7387
|
if (
|
|
7107
7388
|
(socketLease && !this.#leaseTokenAllows(socketLease)) ||
|
|
7108
7389
|
(topicLease && !this.topicLeaseIsCurrent(topicLease))
|
|
7109
7390
|
) {
|
|
7110
7391
|
this.pool.settle(item.itemId!, "rejected");
|
|
7392
|
+
this.failLegacyToolStart(toolActivity);
|
|
7111
7393
|
continue;
|
|
7112
7394
|
}
|
|
7113
7395
|
const topicId = topicLease?.topicId;
|
|
7114
7396
|
if (topicId && !(await this.pairedChatIsPrivate())) {
|
|
7115
7397
|
this.pool.settle(item.itemId!, "rejected");
|
|
7398
|
+
this.failLegacyToolStart(toolActivity);
|
|
7116
7399
|
continue;
|
|
7117
7400
|
}
|
|
7118
7401
|
if (
|
|
@@ -7120,12 +7403,20 @@ export class TelegramNotificationDaemon {
|
|
|
7120
7403
|
(topicLease && !this.topicLeaseIsCurrent(topicLease))
|
|
7121
7404
|
) {
|
|
7122
7405
|
this.pool.settle(item.itemId!, "rejected");
|
|
7406
|
+
this.failLegacyToolStart(toolActivity);
|
|
7123
7407
|
continue;
|
|
7124
7408
|
}
|
|
7125
7409
|
if (item.payload.toolActivity && !this.toolActivityAuthorityIsCurrent(item.payload.toolActivity)) {
|
|
7126
7410
|
this.pool.settle(item.itemId!, "removed");
|
|
7411
|
+
this.failLegacyToolStart(toolActivity);
|
|
7127
7412
|
continue;
|
|
7128
7413
|
}
|
|
7414
|
+
if (
|
|
7415
|
+
toolActivity?.phase === "terminal" &&
|
|
7416
|
+
!this.toolActivitySummariesAreCurrent(toolActivity) &&
|
|
7417
|
+
toolActivity.summaryFreeSend
|
|
7418
|
+
)
|
|
7419
|
+
send = toolActivity.summaryFreeSend;
|
|
7129
7420
|
// Threaded topic when available; otherwise deliver flat to the paired chat.
|
|
7130
7421
|
const threadField = topicId ? { message_thread_id: Number(topicId) } : {};
|
|
7131
7422
|
const ckey = send.editable ? item.coalesceKey : undefined;
|
|
@@ -7362,23 +7653,74 @@ export class TelegramNotificationDaemon {
|
|
|
7362
7653
|
const rejected = itemOutcomes.some(outcome => outcome.kind === "rejected");
|
|
7363
7654
|
if (retryable) {
|
|
7364
7655
|
if (!accepted) {
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7656
|
+
let retryItemId: string | undefined;
|
|
7657
|
+
let retryAllowed = true;
|
|
7658
|
+
if (toolActivity?.phase === "started") {
|
|
7659
|
+
retryAllowed =
|
|
7660
|
+
!this.toolActivityStopping &&
|
|
7661
|
+
!this.stopRequested &&
|
|
7662
|
+
this.opts.toolActivity?.enabled === true &&
|
|
7663
|
+
toolActivity.policyEpoch === this.toolActivityPolicyEpoch &&
|
|
7664
|
+
this.toolActivityAuthorityIsCurrent(toolActivity);
|
|
7665
|
+
if (toolActivity.session.toolActivityCapability === "v1") {
|
|
7666
|
+
const state = this.legacyToolStarts.get(
|
|
7667
|
+
`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`,
|
|
7668
|
+
);
|
|
7669
|
+
retryAllowed =
|
|
7670
|
+
retryAllowed &&
|
|
7671
|
+
state !== undefined &&
|
|
7672
|
+
state.owner === toolActivity &&
|
|
7673
|
+
!state.terminalClaimed;
|
|
7674
|
+
if (state !== undefined && state.owner === toolActivity) {
|
|
7675
|
+
if (retryAllowed) {
|
|
7676
|
+
state.phase = "queued";
|
|
7677
|
+
state.itemId = `legacy-tool-start:${this.nextLegacyToolStartId++}`;
|
|
7678
|
+
retryItemId = state.itemId;
|
|
7679
|
+
} else {
|
|
7680
|
+
this.settleLegacyToolStart(state, "cancelled");
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
} else if (retryAllowed) {
|
|
7684
|
+
retryItemId = `tool-activity-start:${this.nextLegacyToolStartId++}`;
|
|
7685
|
+
}
|
|
7686
|
+
} else if (toolActivity?.phase === "terminal" && item.payload.legacyToolStart !== undefined) {
|
|
7687
|
+
retryItemId = `legacy-tool-terminal:${this.nextLegacyToolStartId++}`;
|
|
7688
|
+
}
|
|
7689
|
+
const newerCoalescedItem =
|
|
7690
|
+
toolActivity === undefined &&
|
|
7691
|
+
item.coalesceKey !== undefined &&
|
|
7692
|
+
this.pool.someQueued(
|
|
7693
|
+
queued =>
|
|
7694
|
+
queued.sessionId === item.sessionId &&
|
|
7695
|
+
queued.lane === item.lane &&
|
|
7696
|
+
queued.coalesceKey === item.coalesceKey,
|
|
7697
|
+
);
|
|
7698
|
+
if (retryAllowed && !newerCoalescedItem) {
|
|
7699
|
+
this.submitPool({
|
|
7700
|
+
sessionId: item.sessionId,
|
|
7701
|
+
lane: item.lane,
|
|
7702
|
+
coalesceKey: item.coalesceKey,
|
|
7703
|
+
deadlineAt: item.deadlineAt,
|
|
7704
|
+
...(retryItemId !== undefined ? { itemId: retryItemId } : {}),
|
|
7705
|
+
payload: item.payload,
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7372
7708
|
}
|
|
7373
7709
|
disposition = "ambiguous";
|
|
7374
7710
|
} else if (disposition === "accepted" && !accepted && rejected) {
|
|
7375
7711
|
disposition = "rejected";
|
|
7376
7712
|
}
|
|
7377
7713
|
this.pool.settle(item.itemId!, disposition);
|
|
7714
|
+
if (toolActivity?.phase === "started" && !retryable) this.failLegacyToolStart(toolActivity);
|
|
7715
|
+
if (toolActivity?.phase === "terminal" && !retryable) {
|
|
7716
|
+
const state = item.payload.legacyToolStart;
|
|
7717
|
+
if (state !== undefined && this.legacyToolStarts.get(state.key) === state)
|
|
7718
|
+
this.settleLegacyToolStart(state, "terminal");
|
|
7719
|
+
}
|
|
7378
7720
|
// A terminal tool frame owns the end of this coalescing key even when both
|
|
7379
7721
|
// edit and fallback delivery fail. Retaining the old message id would leak
|
|
7380
7722
|
// one entry per failure and let a later reused key edit stale Telegram state.
|
|
7381
|
-
if (send.terminal && editKey) {
|
|
7723
|
+
if (send.terminal && editKey && !retryable) {
|
|
7382
7724
|
this.liveMessages.delete(editKey);
|
|
7383
7725
|
const owner = this.toolActivityOwners.get(editKey);
|
|
7384
7726
|
if (!item.payload.toolActivity || owner?.session === item.payload.toolActivity.session)
|
|
@@ -7414,6 +7756,18 @@ export class TelegramNotificationDaemon {
|
|
|
7414
7756
|
}
|
|
7415
7757
|
}
|
|
7416
7758
|
if (toolActivity) {
|
|
7759
|
+
const legacyStart = this.legacyToolStarts.get(mapKey);
|
|
7760
|
+
if (
|
|
7761
|
+
legacyStart !== undefined &&
|
|
7762
|
+
legacyStart.owner === toolActivity &&
|
|
7763
|
+
(toolActivity.session.toolActivityCapability !== "v1" ||
|
|
7764
|
+
legacyStart.policyEpoch !== this.toolActivityPolicyEpoch ||
|
|
7765
|
+
!this.toolActivityAuthorityIsCurrent(toolActivity))
|
|
7766
|
+
) {
|
|
7767
|
+
this.settleLegacyToolStart(legacyStart, "terminal");
|
|
7768
|
+
void this.enqueueToolTerminalization([{ messageId, owner: toolActivity }], false);
|
|
7769
|
+
return;
|
|
7770
|
+
}
|
|
7417
7771
|
const owner = this.toolActivityOwners.get(mapKey);
|
|
7418
7772
|
if (this.revokedToolEndpoints.has(toolActivity.endpointDigest) || owner?.session !== toolActivity.session) {
|
|
7419
7773
|
void this.enqueueToolTerminalization([{ messageId, owner: toolActivity }], false);
|
|
@@ -7422,6 +7776,10 @@ export class TelegramNotificationDaemon {
|
|
|
7422
7776
|
}
|
|
7423
7777
|
this.liveMessages.set(mapKey, messageId);
|
|
7424
7778
|
if (toolActivity) this.toolActivityOwners.set(mapKey, toolActivity);
|
|
7779
|
+
if (toolActivity) {
|
|
7780
|
+
const state = this.legacyToolStarts.get(mapKey);
|
|
7781
|
+
if (state !== undefined && state.owner === toolActivity) this.settleLegacyToolStart(state, "visible");
|
|
7782
|
+
}
|
|
7425
7783
|
}
|
|
7426
7784
|
|
|
7427
7785
|
/**
|
|
@@ -7440,16 +7798,50 @@ export class TelegramNotificationDaemon {
|
|
|
7440
7798
|
socketLease?: { session: SessionSocket; token: number; logicalSessionId: string },
|
|
7441
7799
|
): Promise<void> {
|
|
7442
7800
|
if ((socketLease && !this.#leaseTokenAllows(socketLease)) || !(await this.pairedChatIsPrivate())) return;
|
|
7801
|
+
if (toolActivity && !this.toolActivityDeliveryIsCurrent(toolActivity)) return;
|
|
7443
7802
|
if (socketLease && !this.#leaseTokenAllows(socketLease)) return;
|
|
7444
7803
|
await this.notifyThreadedFallback(socketLease);
|
|
7804
|
+
if (toolActivity && !this.toolActivityDeliveryIsCurrent(toolActivity)) return;
|
|
7445
7805
|
if (socketLease && !this.#leaseTokenAllows(socketLease)) return;
|
|
7806
|
+
if (toolActivity && !this.toolActivityDeliveryIsCurrent(toolActivity)) return;
|
|
7446
7807
|
if (send.identity && this.flatIdentitySent.has(sessionId)) return;
|
|
7447
|
-
this.
|
|
7808
|
+
if (toolActivity && !this.toolActivityDeliveryIsCurrent(toolActivity)) return;
|
|
7809
|
+
const legacyToolStart =
|
|
7810
|
+
toolActivity?.phase === "started"
|
|
7811
|
+
? this.legacyToolStarts.get(`${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`)
|
|
7812
|
+
: toolActivity?.phase === "terminal"
|
|
7813
|
+
? this.legacyToolStartForTerminal(toolActivity)
|
|
7814
|
+
: undefined;
|
|
7815
|
+
if (
|
|
7816
|
+
legacyToolStart !== undefined &&
|
|
7817
|
+
legacyToolStart.owner === toolActivity &&
|
|
7818
|
+
toolActivity?.phase === "started"
|
|
7819
|
+
) {
|
|
7820
|
+
legacyToolStart.phase = "queued";
|
|
7821
|
+
legacyToolStart.itemId ??= `legacy-tool-start:${this.nextLegacyToolStartId++}`;
|
|
7822
|
+
}
|
|
7823
|
+
const poolItemId =
|
|
7824
|
+
legacyToolStart !== undefined && legacyToolStart.owner === toolActivity
|
|
7825
|
+
? toolActivity!.phase === "terminal"
|
|
7826
|
+
? `legacy-tool-terminal:${this.nextLegacyToolStartId++}`
|
|
7827
|
+
: legacyToolStart.itemId
|
|
7828
|
+
: undefined;
|
|
7829
|
+
const submitted = this.submitPool({
|
|
7448
7830
|
sessionId,
|
|
7449
7831
|
lane: send.lane,
|
|
7450
7832
|
coalesceKey: send.coalesceKey,
|
|
7451
|
-
|
|
7833
|
+
...(poolItemId !== undefined ? { itemId: poolItemId } : {}),
|
|
7834
|
+
payload: {
|
|
7835
|
+
send,
|
|
7836
|
+
...(socketLease ? { socketLease } : {}),
|
|
7837
|
+
...(toolActivity ? { toolActivity } : {}),
|
|
7838
|
+
...(legacyToolStart !== undefined && toolActivity?.phase === "terminal" ? { legacyToolStart } : {}),
|
|
7839
|
+
},
|
|
7452
7840
|
});
|
|
7841
|
+
if (!submitted) {
|
|
7842
|
+
this.settleRejectedLegacyToolSubmission(toolActivity, legacyToolStart);
|
|
7843
|
+
return;
|
|
7844
|
+
}
|
|
7453
7845
|
await this.flushPool();
|
|
7454
7846
|
if (socketLease && !this.#leaseTokenAllows(socketLease)) return;
|
|
7455
7847
|
if (send.identity) this.flatIdentitySent.add(sessionId);
|
|
@@ -7708,6 +8100,10 @@ export class TelegramNotificationDaemon {
|
|
|
7708
8100
|
async handleSessionMessage(session: SessionSocket, msg: any): Promise<void> {
|
|
7709
8101
|
if (msg?.type === "hello") {
|
|
7710
8102
|
const capabilities = Array.isArray(msg.capabilities) ? msg.capabilities : [];
|
|
8103
|
+
const previousToolActivityCapability = session.toolActivityCapability;
|
|
8104
|
+
session.toolActivityCapability = negotiateToolActivityCapability(previousToolActivityCapability, capabilities);
|
|
8105
|
+
if (previousToolActivityCapability === "v1" && session.toolActivityCapability === "v2")
|
|
8106
|
+
this.cleanLegacyToolStartsForCapabilityUpgrade(session);
|
|
7711
8107
|
if (capabilities.includes("ephemeral_turn_v1")) {
|
|
7712
8108
|
session.ephemeralCapable = true;
|
|
7713
8109
|
this.#resumeBtwTurnsForSession(session);
|
|
@@ -7719,7 +8115,7 @@ export class TelegramNotificationDaemon {
|
|
|
7719
8115
|
return;
|
|
7720
8116
|
}
|
|
7721
8117
|
if (session.replayPending) {
|
|
7722
|
-
if (msg?.type === "tool_activity" && (this.opts.toolActivity?.enabled
|
|
8118
|
+
if (msg?.type === "tool_activity" && (this.opts.toolActivity?.enabled !== true || this.toolActivityStopping))
|
|
7723
8119
|
return;
|
|
7724
8120
|
const matchingReplay = msg?.type === "event_replay_result" && msg.id === session.replayId;
|
|
7725
8121
|
if (!matchingReplay) {
|
|
@@ -8073,34 +8469,76 @@ export class TelegramNotificationDaemon {
|
|
|
8073
8469
|
return;
|
|
8074
8470
|
}
|
|
8075
8471
|
if (typeof msg?.type === "string" && TelegramNotificationDaemon.THREADED_FRAMES.has(msg.type)) {
|
|
8076
|
-
|
|
8472
|
+
let threadedFrame = msg as Record<string, unknown>;
|
|
8473
|
+
let legacyUnknownStart: LegacyToolStartSettlement | undefined;
|
|
8474
|
+
if (threadedFrame.type === "tool_activity" && threadedFrame.phase === "unknown") {
|
|
8475
|
+
const toolCallId = typeof threadedFrame.toolCallId === "string" ? threadedFrame.toolCallId : undefined;
|
|
8476
|
+
const toolName = typeof threadedFrame.toolName === "string" ? threadedFrame.toolName : undefined;
|
|
8477
|
+
const liveKey = toolCallId ? `${this.#logicalSessionId(session)}:tool:${toolCallId}` : undefined;
|
|
8478
|
+
const state = liveKey ? this.legacyToolStarts.get(liveKey) : undefined;
|
|
8479
|
+
if (
|
|
8480
|
+
session.toolActivityCapability !== "v1" ||
|
|
8481
|
+
!state ||
|
|
8482
|
+
state.owner.session !== session ||
|
|
8483
|
+
state.owner.toolName !== toolName ||
|
|
8484
|
+
state.terminalClaimed
|
|
8485
|
+
)
|
|
8486
|
+
return;
|
|
8487
|
+
state.terminalClaimed = true;
|
|
8488
|
+
if (this.cancelUnsentLegacyToolStart(state)) return;
|
|
8489
|
+
if (state.phase === "dispatching") await state.settled;
|
|
8490
|
+
if (
|
|
8491
|
+
state.settledOutcome !== "visible" ||
|
|
8492
|
+
this.legacyToolStarts.get(state.key) !== state ||
|
|
8493
|
+
session.toolActivityCapability !== "v1" ||
|
|
8494
|
+
state.owner.session !== session ||
|
|
8495
|
+
state.policyEpoch !== this.toolActivityPolicyEpoch ||
|
|
8496
|
+
!this.toolActivityAuthorityIsCurrent(state.owner) ||
|
|
8497
|
+
this.toolActivityOwners.get(state.key) !== state.owner ||
|
|
8498
|
+
!this.liveMessages.has(state.key)
|
|
8499
|
+
)
|
|
8500
|
+
return;
|
|
8501
|
+
legacyUnknownStart = state;
|
|
8502
|
+
threadedFrame = { ...this.toolActivityFrameWithoutSummaries(threadedFrame), phase: "cancelled" };
|
|
8503
|
+
}
|
|
8077
8504
|
const toolActivity = this.toolActivityOwner(session, threadedFrame);
|
|
8505
|
+
if (threadedFrame.type === "tool_activity" && !toolActivity) return;
|
|
8078
8506
|
const toolAdmissionEpoch = toolActivity
|
|
8079
8507
|
? (this.replayToolActivityEpochs.get(threadedFrame) ?? this.toolActivityPolicyEpoch)
|
|
8080
8508
|
: undefined;
|
|
8081
8509
|
if (toolActivity) toolActivity.policyEpoch = toolAdmissionEpoch;
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
(
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8510
|
+
if (toolActivity?.phase === "started" && toolActivity.session.toolActivityCapability === "v1") {
|
|
8511
|
+
const state = this.admitLegacyToolStart(toolActivity);
|
|
8512
|
+
if (!state) return;
|
|
8513
|
+
}
|
|
8514
|
+
if (legacyUnknownStart && toolActivity?.phase === "terminal") {
|
|
8515
|
+
if (
|
|
8516
|
+
legacyUnknownStart.owner.session !== toolActivity.session ||
|
|
8517
|
+
legacyUnknownStart.key !== `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`
|
|
8518
|
+
)
|
|
8519
|
+
return;
|
|
8520
|
+
}
|
|
8521
|
+
const toolFrameIsCurrent = (): boolean => !toolActivity || this.toolActivityDeliveryIsCurrent(toolActivity);
|
|
8088
8522
|
const abandonStaleToolStart = (): void => {
|
|
8089
8523
|
if (toolActivity?.phase !== "started") return;
|
|
8090
8524
|
const key = `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`;
|
|
8091
8525
|
const owner = this.toolActivityOwners.get(key);
|
|
8092
8526
|
if (!this.liveMessages.has(key) && owner?.session === toolActivity.session)
|
|
8093
8527
|
this.toolActivityOwners.delete(key);
|
|
8528
|
+
this.failLegacyToolStart(toolActivity);
|
|
8094
8529
|
};
|
|
8095
8530
|
if (toolActivity) {
|
|
8096
8531
|
const liveKey = `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`;
|
|
8097
8532
|
const currentOwner = this.toolActivityOwners.get(liveKey);
|
|
8098
8533
|
if (toolActivity.phase === "started") {
|
|
8099
|
-
if (!
|
|
8534
|
+
if (!toolFrameIsCurrent()) {
|
|
8535
|
+
this.failLegacyToolStart(toolActivity);
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8100
8538
|
this.toolActivityOwners.set(liveKey, toolActivity);
|
|
8101
8539
|
} else {
|
|
8102
8540
|
if (currentOwner && currentOwner.session !== session) return;
|
|
8103
|
-
if (this.opts.toolActivity?.enabled
|
|
8541
|
+
if (this.opts.toolActivity?.enabled !== true) {
|
|
8104
8542
|
if (!currentOwner) return;
|
|
8105
8543
|
if (!this.liveMessages.has(liveKey)) {
|
|
8106
8544
|
// A start may already be granted to the serialized dispatcher but not
|
|
@@ -8116,8 +8554,21 @@ export class TelegramNotificationDaemon {
|
|
|
8116
8554
|
}
|
|
8117
8555
|
}
|
|
8118
8556
|
}
|
|
8119
|
-
const
|
|
8120
|
-
|
|
8557
|
+
const summaryFreeToolFrame =
|
|
8558
|
+
toolActivity?.phase === "terminal" ? this.toolActivityFrameWithoutSummaries(threadedFrame) : threadedFrame;
|
|
8559
|
+
const renderedFrame =
|
|
8560
|
+
toolActivity?.phase === "terminal" && !this.toolActivitySummariesAreCurrent(toolActivity)
|
|
8561
|
+
? summaryFreeToolFrame
|
|
8562
|
+
: threadedFrame;
|
|
8563
|
+
const send = this.renderThreadedFrame(renderedFrame);
|
|
8564
|
+
if (toolActivity?.phase === "terminal") {
|
|
8565
|
+
const summaryFreeSend = this.renderThreadedFrame(summaryFreeToolFrame);
|
|
8566
|
+
if (summaryFreeSend) toolActivity.summaryFreeSend = summaryFreeSend;
|
|
8567
|
+
}
|
|
8568
|
+
if (!send) {
|
|
8569
|
+
this.failLegacyToolStart(toolActivity);
|
|
8570
|
+
return;
|
|
8571
|
+
}
|
|
8121
8572
|
const transportLogicalSessionId = this.#logicalSessionId(session);
|
|
8122
8573
|
// Preserve legacy identity routing for direct/non-replay session callers.
|
|
8123
8574
|
// Authenticated transports never infer topic ownership from display identity:
|
|
@@ -8126,19 +8577,27 @@ export class TelegramNotificationDaemon {
|
|
|
8126
8577
|
send.identity && !session.logicalSessionIdTrusted && this.sessions.get(session.sessionId) !== session
|
|
8127
8578
|
? (this.topicOwnerForIdentity(msg) ?? transportLogicalSessionId)
|
|
8128
8579
|
: transportLogicalSessionId;
|
|
8129
|
-
if (!this.#leaseAllows(session, logicalSessionId))
|
|
8580
|
+
if (!this.#leaseAllows(session, logicalSessionId)) {
|
|
8581
|
+
this.failLegacyToolStart(toolActivity);
|
|
8582
|
+
return;
|
|
8583
|
+
}
|
|
8130
8584
|
const socketLease = this.#socketLease(session, logicalSessionId);
|
|
8131
|
-
if (!socketLease)
|
|
8585
|
+
if (!socketLease) {
|
|
8586
|
+
this.failLegacyToolStart(toolActivity);
|
|
8587
|
+
return;
|
|
8588
|
+
}
|
|
8132
8589
|
const existingTopic = await this.existingTopicForPrivateChat(logicalSessionId);
|
|
8133
|
-
if (!
|
|
8590
|
+
if (!toolFrameIsCurrent()) {
|
|
8134
8591
|
abandonStaleToolStart();
|
|
8135
8592
|
return;
|
|
8136
8593
|
}
|
|
8137
8594
|
if (
|
|
8138
8595
|
this.topics.get(logicalSessionId)?.authorityState === "delete_pending" ||
|
|
8139
8596
|
this.topics.get(logicalSessionId)?.bindingMalformed
|
|
8140
|
-
)
|
|
8597
|
+
) {
|
|
8598
|
+
this.failLegacyToolStart(toolActivity);
|
|
8141
8599
|
return;
|
|
8600
|
+
}
|
|
8142
8601
|
if (!send.identity && !existingTopic && !this.flatIdentitySent.has(logicalSessionId)) {
|
|
8143
8602
|
this.rememberPendingThreadedFrame(session, send, threadedFrame, toolActivity);
|
|
8144
8603
|
return;
|
|
@@ -8151,8 +8610,14 @@ export class TelegramNotificationDaemon {
|
|
|
8151
8610
|
if (
|
|
8152
8611
|
this.topics.get(logicalSessionId)?.authorityState === "delete_pending" ||
|
|
8153
8612
|
this.topics.get(logicalSessionId)?.bindingMalformed
|
|
8154
|
-
)
|
|
8613
|
+
) {
|
|
8614
|
+
this.failLegacyToolStart(toolActivity);
|
|
8155
8615
|
return;
|
|
8616
|
+
}
|
|
8617
|
+
if (!toolFrameIsCurrent()) {
|
|
8618
|
+
abandonStaleToolStart();
|
|
8619
|
+
return;
|
|
8620
|
+
}
|
|
8156
8621
|
await this.deliverFlatFallback(logicalSessionId, send, toolActivity, socketLease);
|
|
8157
8622
|
return;
|
|
8158
8623
|
}
|
|
@@ -8194,7 +8659,7 @@ export class TelegramNotificationDaemon {
|
|
|
8194
8659
|
await this.reconcileUserTopicName(topicLease);
|
|
8195
8660
|
return;
|
|
8196
8661
|
}
|
|
8197
|
-
if (!
|
|
8662
|
+
if (!toolFrameIsCurrent()) {
|
|
8198
8663
|
abandonStaleToolStart();
|
|
8199
8664
|
return;
|
|
8200
8665
|
}
|
|
@@ -8590,6 +9055,7 @@ export class TelegramNotificationDaemon {
|
|
|
8590
9055
|
? "notifications.telegram.rich.enabled"
|
|
8591
9056
|
: "notifications.telegram.toolActivity.enabled";
|
|
8592
9057
|
const label = isRichCommand ? "Rich messages" : "Tool activity";
|
|
9058
|
+
const toolActivityChanged = isRichCommand || this.opts.toolActivity?.enabled !== desired;
|
|
8593
9059
|
try {
|
|
8594
9060
|
await this.opts.settings.set(settingPath, desired);
|
|
8595
9061
|
// Confirm only after the global config write is durable.
|
|
@@ -8603,13 +9069,26 @@ export class TelegramNotificationDaemon {
|
|
|
8603
9069
|
}
|
|
8604
9070
|
if (isRichCommand) {
|
|
8605
9071
|
this.opts.rich = { enabled: desired };
|
|
8606
|
-
} else {
|
|
9072
|
+
} else if (toolActivityChanged) {
|
|
8607
9073
|
this.toolActivityPolicyEpoch++;
|
|
8608
9074
|
this.opts.toolActivity = { enabled: desired };
|
|
9075
|
+
const legacySettlements = this.cancelLegacyToolStartsForPolicyTransition();
|
|
8609
9076
|
if (!desired) {
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
9077
|
+
// Unsent legacy starts are cancelled by exact item/frame identity; a
|
|
9078
|
+
// dispatch already inside Bot API is joined through its own settlement.
|
|
9079
|
+
await legacySettlements;
|
|
9080
|
+
await this.flushChain;
|
|
9081
|
+
const removedTools = this.pool.removeWhere(item => {
|
|
9082
|
+
const toolActivity = item.payload.toolActivity;
|
|
9083
|
+
if (!toolActivity) return false;
|
|
9084
|
+
if (toolActivity.phase === "started") return true;
|
|
9085
|
+
// Keep a terminal only when its exact start is already visible. A
|
|
9086
|
+
// queued or pending start is cancelled above and cannot retain a
|
|
9087
|
+
// terminal merely because its owner record has not been swept yet.
|
|
9088
|
+
const key = `${toolActivity.sessionId}:tool:${toolActivity.toolCallId}`;
|
|
9089
|
+
const owner = this.toolActivityOwners.get(key);
|
|
9090
|
+
return !this.liveMessages.has(key) || owner?.session !== toolActivity.session;
|
|
9091
|
+
});
|
|
8613
9092
|
for (const item of removedTools) {
|
|
8614
9093
|
const toolActivity = item.payload.toolActivity;
|
|
8615
9094
|
if (!toolActivity) continue;
|
|
@@ -8639,7 +9118,6 @@ export class TelegramNotificationDaemon {
|
|
|
8639
9118
|
// future starts are rejected immediately, queued starts are removed,
|
|
8640
9119
|
// and any already-granted Bot API effect settles before the off
|
|
8641
9120
|
// acknowledgement. Visible starts may still receive their terminal edit.
|
|
8642
|
-
await this.flushChain;
|
|
8643
9121
|
}
|
|
8644
9122
|
await reply(`${label}: ${desired ? "on" : "off"}`);
|
|
8645
9123
|
return;
|