@code-yeongyu/senpi 2026.9.2 → 2026.9.3
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 +124 -2
- package/dist/beta/omo-local-update-fingerprint.d.ts.map +1 -1
- package/dist/beta/omo-local-update-fingerprint.js +0 -1
- package/dist/beta/omo-local-update-fingerprint.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dynamic-prompt/build.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/build.js +2 -5
- package/dist/core/dynamic-prompt/build.js.map +1 -1
- package/dist/core/dynamic-prompt/index.d.ts +1 -2
- package/dist/core/dynamic-prompt/index.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/index.js +1 -2
- package/dist/core/dynamic-prompt/index.js.map +1 -1
- package/dist/core/dynamic-prompt/intent-gate.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/intent-gate.js +9 -24
- package/dist/core/dynamic-prompt/intent-gate.js.map +1 -1
- package/dist/core/dynamic-prompt/policies.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/policies.js +3 -7
- package/dist/core/dynamic-prompt/policies.js.map +1 -1
- package/dist/core/dynamic-prompt/style.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/style.js +6 -8
- package/dist/core/dynamic-prompt/style.js.map +1 -1
- package/dist/core/dynamic-prompt/verification.js +1 -1
- package/dist/core/dynamic-prompt/verification.js.map +1 -1
- package/dist/core/dynamic-prompt/working-task.d.ts +2 -0
- package/dist/core/dynamic-prompt/working-task.d.ts.map +1 -0
- package/dist/core/dynamic-prompt/working-task.js +10 -0
- package/dist/core/dynamic-prompt/working-task.js.map +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/options.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/options.js +6 -2
- package/dist/core/extensions/builtin/claude-sdk-oauth/options.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/stream.js +2 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/extension-wiring.js +1 -1
- package/dist/core/extensions/builtin/compaction/extension-wiring.js.map +1 -1
- package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts +1 -1
- package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts.map +1 -1
- package/dist/core/extensions/builtin/config-reload/watch-event-source.js +27 -9
- package/dist/core/extensions/builtin/config-reload/watch-event-source.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +52 -50
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +2 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.js +7 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.js +7 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-6.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.js +7 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-7.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.js +7 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-4-8.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +2 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +50 -0
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +66 -0
- package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts +5 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +6 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-6.js +11 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-6.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.js +11 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +2 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js +8 -2
- package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +8 -3
- package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts +4 -3
- package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.js +18 -18
- package/dist/core/extensions/builtin/terminal/monitor-notify.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +7 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +37 -8
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-status.js +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-status.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/prompt.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/prompt.js +17 -13
- package/dist/core/extensions/builtin/terminal/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash-output.js +14 -2
- package/dist/core/extensions/builtin/terminal/tools/bash-output.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +27 -10
- package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +17 -8
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +5 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts +6 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
- package/dist/modes/rpc/host-ensure.js +36 -9
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +16 -4
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.d.ts +7 -4
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +7 -4
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +8 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +62 -7
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +22 -0
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +132 -94
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-fanout.d.ts +40 -0
- package/dist/modes/rpc/session-event-fanout.d.ts.map +1 -0
- package/dist/modes/rpc/session-event-fanout.js +146 -0
- package/dist/modes/rpc/session-event-fanout.js.map +1 -0
- package/dist/modes/rpc/session-event-writer.d.ts +6 -13
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +48 -96
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +9 -8
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +14 -42
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/session-teardown.d.ts +12 -0
- package/dist/modes/rpc/session-teardown.d.ts.map +1 -0
- package/dist/modes/rpc/session-teardown.js +117 -0
- package/dist/modes/rpc/session-teardown.js.map +1 -0
- package/docs/rpc.md +10 -6
- package/docs/settings.md +4 -4
- package/docs/terminal-tools.md +26 -6
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +57 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +4 -2
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/skill-contribution.ts +43 -9
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +4 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/SKILL.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/skill/bun-1-4/references/runtime-apis.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +3 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +3 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js +26 -4
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google-vertex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/profiles.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/profiles.js +3 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry-profile/profiles.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/core/dynamic-prompt/exploration.d.ts +0 -2
- package/dist/core/dynamic-prompt/exploration.d.ts.map +0 -1
- package/dist/core/dynamic-prompt/exploration.js +0 -6
- package/dist/core/dynamic-prompt/exploration.js.map +0 -1
- package/dist/core/dynamic-prompt/parallel-tools.d.ts +0 -2
- package/dist/core/dynamic-prompt/parallel-tools.d.ts.map +0 -1
- package/dist/core/dynamic-prompt/parallel-tools.js +0 -8
- package/dist/core/dynamic-prompt/parallel-tools.js.map +0 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { SocketEventSinkActor } from "./socket-event-fanout.js";
|
|
2
|
+
export const RENDERED_COMPONENT_RECORD = "__senpiRenderedComponent";
|
|
3
|
+
const BROADCAST_LIFECYCLE_RECORDS = new Set([
|
|
4
|
+
"agent_start",
|
|
5
|
+
"agent_settled",
|
|
6
|
+
"agent_idle",
|
|
7
|
+
"session_opened",
|
|
8
|
+
"session_closed",
|
|
9
|
+
]);
|
|
10
|
+
export class SessionEventFanout {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.connections = new Map();
|
|
13
|
+
this.sessionSnapshots = new Map();
|
|
14
|
+
this.connectionCapabilities = new Map();
|
|
15
|
+
this.connectionSessions = new Map();
|
|
16
|
+
this.registeredCapabilityConnections = new Set();
|
|
17
|
+
}
|
|
18
|
+
registerConnection(id, connection) {
|
|
19
|
+
const actor = new SocketEventSinkActor(connection, () => {
|
|
20
|
+
if (this.connections.get(id)?.actor === actor) {
|
|
21
|
+
this.connections.delete(id);
|
|
22
|
+
this.connectionCapabilities.delete(id);
|
|
23
|
+
this.connectionSessions.delete(id);
|
|
24
|
+
this.registeredCapabilityConnections.delete(id);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
this.connections.set(id, { connection, actor });
|
|
28
|
+
this.connectionCapabilities.set(id, new Set());
|
|
29
|
+
this.connectionSessions.set(id, new Set());
|
|
30
|
+
}
|
|
31
|
+
unregisterConnection(id) {
|
|
32
|
+
const registered = this.connections.get(id);
|
|
33
|
+
if (!registered)
|
|
34
|
+
return;
|
|
35
|
+
registered.actor.close();
|
|
36
|
+
this.connections.delete(id);
|
|
37
|
+
this.connectionCapabilities.delete(id);
|
|
38
|
+
this.connectionSessions.delete(id);
|
|
39
|
+
this.registeredCapabilityConnections.delete(id);
|
|
40
|
+
}
|
|
41
|
+
attachConnectionToSession(id, sessionId) {
|
|
42
|
+
if (!this.connections.has(id))
|
|
43
|
+
return;
|
|
44
|
+
const sessions = this.connectionSessions.get(id) ?? new Set();
|
|
45
|
+
if (sessions.has(sessionId))
|
|
46
|
+
return;
|
|
47
|
+
sessions.add(sessionId);
|
|
48
|
+
this.connectionSessions.set(id, sessions);
|
|
49
|
+
this.replaySnapshot(id, sessionId);
|
|
50
|
+
}
|
|
51
|
+
detachConnectionFromSession(id, sessionId) {
|
|
52
|
+
this.connectionSessions.get(id)?.delete(sessionId);
|
|
53
|
+
}
|
|
54
|
+
setConnectionCapabilities(id, capabilities) {
|
|
55
|
+
const registered = this.connections.get(id);
|
|
56
|
+
if (!registered)
|
|
57
|
+
return;
|
|
58
|
+
const wasCapable = this.connectionCapabilities.get(id)?.has("rendered_components") ?? false;
|
|
59
|
+
this.connectionCapabilities.set(id, new Set(capabilities));
|
|
60
|
+
this.registeredCapabilityConnections.add(id);
|
|
61
|
+
if (!wasCapable && capabilities.includes("rendered_components"))
|
|
62
|
+
for (const sessionId of this.connectionSessions.get(id) ?? [])
|
|
63
|
+
this.replayRendered(id, sessionId);
|
|
64
|
+
}
|
|
65
|
+
clearConnectionCapabilities(id) {
|
|
66
|
+
if (this.connections.has(id)) {
|
|
67
|
+
this.connectionCapabilities.set(id, new Set());
|
|
68
|
+
this.registeredCapabilityConnections.delete(id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
hasRegisteredConnectionCapabilities(id) {
|
|
72
|
+
return this.registeredCapabilityConnections.has(id);
|
|
73
|
+
}
|
|
74
|
+
getConnectionCapabilities(id) {
|
|
75
|
+
if (!this.registeredCapabilityConnections.has(id))
|
|
76
|
+
return undefined;
|
|
77
|
+
return [...(this.connectionCapabilities.get(id) ?? [])];
|
|
78
|
+
}
|
|
79
|
+
hasCapableConnection(sessionId) {
|
|
80
|
+
for (const [id, capabilities] of this.connectionCapabilities)
|
|
81
|
+
if (capabilities.has("rendered_components") && this.connectionSessions.get(id)?.has(sessionId))
|
|
82
|
+
return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
targets(sessionId, targetId, isTargeted, rendered, recordType) {
|
|
86
|
+
if (isTargeted)
|
|
87
|
+
return [targetId];
|
|
88
|
+
if (typeof recordType === "string" && BROADCAST_LIFECYCLE_RECORDS.has(recordType))
|
|
89
|
+
return this.connections.size > 0 ? [...this.connections.keys()] : [undefined];
|
|
90
|
+
if (rendered)
|
|
91
|
+
return this.connections.size > 0
|
|
92
|
+
? [...this.connections.keys()].filter((id) => this.connectionCapabilities.get(id)?.has("rendered_components") &&
|
|
93
|
+
this.connectionSessions.get(id)?.has(sessionId))
|
|
94
|
+
: [undefined];
|
|
95
|
+
return this.connections.size > 0
|
|
96
|
+
? [...this.connections.keys()].filter((id) => this.connectionSessions.get(id)?.has(sessionId))
|
|
97
|
+
: [undefined];
|
|
98
|
+
}
|
|
99
|
+
get(id) {
|
|
100
|
+
return this.connections.get(id);
|
|
101
|
+
}
|
|
102
|
+
values() {
|
|
103
|
+
return this.connections.values();
|
|
104
|
+
}
|
|
105
|
+
/** True when no socket connection is registered, i.e. records must fall back to the stdio lane. */
|
|
106
|
+
isEmpty() {
|
|
107
|
+
return this.connections.size === 0;
|
|
108
|
+
}
|
|
109
|
+
broadcast(line) {
|
|
110
|
+
for (const { actor } of this.connections.values())
|
|
111
|
+
actor.enqueue(line);
|
|
112
|
+
}
|
|
113
|
+
rememberSnapshot(sessionId, value, line) {
|
|
114
|
+
const event = value.assistantMessageEvent;
|
|
115
|
+
const record = { line, rendered: value[RENDERED_COMPONENT_RECORD] === true };
|
|
116
|
+
if (value.type === "message_start" || (value.type === "message_update" && event?.type === "text_start")) {
|
|
117
|
+
this.sessionSnapshots.set(sessionId, [record]);
|
|
118
|
+
}
|
|
119
|
+
else if (this.sessionSnapshots.has(sessionId)) {
|
|
120
|
+
this.sessionSnapshots.get(sessionId)?.push(record);
|
|
121
|
+
}
|
|
122
|
+
if (value.type === "message_end")
|
|
123
|
+
this.sessionSnapshots.delete(sessionId);
|
|
124
|
+
}
|
|
125
|
+
forgetSession(sessionId) {
|
|
126
|
+
this.sessionSnapshots.delete(sessionId);
|
|
127
|
+
}
|
|
128
|
+
replaySnapshot(id, sessionId) {
|
|
129
|
+
const actor = this.connections.get(id)?.actor;
|
|
130
|
+
if (!actor)
|
|
131
|
+
return;
|
|
132
|
+
const capable = this.connectionCapabilities.get(id)?.has("rendered_components") ?? false;
|
|
133
|
+
for (const record of this.sessionSnapshots.get(sessionId) ?? [])
|
|
134
|
+
if (!record.rendered || capable)
|
|
135
|
+
actor.enqueue(record.line);
|
|
136
|
+
}
|
|
137
|
+
replayRendered(id, sessionId) {
|
|
138
|
+
const actor = this.connections.get(id)?.actor;
|
|
139
|
+
if (!actor)
|
|
140
|
+
return;
|
|
141
|
+
for (const record of this.sessionSnapshots.get(sessionId) ?? [])
|
|
142
|
+
if (record.rendered)
|
|
143
|
+
actor.enqueue(record.line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=session-event-fanout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-event-fanout.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-fanout.ts"],"names":[],"mappings":"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","sourcesContent":["import { SocketEventSinkActor } from \"./socket-event-fanout.ts\";\n\nexport type RawWriter = (chunk: string) => void;\nexport type BackpressureWaiter = () => Promise<void>;\n\nexport interface SessionEventWriterConnection {\n\treadonly writeRaw: RawWriter;\n\treadonly waitForBackpressure: BackpressureWaiter;\n}\n\nexport const RENDERED_COMPONENT_RECORD = \"__senpiRenderedComponent\";\n\nconst BROADCAST_LIFECYCLE_RECORDS = new Set([\n\t\"agent_start\",\n\t\"agent_settled\",\n\t\"agent_idle\",\n\t\"session_opened\",\n\t\"session_closed\",\n]);\n\ntype RegisteredConnection = {\n\treadonly connection: SessionEventWriterConnection;\n\treadonly actor: SocketEventSinkActor;\n};\n\ntype SnapshotRecord = { readonly line: string; readonly rendered: boolean };\n\nexport class SessionEventFanout {\n\tprivate readonly connections = new Map<string, RegisteredConnection>();\n\tprivate readonly sessionSnapshots = new Map<string, SnapshotRecord[]>();\n\tprivate readonly connectionCapabilities = new Map<string, Set<string>>();\n\tprivate readonly connectionSessions = new Map<string, Set<string>>();\n\tprivate readonly registeredCapabilityConnections = new Set<string>();\n\n\tregisterConnection(id: string, connection: SessionEventWriterConnection): void {\n\t\tconst actor = new SocketEventSinkActor(connection, () => {\n\t\t\tif (this.connections.get(id)?.actor === actor) {\n\t\t\t\tthis.connections.delete(id);\n\t\t\t\tthis.connectionCapabilities.delete(id);\n\t\t\t\tthis.connectionSessions.delete(id);\n\t\t\t\tthis.registeredCapabilityConnections.delete(id);\n\t\t\t}\n\t\t});\n\t\tthis.connections.set(id, { connection, actor });\n\t\tthis.connectionCapabilities.set(id, new Set());\n\t\tthis.connectionSessions.set(id, new Set());\n\t}\n\n\tunregisterConnection(id: string): void {\n\t\tconst registered = this.connections.get(id);\n\t\tif (!registered) return;\n\t\tregistered.actor.close();\n\t\tthis.connections.delete(id);\n\t\tthis.connectionCapabilities.delete(id);\n\t\tthis.connectionSessions.delete(id);\n\t\tthis.registeredCapabilityConnections.delete(id);\n\t}\n\n\tattachConnectionToSession(id: string, sessionId: string): void {\n\t\tif (!this.connections.has(id)) return;\n\t\tconst sessions = this.connectionSessions.get(id) ?? new Set<string>();\n\t\tif (sessions.has(sessionId)) return;\n\t\tsessions.add(sessionId);\n\t\tthis.connectionSessions.set(id, sessions);\n\t\tthis.replaySnapshot(id, sessionId);\n\t}\n\n\tdetachConnectionFromSession(id: string, sessionId: string): void {\n\t\tthis.connectionSessions.get(id)?.delete(sessionId);\n\t}\n\n\tsetConnectionCapabilities(id: string, capabilities: readonly string[]): void {\n\t\tconst registered = this.connections.get(id);\n\t\tif (!registered) return;\n\t\tconst wasCapable = this.connectionCapabilities.get(id)?.has(\"rendered_components\") ?? false;\n\t\tthis.connectionCapabilities.set(id, new Set(capabilities));\n\t\tthis.registeredCapabilityConnections.add(id);\n\t\tif (!wasCapable && capabilities.includes(\"rendered_components\"))\n\t\t\tfor (const sessionId of this.connectionSessions.get(id) ?? []) this.replayRendered(id, sessionId);\n\t}\n\n\tclearConnectionCapabilities(id: string): void {\n\t\tif (this.connections.has(id)) {\n\t\t\tthis.connectionCapabilities.set(id, new Set());\n\t\t\tthis.registeredCapabilityConnections.delete(id);\n\t\t}\n\t}\n\n\thasRegisteredConnectionCapabilities(id: string): boolean {\n\t\treturn this.registeredCapabilityConnections.has(id);\n\t}\n\n\tgetConnectionCapabilities(id: string): readonly string[] | undefined {\n\t\tif (!this.registeredCapabilityConnections.has(id)) return undefined;\n\t\treturn [...(this.connectionCapabilities.get(id) ?? [])];\n\t}\n\n\thasCapableConnection(sessionId: string): boolean {\n\t\tfor (const [id, capabilities] of this.connectionCapabilities)\n\t\t\tif (capabilities.has(\"rendered_components\") && this.connectionSessions.get(id)?.has(sessionId)) return true;\n\t\treturn false;\n\t}\n\n\ttargets(\n\t\tsessionId: string,\n\t\ttargetId: string | undefined,\n\t\tisTargeted: boolean,\n\t\trendered: boolean,\n\t\trecordType: unknown,\n\t): readonly (string | undefined)[] {\n\t\tif (isTargeted) return [targetId];\n\t\tif (typeof recordType === \"string\" && BROADCAST_LIFECYCLE_RECORDS.has(recordType))\n\t\t\treturn this.connections.size > 0 ? [...this.connections.keys()] : [undefined];\n\t\tif (rendered)\n\t\t\treturn this.connections.size > 0\n\t\t\t\t? [...this.connections.keys()].filter(\n\t\t\t\t\t\t(id) =>\n\t\t\t\t\t\t\tthis.connectionCapabilities.get(id)?.has(\"rendered_components\") &&\n\t\t\t\t\t\t\tthis.connectionSessions.get(id)?.has(sessionId),\n\t\t\t\t\t)\n\t\t\t\t: [undefined];\n\t\treturn this.connections.size > 0\n\t\t\t? [...this.connections.keys()].filter((id) => this.connectionSessions.get(id)?.has(sessionId))\n\t\t\t: [undefined];\n\t}\n\n\tget(id: string): RegisteredConnection | undefined {\n\t\treturn this.connections.get(id);\n\t}\n\n\tvalues(): IterableIterator<RegisteredConnection> {\n\t\treturn this.connections.values();\n\t}\n\n\t/** True when no socket connection is registered, i.e. records must fall back to the stdio lane. */\n\tisEmpty(): boolean {\n\t\treturn this.connections.size === 0;\n\t}\n\n\tbroadcast(line: string): void {\n\t\tfor (const { actor } of this.connections.values()) actor.enqueue(line);\n\t}\n\n\trememberSnapshot(sessionId: string, value: Record<string, unknown>, line: string): void {\n\t\tconst event = value.assistantMessageEvent as Record<string, unknown> | undefined;\n\t\tconst record = { line, rendered: value[RENDERED_COMPONENT_RECORD] === true };\n\t\tif (value.type === \"message_start\" || (value.type === \"message_update\" && event?.type === \"text_start\")) {\n\t\t\tthis.sessionSnapshots.set(sessionId, [record]);\n\t\t} else if (this.sessionSnapshots.has(sessionId)) {\n\t\t\tthis.sessionSnapshots.get(sessionId)?.push(record);\n\t\t}\n\t\tif (value.type === \"message_end\") this.sessionSnapshots.delete(sessionId);\n\t}\n\n\tforgetSession(sessionId: string): void {\n\t\tthis.sessionSnapshots.delete(sessionId);\n\t}\n\n\tprivate replaySnapshot(id: string, sessionId: string): void {\n\t\tconst actor = this.connections.get(id)?.actor;\n\t\tif (!actor) return;\n\t\tconst capable = this.connectionCapabilities.get(id)?.has(\"rendered_components\") ?? false;\n\t\tfor (const record of this.sessionSnapshots.get(sessionId) ?? [])\n\t\t\tif (!record.rendered || capable) actor.enqueue(record.line);\n\t}\n\n\tprivate replayRendered(id: string, sessionId: string): void {\n\t\tconst actor = this.connections.get(id)?.actor;\n\t\tif (!actor) return;\n\t\tfor (const record of this.sessionSnapshots.get(sessionId) ?? []) if (record.rendered) actor.enqueue(record.line);\n\t}\n}\n"]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type SessionEventWriterConnection } from "./session-event-fanout.ts";
|
|
2
|
+
export { RENDERED_COMPONENT_RECORD, type SessionEventWriterConnection } from "./session-event-fanout.ts";
|
|
1
3
|
type RawWriter = (chunk: string) => void;
|
|
2
4
|
type BackpressureWaiter = () => Promise<void>;
|
|
3
5
|
type FlushScheduler = (flush: () => Promise<void>) => void;
|
|
@@ -10,19 +12,10 @@ type FlushScheduler = (flush: () => Promise<void>) => void;
|
|
|
10
12
|
* by itself: coalescing records from different sessions would obscure the
|
|
11
13
|
* scheduling boundary and violate D9.
|
|
12
14
|
*/
|
|
13
|
-
export interface SessionEventWriterConnection {
|
|
14
|
-
readonly writeRaw: RawWriter;
|
|
15
|
-
readonly waitForBackpressure: BackpressureWaiter;
|
|
16
|
-
}
|
|
17
|
-
export declare const RENDERED_COMPONENT_RECORD = "__senpiRenderedComponent";
|
|
18
15
|
export declare class SessionEventWriter {
|
|
19
16
|
private readonly queues;
|
|
20
|
-
private readonly
|
|
21
|
-
private readonly sessionSnapshots;
|
|
17
|
+
private readonly fanout;
|
|
22
18
|
private readonly connectionContext;
|
|
23
|
-
private readonly connectionCapabilities;
|
|
24
|
-
private readonly connectionSessions;
|
|
25
|
-
private readonly registeredCapabilityConnections;
|
|
26
19
|
private readonly controlQueue;
|
|
27
20
|
private readonly readyQueues;
|
|
28
21
|
private readonly sealedSessions;
|
|
@@ -50,7 +43,7 @@ export declare class SessionEventWriter {
|
|
|
50
43
|
withConnection<T>(id: string, task: () => T): T;
|
|
51
44
|
/** Connection id of the command currently being dispatched, when one owns it. */
|
|
52
45
|
currentConnection(): string | undefined;
|
|
53
|
-
/** Queue a session record.
|
|
46
|
+
/** Queue a session record. Content events target connections attached to the session. */
|
|
54
47
|
enqueue(sessionId: string, value: object): boolean;
|
|
55
48
|
/** Queue one untagged host-control response for the current connection. */
|
|
56
49
|
enqueueControl(value: object): Promise<void>;
|
|
@@ -60,6 +53,8 @@ export declare class SessionEventWriter {
|
|
|
60
53
|
* session's final stdout record.
|
|
61
54
|
*/
|
|
62
55
|
closeSession(sessionId: string, response: object): void;
|
|
56
|
+
/** Queue a successful response for a joined close after the terminal lifecycle record. */
|
|
57
|
+
enqueueClosedResponse(sessionId: string, response: object): void;
|
|
63
58
|
/**
|
|
64
59
|
* Drops per-session bookkeeping for a handle whose runtime is fully disposed.
|
|
65
60
|
* Routing handles are unique per process epoch, so nothing can legitimately
|
|
@@ -71,7 +66,6 @@ export declare class SessionEventWriter {
|
|
|
71
66
|
flush(): Promise<void>;
|
|
72
67
|
private drainUntilEmpty;
|
|
73
68
|
private drainReadyQueues;
|
|
74
|
-
private rememberSnapshot;
|
|
75
69
|
private connectionQueue;
|
|
76
70
|
private appendSessionRecord;
|
|
77
71
|
private demoteAndMerge;
|
|
@@ -82,5 +76,4 @@ export declare class SessionEventWriter {
|
|
|
82
76
|
private fail;
|
|
83
77
|
private allQueues;
|
|
84
78
|
}
|
|
85
|
-
export {};
|
|
86
79
|
//# sourceMappingURL=session-event-writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-event-writer.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-event-writer.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-event-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { serializeJsonLine } from "./jsonl.js";
|
|
3
|
-
import {
|
|
3
|
+
import { RENDERED_COMPONENT_RECORD, SessionEventFanout, } from "./session-event-fanout.js";
|
|
4
|
+
export { RENDERED_COMPONENT_RECORD } from "./session-event-fanout.js";
|
|
4
5
|
const MESSAGE_KEY = "message";
|
|
5
6
|
const COMPACT_DELTA_TYPES = new Set(["text_delta", "thinking_delta", "toolcall_delta"]);
|
|
6
7
|
function compactDelta(value) {
|
|
@@ -27,16 +28,20 @@ function toolUpdateKey(value) {
|
|
|
27
28
|
? `tool:${value.toolCallId}`
|
|
28
29
|
: undefined;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Process-wide stdout scheduler for multi-session RPC mode.
|
|
33
|
+
*
|
|
34
|
+
* Each queue contains complete structured JSONL records for one routing handle.
|
|
35
|
+
* Draining takes one record per queue in round-robin order and waits for stdout
|
|
36
|
+
* backpressure before selecting the next one. A record is deliberately written
|
|
37
|
+
* by itself: coalescing records from different sessions would obscure the
|
|
38
|
+
* scheduling boundary and violate D9.
|
|
39
|
+
*/
|
|
31
40
|
export class SessionEventWriter {
|
|
32
41
|
constructor(writeRaw, waitOrSchedule, scheduleFlush) {
|
|
33
42
|
this.queues = new Map();
|
|
34
|
-
this.
|
|
35
|
-
this.sessionSnapshots = new Map();
|
|
43
|
+
this.fanout = new SessionEventFanout();
|
|
36
44
|
this.connectionContext = new AsyncLocalStorage();
|
|
37
|
-
this.connectionCapabilities = new Map();
|
|
38
|
-
this.connectionSessions = new Map();
|
|
39
|
-
this.registeredCapabilityConnections = new Set();
|
|
40
45
|
this.controlQueue = { latestByKey: new Map(), ready: false };
|
|
41
46
|
this.readyQueues = [];
|
|
42
47
|
this.sealedSessions = new Set();
|
|
@@ -68,78 +73,31 @@ export class SessionEventWriter {
|
|
|
68
73
|
return bytes;
|
|
69
74
|
}
|
|
70
75
|
registerConnection(id, connection) {
|
|
71
|
-
|
|
72
|
-
if (this.connections.get(id)?.actor === actor) {
|
|
73
|
-
this.connections.delete(id);
|
|
74
|
-
this.connectionCapabilities.delete(id);
|
|
75
|
-
this.connectionSessions.delete(id);
|
|
76
|
-
this.registeredCapabilityConnections.delete(id);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
this.connections.set(id, { connection, actor });
|
|
80
|
-
this.connectionCapabilities.set(id, new Set());
|
|
81
|
-
this.connectionSessions.set(id, new Set());
|
|
82
|
-
for (const snapshot of this.sessionSnapshots.values())
|
|
83
|
-
for (const record of snapshot)
|
|
84
|
-
if (!record.rendered)
|
|
85
|
-
actor.enqueue(record.line);
|
|
76
|
+
this.fanout.registerConnection(id, connection);
|
|
86
77
|
}
|
|
87
78
|
unregisterConnection(id) {
|
|
88
|
-
|
|
89
|
-
if (!registered)
|
|
90
|
-
return;
|
|
91
|
-
registered.actor.close();
|
|
92
|
-
this.connections.delete(id);
|
|
93
|
-
this.connectionCapabilities.delete(id);
|
|
94
|
-
this.connectionSessions.delete(id);
|
|
95
|
-
this.registeredCapabilityConnections.delete(id);
|
|
79
|
+
this.fanout.unregisterConnection(id);
|
|
96
80
|
}
|
|
97
81
|
attachConnectionToSession(id, sessionId) {
|
|
98
|
-
|
|
99
|
-
return;
|
|
100
|
-
const sessions = this.connectionSessions.get(id) ?? new Set();
|
|
101
|
-
sessions.add(sessionId);
|
|
102
|
-
this.connectionSessions.set(id, sessions);
|
|
103
|
-
if (this.connectionCapabilities.get(id)?.has("rendered_components"))
|
|
104
|
-
for (const record of this.sessionSnapshots.get(sessionId) ?? [])
|
|
105
|
-
if (record.rendered)
|
|
106
|
-
this.connections.get(id).actor.enqueue(record.line);
|
|
82
|
+
this.fanout.attachConnectionToSession(id, sessionId);
|
|
107
83
|
}
|
|
108
84
|
detachConnectionFromSession(id, sessionId) {
|
|
109
|
-
this.
|
|
85
|
+
this.fanout.detachConnectionFromSession(id, sessionId);
|
|
110
86
|
}
|
|
111
87
|
setConnectionCapabilities(id, capabilities) {
|
|
112
|
-
|
|
113
|
-
if (!registered)
|
|
114
|
-
return;
|
|
115
|
-
const wasCapable = this.connectionCapabilities.get(id)?.has("rendered_components") ?? false;
|
|
116
|
-
this.connectionCapabilities.set(id, new Set(capabilities));
|
|
117
|
-
this.registeredCapabilityConnections.add(id);
|
|
118
|
-
if (!wasCapable && capabilities.includes("rendered_components"))
|
|
119
|
-
for (const sessionId of this.connectionSessions.get(id) ?? [])
|
|
120
|
-
for (const record of this.sessionSnapshots.get(sessionId) ?? [])
|
|
121
|
-
if (record.rendered)
|
|
122
|
-
registered.actor.enqueue(record.line);
|
|
88
|
+
this.fanout.setConnectionCapabilities(id, capabilities);
|
|
123
89
|
}
|
|
124
90
|
clearConnectionCapabilities(id) {
|
|
125
|
-
|
|
126
|
-
this.connectionCapabilities.set(id, new Set());
|
|
127
|
-
this.registeredCapabilityConnections.delete(id);
|
|
128
|
-
}
|
|
91
|
+
this.fanout.clearConnectionCapabilities(id);
|
|
129
92
|
}
|
|
130
93
|
hasRegisteredConnectionCapabilities(id) {
|
|
131
|
-
return this.
|
|
94
|
+
return this.fanout.hasRegisteredConnectionCapabilities(id);
|
|
132
95
|
}
|
|
133
96
|
getConnectionCapabilities(id) {
|
|
134
|
-
|
|
135
|
-
return undefined;
|
|
136
|
-
return [...(this.connectionCapabilities.get(id) ?? [])];
|
|
97
|
+
return this.fanout.getConnectionCapabilities(id);
|
|
137
98
|
}
|
|
138
99
|
hasCapableConnection(sessionId) {
|
|
139
|
-
|
|
140
|
-
if (capabilities.has("rendered_components") && this.connectionSessions.get(id)?.has(sessionId))
|
|
141
|
-
return true;
|
|
142
|
-
return false;
|
|
100
|
+
return this.fanout.hasCapableConnection(sessionId);
|
|
143
101
|
}
|
|
144
102
|
/** Execute a connection's command with its response destination in context. */
|
|
145
103
|
withConnection(id, task) {
|
|
@@ -149,7 +107,7 @@ export class SessionEventWriter {
|
|
|
149
107
|
currentConnection() {
|
|
150
108
|
return this.connectionContext.getStore();
|
|
151
109
|
}
|
|
152
|
-
/** Queue a session record.
|
|
110
|
+
/** Queue a session record. Content events target connections attached to the session. */
|
|
153
111
|
enqueue(sessionId, value) {
|
|
154
112
|
if (this.sealedSessions.has(sessionId))
|
|
155
113
|
return false;
|
|
@@ -162,19 +120,13 @@ export class SessionEventWriter {
|
|
|
162
120
|
const tagged = { ...value, sessionId };
|
|
163
121
|
const { [RENDERED_COMPONENT_RECORD]: _rendered, ...wireTagged } = tagged;
|
|
164
122
|
const line = serializeJsonLine(wireTagged);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
: record[RENDERED_COMPONENT_RECORD] && this.connections.size > 0
|
|
169
|
-
? [...this.connections.keys()].filter((id) => this.connectionCapabilities.get(id)?.has("rendered_components") &&
|
|
170
|
-
this.connectionSessions.get(id)?.has(sessionId))
|
|
171
|
-
: this.connections.size > 0
|
|
172
|
-
? [...this.connections.keys()]
|
|
173
|
-
: [undefined];
|
|
123
|
+
if (!isTargeted)
|
|
124
|
+
this.fanout.rememberSnapshot(sessionId, tagged, line);
|
|
125
|
+
const targets = this.fanout.targets(sessionId, targetId, isTargeted, record[RENDERED_COMPONENT_RECORD] === true, record.type);
|
|
174
126
|
for (const target of targets) {
|
|
175
|
-
if (target !== undefined && !this.
|
|
127
|
+
if (target !== undefined && !this.fanout.get(target))
|
|
176
128
|
continue;
|
|
177
|
-
const registered = target === undefined ? undefined : this.
|
|
129
|
+
const registered = target === undefined ? undefined : this.fanout.get(target);
|
|
178
130
|
if (registered)
|
|
179
131
|
registered.actor.enqueue(line, compactDelta(tagged) && tagged.message !== null ? MESSAGE_KEY : undefined);
|
|
180
132
|
else
|
|
@@ -188,7 +140,7 @@ export class SessionEventWriter {
|
|
|
188
140
|
if (this.failure !== undefined)
|
|
189
141
|
return Promise.reject(this.failure);
|
|
190
142
|
const targetId = this.connectionContext.getStore();
|
|
191
|
-
const registered = targetId === undefined ? undefined : this.
|
|
143
|
+
const registered = targetId === undefined ? undefined : this.fanout.get(targetId);
|
|
192
144
|
if (registered) {
|
|
193
145
|
registered.actor.enqueue(serializeJsonLine(value));
|
|
194
146
|
return Promise.resolve();
|
|
@@ -212,11 +164,23 @@ export class SessionEventWriter {
|
|
|
212
164
|
this.sealedSessions.add(sessionId);
|
|
213
165
|
const targetId = this.connectionContext.getStore();
|
|
214
166
|
const lifecycle = { type: "session_closed", sessionId };
|
|
215
|
-
|
|
216
|
-
this.
|
|
217
|
-
|
|
167
|
+
if (this.fanout.isEmpty())
|
|
168
|
+
this.appendSessionRecord(sessionId, lifecycle);
|
|
169
|
+
else
|
|
170
|
+
this.fanout.broadcast(serializeJsonLine(lifecycle));
|
|
218
171
|
const taggedResponse = { ...response, sessionId };
|
|
219
|
-
const registered = targetId === undefined ? undefined : this.
|
|
172
|
+
const registered = targetId === undefined ? undefined : this.fanout.get(targetId);
|
|
173
|
+
if (registered)
|
|
174
|
+
registered.actor.enqueue(serializeJsonLine(taggedResponse));
|
|
175
|
+
else
|
|
176
|
+
this.appendSessionRecord(sessionId, taggedResponse, targetId);
|
|
177
|
+
this.requestFlush();
|
|
178
|
+
}
|
|
179
|
+
/** Queue a successful response for a joined close after the terminal lifecycle record. */
|
|
180
|
+
enqueueClosedResponse(sessionId, response) {
|
|
181
|
+
const targetId = this.connectionContext.getStore();
|
|
182
|
+
const taggedResponse = { ...response, sessionId };
|
|
183
|
+
const registered = targetId === undefined ? undefined : this.fanout.get(targetId);
|
|
220
184
|
if (registered)
|
|
221
185
|
registered.actor.enqueue(serializeJsonLine(taggedResponse));
|
|
222
186
|
else
|
|
@@ -231,7 +195,7 @@ export class SessionEventWriter {
|
|
|
231
195
|
*/
|
|
232
196
|
forgetSession(sessionId) {
|
|
233
197
|
this.sealedSessions.delete(sessionId);
|
|
234
|
-
this.
|
|
198
|
+
this.fanout.forgetSession(sessionId);
|
|
235
199
|
}
|
|
236
200
|
/** Drain every retained lane and the current in-flight record. */
|
|
237
201
|
flush() {
|
|
@@ -241,7 +205,7 @@ export class SessionEventWriter {
|
|
|
241
205
|
if (this.drainPromise)
|
|
242
206
|
return this.drainPromise;
|
|
243
207
|
if (this.readyQueues.length === 0)
|
|
244
|
-
return Promise.all([...this.
|
|
208
|
+
return Promise.all([...this.fanout.values()].map(({ actor }) => actor.flush())).then(() => undefined);
|
|
245
209
|
let resolveDrain;
|
|
246
210
|
let rejectDrain;
|
|
247
211
|
const drain = new Promise((resolve, reject) => {
|
|
@@ -266,7 +230,7 @@ export class SessionEventWriter {
|
|
|
266
230
|
do {
|
|
267
231
|
await this.drainReadyQueues();
|
|
268
232
|
} while (this.readyQueues.length > 0);
|
|
269
|
-
await Promise.all([...this.
|
|
233
|
+
await Promise.all([...this.fanout.values()].map(({ actor }) => actor.flush()));
|
|
270
234
|
}
|
|
271
235
|
async drainReadyQueues() {
|
|
272
236
|
while (this.readyQueues.length > 0) {
|
|
@@ -280,7 +244,7 @@ export class SessionEventWriter {
|
|
|
280
244
|
try {
|
|
281
245
|
// D9: exactly one complete record per raw write. The next lane is not
|
|
282
246
|
// selected until this record has cleared stdout backpressure.
|
|
283
|
-
const connection = queue.targetId ? this.
|
|
247
|
+
const connection = queue.targetId ? this.fanout.get(queue.targetId)?.connection : undefined;
|
|
284
248
|
const writeRaw = connection?.writeRaw ?? this.writeRaw;
|
|
285
249
|
const waitForBackpressure = connection?.waitForBackpressure ?? this.waitForBackpressure;
|
|
286
250
|
if (!connection && queue.targetId) {
|
|
@@ -311,18 +275,6 @@ export class SessionEventWriter {
|
|
|
311
275
|
}
|
|
312
276
|
}
|
|
313
277
|
}
|
|
314
|
-
rememberSnapshot(sessionId, value, line) {
|
|
315
|
-
const event = value.assistantMessageEvent;
|
|
316
|
-
const record = { line, rendered: value[RENDERED_COMPONENT_RECORD] === true };
|
|
317
|
-
if (value.type === "message_start" || (value.type === "message_update" && event?.type === "text_start")) {
|
|
318
|
-
this.sessionSnapshots.set(sessionId, [record]);
|
|
319
|
-
}
|
|
320
|
-
else if (this.sessionSnapshots.has(sessionId)) {
|
|
321
|
-
this.sessionSnapshots.get(sessionId).push(record);
|
|
322
|
-
}
|
|
323
|
-
if (value.type === "message_end")
|
|
324
|
-
this.sessionSnapshots.delete(sessionId);
|
|
325
|
-
}
|
|
326
278
|
connectionQueue(targetId) {
|
|
327
279
|
const key = `connection:${targetId}`;
|
|
328
280
|
let queue = this.queues.get(key);
|