@ahggg/dsh-side-chat 0.7.3 → 1.0.1

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.
Files changed (80) hide show
  1. package/README.md +52 -20
  2. package/README.zh-CN.md +52 -20
  3. package/lib/client/apply.js +48 -32
  4. package/lib/client/contracts.d.ts +3 -58
  5. package/lib/client/dsh/ReadOnlyConversation.d.ts +10 -0
  6. package/lib/client/dsh/ReadOnlyConversation.js +99 -0
  7. package/lib/client/dsh/SideChatOverlay.d.ts +7 -0
  8. package/lib/client/{rc6/Rc6SideChatOverlay.js → dsh/SideChatOverlay.js} +26 -56
  9. package/lib/client/{rc6 → dsh}/context.d.ts +4 -6
  10. package/lib/client/dsh/conversation-store.d.ts +22 -0
  11. package/lib/client/dsh/conversation-store.js +19 -0
  12. package/lib/client/dsh/primitive-labels.d.ts +2 -0
  13. package/lib/client/dsh/primitive-labels.js +4 -0
  14. package/lib/client/{rc6 → dsh}/remote-adapter.d.ts +2 -2
  15. package/lib/client/dsh/remote-adapter.js +22 -0
  16. package/lib/client/dsh/sessions-adapter.d.ts +56 -0
  17. package/lib/client/dsh/sessions-adapter.js +185 -0
  18. package/lib/client/index.d.ts +2 -2
  19. package/lib/client/panel/SelectionQuote.js +8 -1
  20. package/lib/client/panel/SideChatErrorState.js +1 -1
  21. package/lib/client/panel/SideChatModelSelect.d.ts +2 -1
  22. package/lib/client/panel/SideChatModelSelect.js +43 -24
  23. package/lib/client/panel/SideChatPanel.js +12 -5
  24. package/lib/client/panel/messages.d.ts +1 -1
  25. package/lib/client/panel/messages.js +8 -8
  26. package/lib/client/parent-composer/ParentConversationAnnotations.d.ts +3 -2
  27. package/lib/client/parent-composer/ParentConversationAnnotations.js +50 -30
  28. package/lib/client/parent-composer/add-to-conversation.d.ts +14 -28
  29. package/lib/client/parent-composer/add-to-conversation.js +131 -545
  30. package/lib/client/parent-composer/annotation-persistence.d.ts +8 -8
  31. package/lib/client/parent-composer/annotation-persistence.js +106 -102
  32. package/lib/client/parent-composer/composer-reference.d.ts +17 -55
  33. package/lib/client/parent-composer/composer-reference.js +20 -72
  34. package/lib/client/parent-composer/referenced-conversation.d.ts +5 -7
  35. package/lib/client/parent-composer/referenced-conversation.js +21 -73
  36. package/lib/client/selection/ConversationAnnotationMarkers.js +5 -1
  37. package/lib/client/selection/SelectionActions.js +2 -1
  38. package/lib/client/selection/conversation-dom.d.ts +5 -0
  39. package/lib/client/selection/conversation-dom.js +24 -0
  40. package/lib/client/selection/selection-controller.d.ts +1 -1
  41. package/lib/client/selection/selection-controller.js +7 -3
  42. package/lib/client/selection/selection-normalizer.js +1 -1
  43. package/lib/client/side-chat-controller.d.ts +28 -41
  44. package/lib/client/side-chat-controller.js +247 -337
  45. package/lib/client.js +84 -54
  46. package/lib/host/index.d.ts +1 -1
  47. package/lib/host/index.js +1 -1
  48. package/lib/host/read-only-chat-service.d.ts +30 -0
  49. package/lib/host/read-only-chat-service.js +315 -0
  50. package/lib/index.d.ts +16 -8
  51. package/lib/index.js +34 -13
  52. package/lib/remote.d.ts +23 -14
  53. package/lib/remote.js +2 -5
  54. package/lib/shared/constants.d.ts +0 -13
  55. package/lib/shared/constants.js +0 -13
  56. package/lib/shared/contracts.d.ts +52 -19
  57. package/lib/shared/contracts.js +2 -3
  58. package/lib/shared/error-codes.d.ts +1 -1
  59. package/lib/shared/error-codes.js +2 -2
  60. package/lib/shared/{archived-wire.d.ts → side-chat-wire.d.ts} +109 -20
  61. package/lib/shared/side-chat-wire.js +34 -0
  62. package/lib/typert.d.ts +3 -68
  63. package/lib/typert.js +60 -4
  64. package/package.json +76 -56
  65. package/lib/client/rc6/ArchivedConversation.d.ts +0 -14
  66. package/lib/client/rc6/ArchivedConversation.js +0 -231
  67. package/lib/client/rc6/Rc6SideChatOverlay.d.ts +0 -7
  68. package/lib/client/rc6/SideChatTool.d.ts +0 -22
  69. package/lib/client/rc6/SideChatTool.js +0 -404
  70. package/lib/client/rc6/primitive-labels.d.ts +0 -7
  71. package/lib/client/rc6/primitive-labels.js +0 -60
  72. package/lib/client/rc6/remote-adapter.js +0 -34
  73. package/lib/client/rc6/runtime-compat.d.ts +0 -120
  74. package/lib/client/rc6/runtime-compat.js +0 -187
  75. package/lib/client/rc6/sessions-adapter.d.ts +0 -64
  76. package/lib/client/rc6/sessions-adapter.js +0 -387
  77. package/lib/host/archived-fork-service.d.ts +0 -28
  78. package/lib/host/archived-fork-service.js +0 -309
  79. package/lib/shared/archived-wire.js +0 -47
  80. /package/lib/client/{rc6 → dsh}/context.js +0 -0
@@ -1,187 +0,0 @@
1
- const EMPTY_NODES = [];
2
- const EMPTY_TURN_ENDS = new Map();
3
- const EMPTY_RUNNING_CALLS = [];
4
- const EMPTY_PENDING = [];
5
- const EMPTY_NODE_STORE = { get: () => undefined };
6
- function isLegacySnapshot(snapshot) {
7
- return typeof snapshot === 'object'
8
- && snapshot !== null
9
- && Array.isArray(snapshot.nodes)
10
- && Array.isArray(snapshot.pending);
11
- }
12
- function currentPending(source, sessionId) {
13
- const pending = source?.get(sessionId);
14
- if (typeof pending !== 'object' || pending === null)
15
- return;
16
- const candidate = pending;
17
- if (candidate.kind === 'approval'
18
- && typeof candidate.key === 'string'
19
- && typeof candidate.toolName === 'string'
20
- && typeof candidate.answer === 'function') {
21
- return pending;
22
- }
23
- if ((candidate.kind === 'question' || candidate.kind === 'plan-review')
24
- && typeof candidate.key === 'string'
25
- && Array.isArray(candidate.questions)
26
- && typeof candidate.answer === 'function'
27
- && typeof candidate.cancel === 'function') {
28
- return pending;
29
- }
30
- return undefined;
31
- }
32
- function normalizeLegacyPending(wait) {
33
- if (wait.kind === 'approval') {
34
- return {
35
- kind: 'approval',
36
- key: wait.key,
37
- toolName: wait.payload.toolName,
38
- ...(wait.payload.reason === undefined ? {} : { reason: wait.payload.reason }),
39
- respond: async (decision) => {
40
- const receipt = await wait.respond({
41
- ok: true,
42
- value: {
43
- sessionId: wait.sessionId,
44
- approvalId: wait.payload.approvalId,
45
- outcome: decision === 'approve' ? 'allowed-once' : 'rejected',
46
- },
47
- });
48
- return receipt.accepted;
49
- },
50
- };
51
- }
52
- return {
53
- kind: 'question',
54
- key: wait.key,
55
- questions: wait.payload.questions,
56
- respond: async (answer) => {
57
- const receipt = await wait.respond(answer === null
58
- ? {
59
- ok: false,
60
- error: { code: 'cancelled', message: 'Question cancelled.', details: {} },
61
- }
62
- : {
63
- ok: true,
64
- value: { sessionId: wait.sessionId, answer },
65
- });
66
- return receipt.accepted;
67
- },
68
- };
69
- }
70
- function normalizeCurrentPending(wait) {
71
- if (wait.kind === 'approval') {
72
- return {
73
- kind: 'approval',
74
- key: wait.key,
75
- toolName: wait.toolName,
76
- ...(wait.reason === undefined ? {} : { reason: wait.reason }),
77
- respond: async (decision) => {
78
- await wait.answer(decision === 'approve' ? 'allowed-once' : 'rejected');
79
- return true;
80
- },
81
- };
82
- }
83
- return {
84
- kind: 'question',
85
- key: wait.key,
86
- questions: wait.questions,
87
- respond: async (answer) => {
88
- if (answer === null)
89
- await wait.cancel();
90
- else
91
- await wait.answer(answer);
92
- return true;
93
- },
94
- };
95
- }
96
- /** Bridge DSH <=0.1.1's combined Session snapshot and 0.1.2's split stores. */
97
- export class CompatibleConversationFace {
98
- session;
99
- chat;
100
- pending;
101
- sessionId;
102
- cache;
103
- constructor(session, chat, pending) {
104
- this.session = session;
105
- this.chat = chat;
106
- this.pending = pending;
107
- this.sessionId = session.sessionId;
108
- }
109
- subscribe = (listener) => {
110
- const removers = [this.session.subscribe(listener)];
111
- if (this.chat !== undefined)
112
- removers.push(this.chat.subscribe(listener));
113
- if (this.pending !== undefined)
114
- removers.push(this.pending.subscribe(listener));
115
- return () => {
116
- for (const remove of removers)
117
- remove();
118
- };
119
- };
120
- getSnapshot = () => {
121
- const session = this.session.getSnapshot();
122
- if (isLegacySnapshot(session)) {
123
- if (this.cache?.session === session)
124
- return this.cache.value;
125
- const value = {
126
- nodes: session.nodes,
127
- turnEnds: session.turnEnds,
128
- partial: session.partial,
129
- runningCalls: session.runningCalls,
130
- pending: session.pending.map(normalizeLegacyPending),
131
- queue: session.queue,
132
- running: session.running,
133
- openState: session.openState,
134
- promptError: session.promptError,
135
- lastAgentError: session.lastAgentError,
136
- chatNodes: session.chat.nodes,
137
- };
138
- this.cache = { session, chat: undefined, pending: undefined, value };
139
- return value;
140
- }
141
- const lifecycle = session;
142
- const chat = this.chat?.getSnapshot();
143
- const pending = this.pending?.getSnapshot();
144
- const cached = this.cache;
145
- if (cached !== undefined
146
- && cached.session === session
147
- && cached.chat === chat
148
- && cached.pending === pending) {
149
- return cached.value;
150
- }
151
- const legacy = chat?.legacy;
152
- const wait = currentPending(pending, this.sessionId);
153
- const value = {
154
- nodes: legacy?.nodes ?? EMPTY_NODES,
155
- turnEnds: legacy?.turnEnds ?? EMPTY_TURN_ENDS,
156
- partial: legacy?.partial ?? null,
157
- runningCalls: legacy?.runningCalls ?? EMPTY_RUNNING_CALLS,
158
- pending: wait === undefined ? EMPTY_PENDING : [normalizeCurrentPending(wait)],
159
- queue: lifecycle.queue,
160
- running: lifecycle.running,
161
- openState: lifecycle.openState,
162
- promptError: lifecycle.promptError,
163
- lastAgentError: lifecycle.lastAgentError,
164
- chatNodes: chat?.nodes ?? EMPTY_NODE_STORE,
165
- };
166
- this.cache = { session, chat, pending, value };
167
- return value;
168
- };
169
- prompt(content, mode) {
170
- return this.session.prompt(content, mode);
171
- }
172
- updateQueue(itemId, action) {
173
- return this.session.updateQueue(itemId, action);
174
- }
175
- cancel() {
176
- return this.session.cancel();
177
- }
178
- rename(title) {
179
- return this.session.rename(title);
180
- }
181
- async open() {
182
- await this.session.open?.call(this.session);
183
- }
184
- }
185
- export function compatibleConversationFace(session, sources = {}) {
186
- return new CompatibleConversationFace(session, sources.chat, sources.pending);
187
- }
@@ -1,64 +0,0 @@
1
- import type { ModelDirectory } from '@deepseek-ai/dsh-client-ui-model-selection/client';
2
- import type { SideChatClientSessions, SideChatSessionLease } from '../contracts.js';
3
- import type { ConversationSelection, SessionId, SideChatModelSelection } from '../../shared/contracts.js';
4
- import type { ParentComposerInputSnapshot } from '../parent-composer/add-to-conversation.js';
5
- import { type ReferencedSideChatConversation } from '../parent-composer/referenced-conversation.js';
6
- import type { Rc6ClientContext } from './context.js';
7
- import { type SideChatConversationFace, type SideChatConversationSnapshot } from './runtime-compat.js';
8
- /** Adapter over rc.6's public SessionRuntime and exported concrete Session type. */
9
- export declare class Rc6SideChatSessions implements SideChatClientSessions {
10
- private readonly ctx;
11
- private readonly renamed;
12
- private readonly faces;
13
- private readonly parentInputs;
14
- private readonly annotationPersistence;
15
- private readonly modelPreferences;
16
- constructor(ctx: Rc6ClientContext);
17
- /** Observable rc.6 Session-list surface used to follow main-session switches. */
18
- readonly subscribeList: (listener: () => void) => (() => void);
19
- /** Follow both current-session switches and that session's composer state. */
20
- readonly subscribeConversationInput: (listener: () => void) => (() => void);
21
- readonly currentConversationInputSnapshot: () => ParentComposerInputSnapshot | undefined;
22
- currentSessionId(): SessionId | undefined;
23
- lastCompletedSeq(parentSessionId: SessionId): number | undefined;
24
- selectionIsCurrent(selection: ConversationSelection): boolean;
25
- /** Add one selected passage to the native composer of its parent Session. */
26
- addSelectionToConversation(selection: ConversationSelection, comment?: string): boolean;
27
- /** Add one immutable Side Chat transcript to its parent Session's composer. */
28
- addSideChatToConversation(parentSessionId: SessionId, reference: ReferencedSideChatConversation): boolean;
29
- /** Remove one existing unsent annotation from the aggregated occurrence. */
30
- removeConversationAnnotation(annotationIndex: number): boolean;
31
- /** Update an existing unsent annotation without adding a duplicate passage. */
32
- updateConversationAnnotation(annotationIndex: number, comment?: string): boolean;
33
- /** Mirror or recover the current Session's unsent annotation occurrence. */
34
- reconcileConversationAnnotationPersistence(): void;
35
- /** Number assigned to the next annotation shown beside the selected passage. */
36
- nextConversationAnnotationNumber(): number;
37
- /** Remove the current Session's unsent selected-passage annotations. */
38
- removeConversationAnnotations(): boolean;
39
- retain(sessionId: SessionId): Promise<SideChatSessionLease>;
40
- openSession(sessionId: SessionId): Promise<void>;
41
- notify(message: {
42
- readonly kind: 'status' | 'warning';
43
- readonly text: string;
44
- }): void;
45
- face(sessionId: SessionId): SideChatConversationFace | undefined;
46
- title(sessionId: SessionId): string | undefined;
47
- cwd(sessionId: SessionId): string | undefined;
48
- modelDirectory(sessionId: SessionId): ModelDirectory | undefined;
49
- sideChatModelPreference(): SideChatModelSelection | undefined;
50
- rememberSideChatModelPreference(selection: SideChatModelSelection): void;
51
- private adaptedFace;
52
- private currentParentInput;
53
- private parentInput;
54
- private waitForBinding;
55
- }
56
- export declare function selectionDescriptor(snapshot: SideChatConversationSnapshot, anchorKey: string): {
57
- readonly nodeKey: string;
58
- readonly nodeKind: string;
59
- readonly turnKey: string;
60
- readonly seq: number;
61
- readonly source: 'user' | 'assistant' | 'context' | 'code';
62
- readonly modelVisible: boolean;
63
- readonly settled: boolean;
64
- } | undefined;
@@ -1,387 +0,0 @@
1
- import { SessionId as sideChatSessionId } from '../../shared/contracts.js';
2
- import { addSelectionToConversation as addSelectionToParentComposer, conversationAnnotations, removeConversationAnnotation as removeParentConversationAnnotation, removeConversationAnnotations as removeParentConversationAnnotations, updateConversationAnnotation as updateParentConversationAnnotation, } from '../parent-composer/add-to-conversation.js';
3
- import { ConversationAnnotationPersistence } from '../parent-composer/annotation-persistence.js';
4
- import { addReferencedSideChatToConversation as addReferencedSideChatToParentComposer, } from '../parent-composer/referenced-conversation.js';
5
- import { SideChatModelPreferences } from '../model-preference.js';
6
- import { compatibleConversationFace, } from './runtime-compat.js';
7
- const BINDING_WAIT_MS = 8_000;
8
- function sideChatError(error, fallback) {
9
- const badRequest = error.code === 'bad-request' || error.code === 'gateway/bad-request';
10
- const missing = error.code === 'session-not-found' || error.code === 'session/not-found';
11
- return {
12
- code: missing ? 'side_chat_not_found' : fallback,
13
- message: error.message,
14
- recoverable: !badRequest,
15
- };
16
- }
17
- function operationError(code, message) {
18
- return { code, message, recoverable: true };
19
- }
20
- function dshSessionId(id) {
21
- return id;
22
- }
23
- function latestCompleted(snapshot) {
24
- let latest;
25
- for (const seq of snapshot.turnEnds.values()) {
26
- if (latest === undefined || seq > latest)
27
- latest = seq;
28
- }
29
- return latest;
30
- }
31
- function locationSettled(location) {
32
- if (location.kind === 'turn')
33
- return location.turn.status === 'closed';
34
- if (location.kind === 'step')
35
- return location.turn.status === 'closed' && location.step.status === 'closed';
36
- return false;
37
- }
38
- function childSnapshot(face) {
39
- const snapshot = face.getSnapshot();
40
- const pending = snapshot.pending[0];
41
- const status = pending?.kind === 'approval'
42
- ? 'needs-approval'
43
- : pending?.kind === 'question'
44
- ? 'needs-input'
45
- : snapshot.openState === 'error' || snapshot.lastAgentError !== null
46
- ? 'failed'
47
- : snapshot.running
48
- ? 'running'
49
- : 'idle';
50
- return { status };
51
- }
52
- class Rc6SessionBinding {
53
- face;
54
- sessionId;
55
- constructor(face) {
56
- this.face = face;
57
- this.sessionId = sideChatSessionId(face.sessionId);
58
- }
59
- getSnapshot = () => childSnapshot(this.face);
60
- subscribe = (listener) => this.face.subscribe(listener);
61
- async prompt(content, mode) {
62
- const result = await this.face.prompt(content.map(part => ({ ...part })), mode);
63
- return result.ok
64
- ? { ok: true }
65
- : { ok: false, error: sideChatError(result.error, 'side_chat_prompt_failed') };
66
- }
67
- async updateQueue(itemId, action) {
68
- if (action.kind === 'edit' && action.content.some(part => part.type !== 'text')) {
69
- return {
70
- ok: false,
71
- error: operationError('invalid_request', 'Queued image messages cannot be edited in the Side Chat panel.'),
72
- };
73
- }
74
- const normalized = action.kind === 'edit'
75
- ? { kind: 'edit', content: action.content.map(part => ({ type: 'text', text: part.type === 'text' ? part.text : '' })) }
76
- : action;
77
- const result = await this.face.updateQueue(itemId, normalized);
78
- return result.ok
79
- ? { ok: true }
80
- : { ok: false, error: sideChatError(result.error, 'side_chat_prompt_failed') };
81
- }
82
- async cancel() {
83
- const result = await this.face.cancel();
84
- return result.ok
85
- ? { ok: true }
86
- : { ok: false, error: sideChatError(result.error, 'side_chat_interrupt_failed') };
87
- }
88
- async respondApproval(interactionId, decision) {
89
- const wait = this.face.getSnapshot().pending.find((item) => item.key === interactionId && item.kind === 'approval');
90
- if (wait === undefined) {
91
- return { ok: false, error: operationError('invalid_request', 'The approval is no longer pending.') };
92
- }
93
- try {
94
- return await wait.respond(decision)
95
- ? { ok: true }
96
- : { ok: false, error: operationError('transport_error', 'The approval response arrived too late.') };
97
- }
98
- catch {
99
- return { ok: false, error: operationError('transport_error', 'The approval response failed.') };
100
- }
101
- }
102
- async respondQuestion(interactionId, answer) {
103
- const wait = this.face.getSnapshot().pending.find((item) => item.key === interactionId && item.kind === 'question');
104
- if (wait === undefined) {
105
- return { ok: false, error: operationError('invalid_request', 'The question is no longer pending.') };
106
- }
107
- try {
108
- const normalized = answer === null
109
- ? null
110
- : {
111
- answers: answer.answers.map(item => ({
112
- id: item.id,
113
- selected: [...item.selected],
114
- ...(item.custom === undefined ? {} : { custom: item.custom }),
115
- })),
116
- };
117
- return await wait.respond(normalized)
118
- ? { ok: true }
119
- : { ok: false, error: operationError('transport_error', 'The question response arrived too late.') };
120
- }
121
- catch {
122
- return { ok: false, error: operationError('transport_error', 'The question response failed.') };
123
- }
124
- }
125
- }
126
- /** Adapter over rc.6's public SessionRuntime and exported concrete Session type. */
127
- export class Rc6SideChatSessions {
128
- ctx;
129
- renamed = new Set();
130
- faces = new Map();
131
- parentInputs = new WeakMap();
132
- annotationPersistence = new ConversationAnnotationPersistence();
133
- modelPreferences = new SideChatModelPreferences();
134
- constructor(ctx) {
135
- this.ctx = ctx;
136
- }
137
- /** Observable rc.6 Session-list surface used to follow main-session switches. */
138
- subscribeList = (listener) => this.ctx.sessions.list.subscribe(listener);
139
- /** Follow both current-session switches and that session's composer state. */
140
- subscribeConversationInput = (listener) => {
141
- let input;
142
- let removeInputListener = () => { };
143
- const bindInput = () => {
144
- const next = this.currentParentInput();
145
- if (next === input)
146
- return;
147
- removeInputListener();
148
- input = next;
149
- removeInputListener = input?.state.subscribe?.(listener) ?? (() => { });
150
- };
151
- bindInput();
152
- const removeListListener = this.ctx.sessions.list.subscribe(() => {
153
- bindInput();
154
- listener();
155
- });
156
- return () => {
157
- removeListListener();
158
- removeInputListener();
159
- };
160
- };
161
- currentConversationInputSnapshot = () => this.currentParentInput()?.state.getSnapshot();
162
- currentSessionId() {
163
- const current = this.ctx.sessions.list.getSnapshot().current;
164
- return current === undefined ? undefined : sideChatSessionId(current);
165
- }
166
- lastCompletedSeq(parentSessionId) {
167
- const snapshot = this.face(parentSessionId)?.getSnapshot();
168
- return snapshot === undefined ? undefined : latestCompleted(snapshot);
169
- }
170
- selectionIsCurrent(selection) {
171
- if (this.currentSessionId() !== selection.parentSessionId)
172
- return false;
173
- const snapshot = this.face(selection.parentSessionId)?.getSnapshot();
174
- if (snapshot === undefined)
175
- return false;
176
- return selection.fragments.every((fragment) => {
177
- const node = snapshot.chatNodes.get(fragment.nodeKey);
178
- return node !== undefined
179
- && node.visibility === 'visible'
180
- && node.kind === fragment.nodeKind
181
- && node.anchorSeq === fragment.seq
182
- && locationSettled(node.location);
183
- });
184
- }
185
- /** Add one selected passage to the native composer of its parent Session. */
186
- addSelectionToConversation(selection, comment) {
187
- if (this.currentSessionId() !== selection.parentSessionId)
188
- return false;
189
- const input = this.currentParentInput();
190
- if (input === undefined || !addSelectionToParentComposer(input, selection, comment))
191
- return false;
192
- this.annotationPersistence.reconcile(selection.parentSessionId, input);
193
- return true;
194
- }
195
- /** Add one immutable Side Chat transcript to its parent Session's composer. */
196
- addSideChatToConversation(parentSessionId, reference) {
197
- const scope = this.ctx.sessions.scope(dshSessionId(parentSessionId));
198
- const input = scope === undefined ? undefined : this.parentInput(scope);
199
- if (input === undefined || !addReferencedSideChatToParentComposer(input, reference))
200
- return false;
201
- this.annotationPersistence.reconcile(parentSessionId, input);
202
- return true;
203
- }
204
- /** Remove one existing unsent annotation from the aggregated occurrence. */
205
- removeConversationAnnotation(annotationIndex) {
206
- const sessionId = this.currentSessionId();
207
- const input = this.currentParentInput();
208
- if (sessionId === undefined
209
- || input === undefined
210
- || !removeParentConversationAnnotation(input, annotationIndex))
211
- return false;
212
- this.annotationPersistence.reconcile(sessionId, input);
213
- return true;
214
- }
215
- /** Update an existing unsent annotation without adding a duplicate passage. */
216
- updateConversationAnnotation(annotationIndex, comment) {
217
- const sessionId = this.currentSessionId();
218
- const input = this.currentParentInput();
219
- if (sessionId === undefined
220
- || input === undefined
221
- || !updateParentConversationAnnotation(input, annotationIndex, comment))
222
- return false;
223
- this.annotationPersistence.reconcile(sessionId, input);
224
- return true;
225
- }
226
- /** Mirror or recover the current Session's unsent annotation occurrence. */
227
- reconcileConversationAnnotationPersistence() {
228
- const sessionId = this.currentSessionId();
229
- const input = this.currentParentInput();
230
- if (sessionId !== undefined && input !== undefined) {
231
- this.annotationPersistence.reconcile(sessionId, input);
232
- }
233
- }
234
- /** Number assigned to the next annotation shown beside the selected passage. */
235
- nextConversationAnnotationNumber() {
236
- const snapshot = this.currentConversationInputSnapshot();
237
- return snapshot === undefined ? 1 : conversationAnnotations(snapshot).length + 1;
238
- }
239
- /** Remove the current Session's unsent selected-passage annotations. */
240
- removeConversationAnnotations() {
241
- const sessionId = this.currentSessionId();
242
- const input = this.currentParentInput();
243
- if (sessionId === undefined
244
- || input === undefined
245
- || !removeParentConversationAnnotations(input))
246
- return false;
247
- this.annotationPersistence.reconcile(sessionId, input);
248
- return true;
249
- }
250
- async retain(sessionId) {
251
- const binding = await this.waitForBinding(dshSessionId(sessionId));
252
- const face = this.adaptedFace(binding.session);
253
- await face.open();
254
- if (!this.renamed.has(sessionId)) {
255
- this.renamed.add(sessionId);
256
- const parentTitle = this.ctx.sessions.list.getSnapshot().byId[dshSessionId(sessionId)]?.displayTitle;
257
- const title = parentTitle === undefined ? 'Side Chat' : `Side Chat · ${parentTitle}`;
258
- await face.rename(title.slice(0, 160)).catch(() => undefined);
259
- }
260
- const adapted = new Rc6SessionBinding(face);
261
- return { sessionId, binding: adapted, release: () => { } };
262
- }
263
- async openSession(sessionId) {
264
- this.ctx.sessions.open(dshSessionId(sessionId));
265
- }
266
- notify(message) {
267
- const method = message.kind === 'warning' ? 'warn' : 'info';
268
- console[method](`[dsh-side-chat] ${message.text}`);
269
- }
270
- face(sessionId) {
271
- const source = this.ctx.sessions.binding(dshSessionId(sessionId))?.session;
272
- return source === undefined ? undefined : this.adaptedFace(source);
273
- }
274
- title(sessionId) {
275
- return this.ctx.sessions.list.getSnapshot().byId[dshSessionId(sessionId)]?.displayTitle;
276
- }
277
- cwd(sessionId) {
278
- return this.ctx.sessions.list.getSnapshot().byId[dshSessionId(sessionId)]?.cwd;
279
- }
280
- modelDirectory(sessionId) {
281
- try {
282
- return this.ctx.modelDirectories.directoryFor(dshSessionId(sessionId));
283
- }
284
- catch {
285
- return undefined;
286
- }
287
- }
288
- sideChatModelPreference() {
289
- return this.modelPreferences.get();
290
- }
291
- rememberSideChatModelPreference(selection) {
292
- this.modelPreferences.set(selection);
293
- }
294
- adaptedFace(source) {
295
- const existing = this.faces.get(source.sessionId);
296
- if (existing?.source === source)
297
- return existing.compatible;
298
- const uiConversation = this.ctx.get('uiConversation');
299
- const uiSession = this.ctx.get('uiSession');
300
- const chat = uiConversation?.binding(source.sessionId).target('chat');
301
- const pending = uiSession?.pendingInteractions;
302
- const compatible = compatibleConversationFace(source, {
303
- ...(chat === undefined ? {} : { chat }),
304
- ...(pending === undefined ? {} : { pending }),
305
- });
306
- this.faces.set(source.sessionId, { source, compatible });
307
- return compatible;
308
- }
309
- currentParentInput() {
310
- const sessionId = this.currentSessionId();
311
- if (sessionId === undefined)
312
- return;
313
- const scope = this.ctx.sessions.scope(dshSessionId(sessionId));
314
- return scope === undefined ? undefined : this.parentInput(scope);
315
- }
316
- parentInput(scope) {
317
- const source = this.ctx.conversation.input.for(scope);
318
- if (source === undefined || this.ctx.get('uiConversation') === undefined)
319
- return source;
320
- const existing = this.parentInputs.get(source);
321
- if (existing !== undefined)
322
- return existing;
323
- const adapted = {
324
- referenceMode: 'lexical',
325
- state: source.state,
326
- setDraft: text => { source.setDraft(text); },
327
- insertReference: (reference, span) => source.insertReference(reference, span),
328
- replaceText: (text, span) => scope.bail(scope, 'slash/input-insert-text', { text, span }) === true,
329
- };
330
- this.parentInputs.set(source, adapted);
331
- return adapted;
332
- }
333
- waitForBinding(sessionId) {
334
- const immediate = this.ctx.sessions.binding(sessionId);
335
- if (immediate !== undefined)
336
- return Promise.resolve(immediate);
337
- return new Promise((resolve, reject) => {
338
- let settled = false;
339
- const finish = () => {
340
- if (settled)
341
- return;
342
- const binding = this.ctx.sessions.binding(sessionId);
343
- if (binding === undefined)
344
- return;
345
- settled = true;
346
- clearTimeout(timer);
347
- unsubscribe();
348
- resolve(binding);
349
- };
350
- const unsubscribe = this.ctx.sessions.list.subscribe(finish);
351
- const timer = setTimeout(() => {
352
- if (settled)
353
- return;
354
- settled = true;
355
- unsubscribe();
356
- reject(new Error(`Side Chat child ${sessionId} did not appear in the rc.6 Session list.`));
357
- }, BINDING_WAIT_MS);
358
- finish();
359
- });
360
- }
361
- }
362
- export function selectionDescriptor(snapshot, anchorKey) {
363
- const node = snapshot.chatNodes.get(anchorKey);
364
- if (node === undefined || node.visibility !== 'visible')
365
- return undefined;
366
- const source = node.kind === 'user' || node.kind === 'steering'
367
- ? 'user'
368
- : node.kind === 'assistant-step'
369
- ? 'assistant'
370
- : node.kind === 'context'
371
- ? 'context'
372
- : undefined;
373
- const turn = node.location.kind === 'turn' || node.location.kind === 'step'
374
- ? node.location.turn.turn
375
- : undefined;
376
- if (source === undefined || turn === undefined)
377
- return undefined;
378
- return {
379
- nodeKey: node.key,
380
- nodeKind: node.kind,
381
- turnKey: `turn:${String(turn)}`,
382
- seq: node.anchorSeq,
383
- source,
384
- modelVisible: true,
385
- settled: locationSettled(node.location),
386
- };
387
- }
@@ -1,28 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
3
- import type { CloseSideChatRequest, CloseSideChatValue, CreateSideChatRequest, CreateSideChatValue, SelectSideChatModelRequest, SelectSideChatModelValue, SideChatResult } from '../shared/contracts.js';
4
- interface BoundaryCut {
5
- readonly boundarySeq: number;
6
- readonly cut: number;
7
- readonly inheritedThroughSeq: number;
8
- }
9
- declare function boundaryCut(events: readonly SessionEvent[], atSeq: number): BoundaryCut | undefined;
10
- /** Stock rc.6 implementation backed by one ordinary archived Session fork. */
11
- export declare class ArchivedForkSideChatService {
12
- private readonly ctx;
13
- private readonly records;
14
- private readonly pendingCreates;
15
- private disposed;
16
- constructor(ctx: Context);
17
- create(request: CreateSideChatRequest): Promise<SideChatResult<CreateSideChatValue>>;
18
- selectModel(request: SelectSideChatModelRequest): Promise<SideChatResult<SelectSideChatModelValue>>;
19
- close(request: CloseSideChatRequest): Promise<SideChatResult<CloseSideChatValue>>;
20
- dispose(): Promise<void>;
21
- private createFork;
22
- private resolveModelSelection;
23
- private readParent;
24
- private resolveComposition;
25
- private closeRecord;
26
- private archiveAndDispose;
27
- }
28
- export { boundaryCut as resolveArchivedForkBoundary };