@jingyi0605/codingns4dsh 0.1.1 → 0.1.7-rc.2

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 (167) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +14 -5
  3. package/data/build/dist/client/account-bar.js +57 -3
  4. package/data/build/dist/client/bundle.js +36245 -267
  5. package/data/build/dist/client/cli-slots.js +60 -18
  6. package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
  7. package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
  8. package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
  9. package/data/build/dist/client/features/file-management-panel.js +48 -0
  10. package/data/build/dist/client/features/file-management.d.ts +4 -0
  11. package/data/build/dist/client/features/file-management.js +50 -0
  12. package/data/build/dist/client/features/index.d.ts +10 -1
  13. package/data/build/dist/client/features/index.js +12 -1
  14. package/data/build/dist/client/features/login-protection-panel.js +1 -1
  15. package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
  16. package/data/build/dist/client/features/login-protection-session.js +18 -0
  17. package/data/build/dist/client/features/peer-host.d.ts +4 -0
  18. package/data/build/dist/client/features/peer-host.js +89 -0
  19. package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
  20. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
  21. package/data/build/dist/client/features/reverse-proxy.js +75 -5
  22. package/data/build/dist/client/features/types.d.ts +9 -0
  23. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
  24. package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
  25. package/data/build/dist/client/file-management-dom.d.ts +12 -0
  26. package/data/build/dist/client/file-management-dom.js +527 -0
  27. package/data/build/dist/client/git-management.js +24 -5
  28. package/data/build/dist/client/host-navigation.d.ts +29 -0
  29. package/data/build/dist/client/host-navigation.js +29 -0
  30. package/data/build/dist/client/host-router.d.ts +3 -0
  31. package/data/build/dist/client/host-router.js +2 -0
  32. package/data/build/dist/client/index.d.ts +6 -1
  33. package/data/build/dist/client/index.js +30 -10
  34. package/data/build/dist/client/locale.js +36 -7
  35. package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
  36. package/data/build/dist/client/peer-host-connection-button.js +69 -0
  37. package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
  38. package/data/build/dist/client/peer-host-management-api.js +33 -0
  39. package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
  40. package/data/build/dist/client/peer-host-management-panel.js +181 -0
  41. package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
  42. package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
  43. package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
  44. package/data/build/dist/client/peer-host-scoped-client.js +316 -0
  45. package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
  46. package/data/build/dist/client/peer-host-session-controller.js +95 -0
  47. package/data/build/dist/client/quick-phrase-slot.js +14 -0
  48. package/data/build/dist/client/remote-web-context.js +26 -4
  49. package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
  50. package/data/build/dist/client/session-changed-files-view.js +337 -0
  51. package/data/build/dist/client/settings-bridge.d.ts +14 -4
  52. package/data/build/dist/client/settings-bridge.js +59 -12
  53. package/data/build/dist/client/settings-section.js +17 -4
  54. package/data/build/dist/client/subscription-slot.js +21 -8
  55. package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
  56. package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
  57. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
  58. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
  59. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
  60. package/data/build/dist/dsh-capabilities/matrix.js +9 -0
  61. package/data/build/dist/dsh-capabilities/routes.js +35 -0
  62. package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
  63. package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
  64. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  65. package/data/build/dist/features/host-router.d.ts +26 -0
  66. package/data/build/dist/features/host-router.js +110 -0
  67. package/data/build/dist/features/registry.js +3 -0
  68. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
  69. package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
  70. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
  71. package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
  72. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  73. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
  74. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
  75. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
  76. package/data/build/dist/host/cli-adapters/feature.js +94 -10
  77. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
  78. package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
  79. package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
  80. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
  81. package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
  82. package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
  83. package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
  84. package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
  85. package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
  86. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
  87. package/data/build/dist/host/cli-adapters/registry.js +41 -7
  88. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
  89. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  90. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
  91. package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
  92. package/data/build/dist/host/dsh-device-runtime.js +55 -29
  93. package/data/build/dist/host/features/auth.js +10 -2
  94. package/data/build/dist/host/features/file-management.d.ts +5 -0
  95. package/data/build/dist/host/features/file-management.js +267 -0
  96. package/data/build/dist/host/features/git-management.js +24 -2
  97. package/data/build/dist/host/features/index.d.ts +2 -0
  98. package/data/build/dist/host/features/index.js +6 -0
  99. package/data/build/dist/host/features/peer-host.d.ts +22 -0
  100. package/data/build/dist/host/features/peer-host.js +322 -0
  101. package/data/build/dist/host/features/types.d.ts +4 -0
  102. package/data/build/dist/host/host-router.d.ts +3 -0
  103. package/data/build/dist/host/host-router.js +2 -0
  104. package/data/build/dist/host/index-injection.d.ts +17 -0
  105. package/data/build/dist/host/index-injection.js +24 -0
  106. package/data/build/dist/host/index.d.ts +4 -1
  107. package/data/build/dist/host/index.js +41 -6
  108. package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
  109. package/data/build/dist/host/lan-access-dsh.js +145 -26
  110. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
  111. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
  112. package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
  113. package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
  114. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
  115. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
  116. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
  117. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
  118. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
  119. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
  120. package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
  121. package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
  122. package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
  123. package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
  124. package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
  125. package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
  126. package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
  127. package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
  128. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
  129. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
  130. package/data/build/dist/host/native-session-bridge.d.ts +18 -2
  131. package/data/build/dist/host/native-session-bridge.js +232 -8
  132. package/data/build/dist/host/remote-web-runtime.js +26 -1
  133. package/data/build/dist/host/rpc.js +8 -3
  134. package/data/build/dist/host/session-changes.d.ts +5 -0
  135. package/data/build/dist/host/session-changes.js +163 -0
  136. package/data/build/dist/host/settings.js +6 -0
  137. package/data/build/dist/index.d.ts +1 -1
  138. package/data/build/dist/index.js +1 -1
  139. package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
  140. package/data/build/dist/shared/contracts/config.d.ts +15 -0
  141. package/data/build/dist/shared/contracts/config.js +10 -0
  142. package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
  143. package/data/build/dist/shared/contracts/errors.d.ts +14 -0
  144. package/data/build/dist/shared/contracts/errors.js +15 -0
  145. package/data/build/dist/shared/contracts/feature.d.ts +2 -0
  146. package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
  147. package/data/build/dist/shared/contracts/file-management.js +3 -0
  148. package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
  149. package/data/build/dist/shared/contracts/peer-host.js +19 -0
  150. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  151. package/data/build/dist/shared/contracts/version.js +1 -1
  152. package/data/build/dist/shared/index.d.ts +4 -3
  153. package/data/build/dist/shared/index.js +2 -2
  154. package/data/build/dist/transport/debug.d.ts +5 -0
  155. package/data/build/dist/transport/debug.js +23 -1
  156. package/data/build/dist/transport/dsh-transport.d.ts +2 -0
  157. package/data/build/dist/transport/dsh-transport.js +11 -0
  158. package/data/build/dist/transport/webrtc-client.d.ts +2 -0
  159. package/data/build/dist/transport/webrtc-client.js +2 -1
  160. package/data/build/dist/transport/webrtc-host.d.ts +2 -0
  161. package/data/build/dist/transport/webrtc-host.js +6 -3
  162. package/dsh.bundle.patch +10 -0
  163. package/package.json +23 -11
  164. package/profile/README.md +2 -2
  165. package/profile/package.json +2 -2
  166. package/profile/version.json +1 -1
  167. package/version.json +1 -1
@@ -16,9 +16,17 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
16
16
  ? ctx.on.bind(ctx)
17
17
  : undefined;
18
18
  const externalHandles = new Map();
19
+ const compactionStates = new Map();
20
+ const confirmedContextWindows = new Map();
21
+ // Agent.inject() 只是把消息放进队列,新的 DSH step 会在稍后的事件循环中
22
+ // 才创建。此期间 activeStep 仍然指向旧 step,工具追加必须暂缓,否则会把
23
+ // 下一步的工具写进旧 step;step/start 到达后由工具投影器重试。
24
+ const pendingStepTransitions = new Set();
19
25
  let injectedStepSequence = 0;
20
26
  const modernInjectedSource = isModernDshVersion(dshVersion);
21
27
  const appendNativeToolCall = (sessionId, call) => {
28
+ if (on !== undefined && pendingStepTransitions.has(sessionId))
29
+ return null;
22
30
  const session = appendableSession(store?.get(sessionId));
23
31
  const position = session === null ? null : activeStep(session);
24
32
  if (session === null || position === null)
@@ -91,11 +99,60 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
91
99
  // 投影先清空旧 pressure,再等待下一条 usage,ContextMeter 因而闪烁。
92
100
  // 保留已有容量并跳过等价事件,让用量更新直接落到同一个组件上。
93
101
  const previous = latestRequestContext(session);
94
- if (previous !== null
95
- && previous.provider === context.provider
96
- && previous.model === context.model
97
- && (context.contextWindow === undefined || previous.contextWindow === context.contextWindow))
98
- return true;
102
+ const contextKey = `${sessionId}\u0000${context.provider}\u0000${context.model}`;
103
+ if (previous !== null && previous.provider === context.provider && previous.model === context.model) {
104
+ const historicalWindow = historicalUsageContextWindow(session);
105
+ if (context.contextWindow === undefined) {
106
+ if (historicalWindow !== undefined && previous.contextWindow !== historicalWindow) {
107
+ session.append('request/context', {
108
+ provider: context.provider,
109
+ model: context.model,
110
+ contextWindow: historicalWindow,
111
+ });
112
+ }
113
+ return true;
114
+ }
115
+ if (previous.contextWindow === context.contextWindow
116
+ && context.confirmed === true
117
+ && historicalWindow !== undefined
118
+ && historicalWindow !== context.contextWindow) {
119
+ session.append('request/context', {
120
+ provider: context.provider,
121
+ model: context.model,
122
+ contextWindow: historicalWindow,
123
+ });
124
+ confirmedContextWindows.set(contextKey, historicalWindow);
125
+ return true;
126
+ }
127
+ if (previous.contextWindow === context.contextWindow) {
128
+ if (context.confirmed === true && context.source !== 'catalog' && context.contextWindow !== undefined) {
129
+ confirmedContextWindows.set(contextKey, context.contextWindow);
130
+ }
131
+ return true;
132
+ }
133
+ // 已经确认过的同一路由容量是会话级事实。迟到/全局 usage 不能覆盖它。
134
+ // DSH 每个新 step 会先写入一次通用 1M 占位值。catalog 提示必须能把
135
+ // 这个占位值恢复为已知容量,但真实 Provider usage 仍由锁定值保护。
136
+ if (confirmedContextWindows.has(contextKey) && context.source !== 'catalog')
137
+ return true;
138
+ // 进程重启后仍可从历史 usage 恢复稳定容量,避免尾部遗留的错误
139
+ // request/context=1M 在第二轮开始时再次成为当前窗口。
140
+ if (context.confirmed === true && historicalWindow !== undefined && historicalWindow !== context.contextWindow) {
141
+ if (previous.contextWindow !== historicalWindow) {
142
+ session.append('request/context', {
143
+ provider: context.provider,
144
+ model: context.model,
145
+ contextWindow: historicalWindow,
146
+ });
147
+ }
148
+ confirmedContextWindows.set(contextKey, historicalWindow);
149
+ return true;
150
+ }
151
+ // 允许本次运行首次明确确认的 usage 修正历史遗留的路由占位值,
152
+ // 例如旧日志中的 1M 随后被 Codex usage 明确纠正为 258400。
153
+ if (context.confirmed !== true)
154
+ return true;
155
+ }
99
156
  // Registry 在一轮开始时只能提供适配器身份,不能提供 Provider 容量。
100
157
  // 继承上一条已知容量,避免先写无容量事件导致 ContextMeter 卸载;
101
158
  // 真正的 usage 到达后,投影器会用 Provider 的最新容量覆盖它。
@@ -105,6 +162,9 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
105
162
  model: context.model,
106
163
  ...(contextWindow === undefined ? {} : { contextWindow }),
107
164
  });
165
+ if (context.confirmed === true && context.source !== 'catalog' && context.contextWindow !== undefined) {
166
+ confirmedContextWindows.set(contextKey, context.contextWindow);
167
+ }
108
168
  return true;
109
169
  }
110
170
  catch {
@@ -117,6 +177,18 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
117
177
  if (session === null || position === null)
118
178
  return false;
119
179
  try {
180
+ const previousContext = latestRequestContext(session);
181
+ const normalizedUsage = previousContext?.contextWindow !== undefined
182
+ && usage.contextWindow !== undefined
183
+ && usage.contextWindow !== previousContext.contextWindow
184
+ ? {
185
+ ...usage,
186
+ contextWindow: previousContext.contextWindow,
187
+ ...(usage.contextTokens === undefined ? {} : {
188
+ contextUsageRatio: Number(Math.min(1, usage.contextTokens / previousContext.contextWindow).toFixed(6)),
189
+ }),
190
+ }
191
+ : usage;
120
192
  session.append('assistant/attempt', {
121
193
  turn: position.turn,
122
194
  step: position.step,
@@ -125,7 +197,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
125
197
  time: Date.now(),
126
198
  chunk: {
127
199
  type: 'usage',
128
- usage: compactUsage(usage),
200
+ usage: compactUsage(normalizedUsage),
129
201
  },
130
202
  }],
131
203
  });
@@ -135,6 +207,72 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
135
207
  return false;
136
208
  }
137
209
  };
210
+ const appendNativeCompactionEvent = (sessionId, event) => {
211
+ const session = appendableSession(store?.get(sessionId));
212
+ if (session === null)
213
+ return false;
214
+ try {
215
+ if (event.phase === 'start') {
216
+ if (compactionStates.has(sessionId))
217
+ return true;
218
+ const compactionId = event.compactionId?.trim() || `codingns-compaction-${randomUUID()}`;
219
+ const position = activeStep(session);
220
+ session.append('compaction/start', {
221
+ compactionId,
222
+ turn: position?.turn ?? null,
223
+ });
224
+ compactionStates.set(sessionId, {
225
+ compactionId,
226
+ turn: position?.turn ?? null,
227
+ summaryAppended: false,
228
+ });
229
+ return true;
230
+ }
231
+ const state = compactionStates.get(sessionId);
232
+ if (state === undefined)
233
+ return false;
234
+ if ((event.phase === 'summary' || event.phase === 'end' && event.summary !== undefined) && !state.summaryAppended) {
235
+ const range = compactionSurfaceRange(session);
236
+ if (range !== null) {
237
+ const summary = boundCompactionSummary(event.summary);
238
+ session.append('compaction/summary', {
239
+ compactionId: state.compactionId,
240
+ summary: [{ type: 'text', text: summary }],
241
+ shadowedRange: { start: range.start, end: range.end },
242
+ shadowedSeqs: range.seqs,
243
+ shadowedTokenCount: safeCount(event.shadowedTokenCount),
244
+ provider: event.provider?.trim() || 'codingns-external',
245
+ model: event.model?.trim() || 'external-agent',
246
+ });
247
+ // DSH 前端的 compaction 节点由紧随 summary 的 checkpoint 替换事件生成。
248
+ session.append('user/message', {
249
+ id: `codingns-compaction-${state.compactionId}`,
250
+ role: 'user',
251
+ content: [{ type: 'text', text: summary }],
252
+ source: { kind: 'plugin', plugin: 'compact' },
253
+ }, {
254
+ surfaceOp: { op: 'replace', startSeq: range.start, endSeq: range.end },
255
+ sourceEventSeqs: range.seqs,
256
+ });
257
+ state.summaryAppended = true;
258
+ }
259
+ return true;
260
+ }
261
+ if (event.phase === 'end') {
262
+ session.append('compaction/end', {
263
+ compactionId: state.compactionId,
264
+ turn: state.turn,
265
+ ...(event.error?.trim() ? { error: event.error.trim().slice(0, 512) } : !state.summaryAppended ? { error: 'Provider 未返回可投影的压缩摘要。' } : {}),
266
+ });
267
+ compactionStates.delete(sessionId);
268
+ }
269
+ return true;
270
+ }
271
+ catch {
272
+ // 原生服务缺失或版本不支持 compaction 事件时,不能阻断 Provider 回合。
273
+ return false;
274
+ }
275
+ };
138
276
  const injectNativeNextStep = (sessionId, summary = '外部工具已完成,继续处理当前任务。') => {
139
277
  const agent = nativeAgent(ctx, sessionId);
140
278
  if (agent === null || typeof agent.inject !== 'function')
@@ -142,7 +280,8 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
142
280
  injectedStepSequence += 1;
143
281
  const boundedSummary = summary.trim().slice(0, 120) || '外部工具已完成,继续处理当前任务。';
144
282
  try {
145
- ;
283
+ if (on !== undefined)
284
+ pendingStepTransitions.add(sessionId);
146
285
  agent.inject({
147
286
  id: `codingns-external-step-${injectedStepSequence}-${randomUUID()}`,
148
287
  role: 'user',
@@ -154,6 +293,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
154
293
  return true;
155
294
  }
156
295
  catch {
296
+ pendingStepTransitions.delete(sessionId);
157
297
  return false;
158
298
  }
159
299
  };
@@ -222,6 +362,9 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
222
362
  appendUsageSample(sessionId, usage) {
223
363
  return appendNativeUsageSample(sessionId, usage);
224
364
  },
365
+ appendCompactionEvent(sessionId, event) {
366
+ return appendNativeCompactionEvent(sessionId, event);
367
+ },
225
368
  canInjectNextStep(sessionId) {
226
369
  return canInjectNativeNextStep(sessionId);
227
370
  },
@@ -319,7 +462,15 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
319
462
  subscribe(handlers) {
320
463
  const disposers = [];
321
464
  if (on !== undefined && handlers.onEvent !== undefined) {
322
- disposers.push(on('session/event', (session, event) => handlers.onEvent?.(session, event)));
465
+ disposers.push(on('session/event', (session, event) => {
466
+ if (isNativeStepStart(event)) {
467
+ for (const sessionId of pendingStepTransitions) {
468
+ if (store?.get(sessionId) === session)
469
+ pendingStepTransitions.delete(sessionId);
470
+ }
471
+ }
472
+ handlers.onEvent?.(session, event);
473
+ }));
323
474
  }
324
475
  if (on !== undefined && handlers.onFlush !== undefined) {
325
476
  disposers.push(on('session/flush', (session) => handlers.onFlush?.(session)));
@@ -336,6 +487,9 @@ function appendableSession(value) {
336
487
  return null;
337
488
  return value;
338
489
  }
490
+ function isNativeStepStart(value) {
491
+ return isRecord(value) && value.type === 'step/start';
492
+ }
339
493
  function latestRequestContext(session) {
340
494
  let events;
341
495
  try {
@@ -362,6 +516,37 @@ function latestRequestContext(session) {
362
516
  }
363
517
  return null;
364
518
  }
519
+ function historicalUsageContextWindow(session) {
520
+ let events;
521
+ try {
522
+ events = session.snapshotEvents();
523
+ }
524
+ catch {
525
+ return undefined;
526
+ }
527
+ const counts = new Map();
528
+ for (const candidate of events) {
529
+ const event = isRecord(candidate) && candidate.type === 'assistant/attempt' ? candidate : null;
530
+ const data = isRecord(event?.data) ? event.data : null;
531
+ const stream = Array.isArray(data?.stream) ? data.stream : [];
532
+ for (const entry of stream) {
533
+ const chunk = isRecord(entry) && isRecord(entry.chunk) ? entry.chunk : null;
534
+ const usage = isRecord(chunk?.usage) ? chunk.usage : null;
535
+ const window = usage?.contextWindow;
536
+ if (typeof window === 'number' && Number.isFinite(window) && window > 0)
537
+ counts.set(window, (counts.get(window) ?? 0) + 1);
538
+ }
539
+ }
540
+ let selected;
541
+ let count = 0;
542
+ for (const [window, occurrences] of counts) {
543
+ if (occurrences >= count) {
544
+ selected = window;
545
+ count = occurrences;
546
+ }
547
+ }
548
+ return selected;
549
+ }
365
550
  function activeStep(session) {
366
551
  let events;
367
552
  try {
@@ -390,6 +575,45 @@ function activeStep(session) {
390
575
  function eventSeq(value) {
391
576
  return isRecord(value) ? finiteInteger(value.seq) : null;
392
577
  }
578
+ function compactionSurfaceRange(session) {
579
+ let nodes = [];
580
+ try {
581
+ if (Array.isArray(session.surface?.nodes))
582
+ nodes = session.surface.nodes;
583
+ if (nodes.length === 0) {
584
+ nodes = session.snapshotEvents().flatMap((candidate) => {
585
+ if (!isRecord(candidate) || typeof candidate.seq !== 'number' || candidate.type === 'system/message')
586
+ return [];
587
+ const op = candidate.surfaceOp;
588
+ return op === 'append' ? [candidate.seq] : [];
589
+ });
590
+ }
591
+ const events = session.snapshotEvents();
592
+ let usable = nodes.filter((seq) => {
593
+ const event = session.eventAt?.(seq) ?? events[seq];
594
+ return !(isRecord(event) && event.type === 'system/message');
595
+ });
596
+ // 当前回合刚追加的 user/message 不是历史压缩对象,保留它避免把用户本轮
597
+ // 输入一并替换;旧 assistant/tool 节点仍可作为压缩范围。
598
+ const last = usable.at(-1);
599
+ const lastEvent = last === undefined ? undefined : session.eventAt?.(last) ?? events[last];
600
+ if (last !== undefined && isRecord(lastEvent) && lastEvent.type === 'user/message')
601
+ usable = usable.slice(0, -1);
602
+ if (usable.length === 0)
603
+ return null;
604
+ return { start: usable[0], end: usable.at(-1), seqs: usable };
605
+ }
606
+ catch {
607
+ return null;
608
+ }
609
+ }
610
+ function boundCompactionSummary(summary) {
611
+ const value = summary?.trim() || 'Codex 已压缩较早的上下文。';
612
+ return value.slice(0, 120);
613
+ }
614
+ function safeCount(value) {
615
+ return value !== undefined && Number.isSafeInteger(value) && value >= 0 ? value : 0;
616
+ }
393
617
  function finiteInteger(value) {
394
618
  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
395
619
  }
@@ -358,7 +358,7 @@ export function createLocalDshWebRuntimeProvider(options) {
358
358
  if (input.headers !== undefined)
359
359
  init.headers = Object.fromEntries(input.headers);
360
360
  if (input.body !== undefined)
361
- init.body = input.body;
361
+ init.body = normalizeConnectionRpcBody(path, input.method ?? 'GET', input.body);
362
362
  await ensureAuthenticated();
363
363
  const response = await fetchLocal(path, init);
364
364
  debug.log('web.provider.request.response', { sessionId: session.sessionId, path, method: input.method ?? 'GET', status: response.status });
@@ -409,6 +409,31 @@ function getSetCookie(headers) {
409
409
  }
410
410
  return undefined;
411
411
  }
412
+ /**
413
+ * DSH Gateway 的 Connection RPC 要求请求参数位于 payload.args。
414
+ * 旧版 DSH Web Client 曾把单个参数对象直接放进 payload;只在 `/api/*`
415
+ * 的 POST RPC 请求上补这一层,已经符合协议的请求保持字节级语义不变。
416
+ */
417
+ function normalizeConnectionRpcBody(path, method, body) {
418
+ if (method.toUpperCase() !== 'POST' || !path.startsWith('/api/'))
419
+ return body;
420
+ try {
421
+ const message = JSON.parse(body);
422
+ if (!isOfficialConnectionRpcMessage(message) || !isRecord(message.payload) || 'args' in message.payload)
423
+ return body;
424
+ return JSON.stringify({ ...message, payload: { args: message.payload } });
425
+ }
426
+ catch {
427
+ return body;
428
+ }
429
+ }
430
+ /** 只兼容 DSH 官方两段式 Remote endpoint,不能改写 CodingNS 自有 RPC。 */
431
+ function isOfficialConnectionRpcMessage(value) {
432
+ if (!isRecord(value) || value.type !== 'client-request' || typeof value.method !== 'string')
433
+ return false;
434
+ const parts = value.method.split('/');
435
+ return parts.length === 2 && parts.every((part) => part !== '') && parts[0] !== 'codingns';
436
+ }
412
437
  class RemoteWebRuntimeError extends Error {
413
438
  code;
414
439
  constructor(code, message) {
@@ -179,7 +179,9 @@ const CODINGNS_RPC_ENDPOINTS = [
179
179
  'debug/runtime/get', 'debug/runtime/list', 'debug/runtime/stop',
180
180
  'debug/port/check', 'debug/port/terminate', 'debug/port/kill', 'debug/proxy/get', 'debug/proxy/enable', 'debug/proxy/disable',
181
181
  'git/status', 'git/init', 'git/diff', 'git/stage', 'git/unstage', 'git/discard', 'git/commit', 'git/commit-diff', 'git/history', 'git/branches', 'git/switch', 'git/fetch', 'git/pull', 'git/push', 'git/undo',
182
- 'lanAccessDsh/addresses', 'lanAccessDsh/detect', 'lanAccessDsh/get', 'lanAccessDsh/settings/get', 'lanAccessDsh/settings/set', 'lanAccessDsh/login/get', 'lanAccessDsh/login/set', 'lanAccessDsh/login/session/open', 'lanAccessDsh/start', 'lanAccessDsh/stop',
182
+ 'fileManagement/session-changes', 'fileManagement/read', 'fileManagement/download', 'fileManagement/write', 'fileManagement/create-file', 'fileManagement/create-directory', 'fileManagement/rename', 'fileManagement/copy', 'fileManagement/move', 'fileManagement/delete', 'fileManagement/git-ignore',
183
+ 'lanAccessDsh/addresses', 'lanAccessDsh/detect', 'lanAccessDsh/get', 'lanAccessDsh/settings/get', 'lanAccessDsh/settings/set', 'lanAccessDsh/login/get', 'lanAccessDsh/login/set', 'lanAccessDsh/login/session/open', 'lanAccessDsh/login/session/refresh', 'lanAccessDsh/start', 'lanAccessDsh/stop',
184
+ 'peerHost/list', 'peerHost/diagnostics', 'peerHost/create', 'peerHost/update', 'peerHost/remove', 'peerHost/check', 'peerHost/reconnect', 'peerHost/login', 'peerHost/logout', 'peerHost/request', 'peerHost/wsEndpoint', 'peerHost/aggregate',
183
185
  'cli/catalog', 'cli/models', 'cli/adapter/set', 'cli/session/get', 'cli/session/set', 'cli/session/list', 'cli/session/adapter-map', 'cli/session/archive', 'cli/session/steer', 'cli/session/follow-up', 'cli/session/interrupt', 'cli/subscription',
184
186
  ];
185
187
  /** 创建远程设置处理器;只允许 Codingns4DSH 自己的 namespace 和路径编辑。 */
@@ -249,9 +251,12 @@ function isAllowedSettingsPath(path) {
249
251
  if (path.length === 1)
250
252
  return ['controlBaseUrl', 'controlBaseUrls', 'terminalEnhancement', 'workspaceSessionEnhancement'].includes(path[0] ?? '');
251
253
  if (path[0] === 'modules')
252
- return path.length === 2 && ['lanAccess', 'reverseProxy', 'cliAdapters', 'terminalEnhancement', 'workspaceSessionEnhancement', 'debug', 'gitManagement'].includes(path[1] ?? '');
254
+ return path.length === 2 && ['lanAccess', 'reverseProxy', 'cliAdapters', 'terminalEnhancement', 'workspaceSessionEnhancement', 'debug', 'gitManagement', 'fileManagement', 'peerHost'].includes(path[1] ?? '');
253
255
  if (path[0] === 'workspaceSessionEnhancement') {
254
- return path.length === 2 && ['showAdapterLogo', 'showArchivedSessions', 'showWorkspaceHiding', 'hiddenWorkspaceIds', 'showSubscriptionUsage', 'showQuickPhrases', 'quickPhrases', 'quickPhrasesSeeded'].includes(path[1] ?? '');
256
+ return path.length === 2 && ['showAdapterLogo', 'showArchivedSessions', 'showWorkspaceHiding', 'hiddenWorkspaceIds', 'showSubscriptionUsage', 'showQuickPhrases', 'rememberConversationRightbarRatio', 'quickPhrases', 'quickPhrasesSeeded'].includes(path[1] ?? '');
257
+ }
258
+ if (path[0] === 'fileManagement') {
259
+ return path.length === 2 && ['menuEnhancement', 'fileEditor', 'sessionChangedFiles'].includes(path[1] ?? '');
255
260
  }
256
261
  return path[0] === 'lanAccessDsh' && path.length === 2 && ['autoStart', 'listenHost', 'listenPort', 'dshPort'].includes(path[1] ?? '');
257
262
  }
@@ -0,0 +1,5 @@
1
+ import type { SessionChangedFiles } from '../shared/contracts/file-management.js';
2
+ import type { CodingNsHostServices } from './features/types.js';
3
+ /** 优先从 DSH 原生 Session 事件读取,原生事件不可用时回退到 JSONL。 */
4
+ export declare function readSessionChangedFiles(services: CodingNsHostServices, sessionId: string, workspaceRoot: string): Promise<SessionChangedFiles>;
5
+ //# sourceMappingURL=session-changes.d.ts.map
@@ -0,0 +1,163 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isAbsolute, normalize, relative, resolve } from 'node:path';
3
+ const PATH_KEYS = new Set([
4
+ 'path', 'paths', 'filePath', 'file_path', 'srcPath', 'src_path',
5
+ 'dstPath', 'dst_path', 'oldPath', 'old_path', 'newPath', 'new_path',
6
+ 'sourcePath', 'source_path', 'targetPath', 'target_path',
7
+ ]);
8
+ /** 优先从 DSH 原生 Session 事件读取,原生事件不可用时回退到 JSONL。 */
9
+ export async function readSessionChangedFiles(services, sessionId, workspaceRoot) {
10
+ let session = services.nativeSessions?.get(sessionId);
11
+ if (session === undefined && services.nativeSessions?.listRemote !== undefined) {
12
+ try {
13
+ const candidates = await services.nativeSessions.listRemote();
14
+ session = candidates.find((candidate) => sessionIdentity(candidate) === sessionId);
15
+ }
16
+ catch {
17
+ // 原生列表接口不可用时继续走 JSONL 降级。
18
+ }
19
+ }
20
+ const root = resolve(workspaceRoot);
21
+ const nativeEvents = snapshotEvents(session);
22
+ const nativePaths = extractToolPaths(nativeEvents, root);
23
+ if (nativePaths.size > 0)
24
+ return { paths: [...nativePaths].sort() };
25
+ const jsonlPath = findJsonlReference(session);
26
+ if (jsonlPath === undefined)
27
+ return { paths: [] };
28
+ try {
29
+ const content = await readFile(jsonlPath, 'utf8');
30
+ const events = content.split(/\r?\n/u).flatMap((line) => {
31
+ try {
32
+ const parsed = JSON.parse(line);
33
+ return [parsed];
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ });
39
+ return { paths: [...extractToolPaths(events, root)].sort() };
40
+ }
41
+ catch {
42
+ // JSONL 是降级数据源;文件不存在或不可读时保持可解释的空结果。
43
+ return { paths: [] };
44
+ }
45
+ }
46
+ function snapshotEvents(value) {
47
+ if (!isRecord(value))
48
+ return [];
49
+ if (typeof value.snapshotEvents === 'function') {
50
+ try {
51
+ const events = value.snapshotEvents();
52
+ if (Array.isArray(events))
53
+ return events;
54
+ }
55
+ catch {
56
+ // 单个会话损坏时继续尝试其内嵌事件数组。
57
+ }
58
+ }
59
+ return Array.isArray(value.events) ? value.events : [];
60
+ }
61
+ function extractToolPaths(events, workspaceRoot) {
62
+ const paths = new Set();
63
+ for (const candidate of events) {
64
+ const event = isRecord(candidate) ? candidate : undefined;
65
+ if (event === undefined)
66
+ continue;
67
+ const data = isRecord(event.data) ? event.data : event;
68
+ if (event.type === 'tool/call') {
69
+ collectInputPaths(data.arguments, workspaceRoot, paths);
70
+ continue;
71
+ }
72
+ if (event.type === 'tool/result') {
73
+ collectInputPaths(data.meta, workspaceRoot, paths);
74
+ continue;
75
+ }
76
+ if (event.type !== 'assistant/message')
77
+ continue;
78
+ const message = isRecord(data.message) ? data.message : undefined;
79
+ const content = message?.content;
80
+ if (!Array.isArray(content))
81
+ continue;
82
+ for (const block of content) {
83
+ if (!isRecord(block) || block.type !== 'tool-call')
84
+ continue;
85
+ collectInputPaths(block.arguments, workspaceRoot, paths);
86
+ }
87
+ }
88
+ return paths;
89
+ }
90
+ function collectInputPaths(value, workspaceRoot, paths) {
91
+ if (typeof value !== 'string') {
92
+ if (Array.isArray(value)) {
93
+ for (const item of value)
94
+ collectInputPaths(item, workspaceRoot, paths);
95
+ return;
96
+ }
97
+ if (isRecord(value)) {
98
+ for (const [key, nested] of Object.entries(value)) {
99
+ if (typeof nested === 'string' && PATH_KEYS.has(key))
100
+ addPath(nested, workspaceRoot, paths);
101
+ else
102
+ collectInputPaths(nested, workspaceRoot, paths);
103
+ }
104
+ }
105
+ return;
106
+ }
107
+ const trimmed = value.trim();
108
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
109
+ for (const line of trimmed.split(/\r?\n/u)) {
110
+ const match = /^\*\*\* (?:Update|Add|Delete) File:\s+(.+)$/u.exec(line)
111
+ ?? /^\*\*\* Move to:\s+(.+)$/u.exec(line);
112
+ if (match?.[1] !== undefined)
113
+ addPath(match[1], workspaceRoot, paths);
114
+ }
115
+ return;
116
+ }
117
+ try {
118
+ collectInputPaths(JSON.parse(trimmed), workspaceRoot, paths);
119
+ }
120
+ catch {
121
+ // 工具参数不是 JSON 时只使用 apply_patch 文本格式。
122
+ }
123
+ }
124
+ function addPath(value, workspaceRoot, paths) {
125
+ const trimmed = value.trim().replace(/^['"]+|['"]+$/gu, '');
126
+ if (trimmed === '' || trimmed === '.' || trimmed === './')
127
+ return;
128
+ const candidate = isAbsolute(trimmed) ? normalize(trimmed) : resolve(workspaceRoot, trimmed);
129
+ const relativePath = relative(workspaceRoot, candidate).replaceAll('\\', '/');
130
+ if (relativePath === '' || relativePath.startsWith('../') || isAbsolute(relativePath))
131
+ return;
132
+ paths.add(relativePath.replace(/^\.\//u, ''));
133
+ }
134
+ function findJsonlReference(value, depth = 0) {
135
+ if (depth > 5 || !isRecord(value))
136
+ return undefined;
137
+ for (const key of ['rawStoreRef', 'jsonlPath', 'sessionPath', 'filePath']) {
138
+ const candidate = value[key];
139
+ if (typeof candidate === 'string' && /\.jsonl(?:\.zstd)?$/u.test(candidate.trim()))
140
+ return candidate.trim();
141
+ }
142
+ for (const nested of Object.values(value)) {
143
+ const result = findJsonlReference(nested, depth + 1);
144
+ if (result !== undefined)
145
+ return result;
146
+ }
147
+ return undefined;
148
+ }
149
+ function sessionIdentity(value) {
150
+ if (!isRecord(value))
151
+ return undefined;
152
+ for (const key of ['sessionId', 'id']) {
153
+ const candidate = value[key];
154
+ if (typeof candidate === 'string' && candidate.trim() !== '')
155
+ return candidate.trim();
156
+ }
157
+ const header = value.header;
158
+ return isRecord(header) && typeof header.id === 'string' ? header.id.trim() : undefined;
159
+ }
160
+ function isRecord(value) {
161
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
162
+ }
163
+ //# sourceMappingURL=session-changes.js.map
@@ -60,6 +60,7 @@ export const CodingNsSettingsSchema = z.object({
60
60
  hiddenWorkspaceIds: z.array(z.string().min(1).max(512)).default(DEFAULT_CODINGNS_SETTINGS.workspaceSessionEnhancement.hiddenWorkspaceIds),
61
61
  showSubscriptionUsage: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.workspaceSessionEnhancement.showSubscriptionUsage),
62
62
  showQuickPhrases: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.workspaceSessionEnhancement.showQuickPhrases),
63
+ rememberConversationRightbarRatio: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.workspaceSessionEnhancement.rememberConversationRightbarRatio),
63
64
  quickPhrases: z.array(z.object({
64
65
  id: z.string().min(1).max(128),
65
66
  text: z.string().min(1).max(4000),
@@ -67,6 +68,11 @@ export const CodingNsSettingsSchema = z.object({
67
68
  // 缺少该字段说明是旧配置;Client 首次加载时会补齐内置快捷会话。
68
69
  quickPhrasesSeeded: z.boolean().default(false),
69
70
  }).default(DEFAULT_CODINGNS_SETTINGS.workspaceSessionEnhancement),
71
+ fileManagement: z.object({
72
+ menuEnhancement: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.fileManagement.menuEnhancement),
73
+ fileEditor: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.fileManagement.fileEditor),
74
+ sessionChangedFiles: z.boolean().default(DEFAULT_CODINGNS_SETTINGS.fileManagement.sessionChangedFiles),
75
+ }).default(DEFAULT_CODINGNS_SETTINGS.fileManagement),
70
76
  });
71
77
  /**
72
78
  * DSH 0.1.7 只会把 `volatile` 配置投影成可编辑 ConfigForm。
@@ -20,7 +20,7 @@ export { createHttpDshH5ControlApi, startDshH5BrowserBootstrap } from './client/
20
20
  export type { DshH5BrowserBootstrapOptions, DshH5BrowserBootstrapResult, DshH5BrowserControlApi } from './client/dsh-h5-bootstrap.js';
21
21
  export { CODINGNS_RPC_CHANNEL } from './shared/contracts/transport.js';
22
22
  export { CODINGNS4DSH_DEBUG_ENV, debugInfo, debugWarn, resolveCodingNsDebugEnabled, } from './shared/debug.js';
23
- export { CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, CODINGNS_LAN_ACCESS_DSH_FIELD, CODINGNS_MODULES_FIELD, CODINGNS_SETTINGS_NAMESPACE, DEFAULT_CODINGNS_CONTROL_BASE_URL, DEFAULT_CODINGNS_CONTROL_BASE_URLS, DEFAULT_CODINGNS_SETTINGS, enabledFeatureNames, isFeatureEnabled, } from './shared/contracts/config.js';
23
+ export { CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, CODINGNS_LAN_ACCESS_DSH_FIELD, CODINGNS_FILE_MANAGEMENT_FIELD, CODINGNS_MODULES_FIELD, CODINGNS_SETTINGS_NAMESPACE, DEFAULT_CODINGNS_CONTROL_BASE_URL, DEFAULT_CODINGNS_CONTROL_BASE_URLS, DEFAULT_CODINGNS_SETTINGS, DEFAULT_FILE_MANAGEMENT_SETTINGS, enabledFeatureNames, isFeatureEnabled, } from './shared/contracts/config.js';
24
24
  export type * from './shared/index.js';
25
25
  export * from './dsh-capabilities/index.js';
26
26
  export { dispatchCodingNsRpc, type CodingNsRpcContext, type CodingNsRpcDispatchResult } from './dsh-capabilities/host/connection-rpc-adapter.js';
@@ -18,7 +18,7 @@ export { RemoteDshWebContext } from './client/remote-web-context.js';
18
18
  export { createHttpDshH5ControlApi, startDshH5BrowserBootstrap } from './client/dsh-h5-bootstrap.js';
19
19
  export { CODINGNS_RPC_CHANNEL } from './shared/contracts/transport.js';
20
20
  export { CODINGNS4DSH_DEBUG_ENV, debugInfo, debugWarn, resolveCodingNsDebugEnabled, } from './shared/debug.js';
21
- export { CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, CODINGNS_LAN_ACCESS_DSH_FIELD, CODINGNS_MODULES_FIELD, CODINGNS_SETTINGS_NAMESPACE, DEFAULT_CODINGNS_CONTROL_BASE_URL, DEFAULT_CODINGNS_CONTROL_BASE_URLS, DEFAULT_CODINGNS_SETTINGS, enabledFeatureNames, isFeatureEnabled, } from './shared/contracts/config.js';
21
+ export { CODINGNS_CONTROL_BASE_URL_FIELD, CODINGNS_CONTROL_BASE_URLS_FIELD, CODINGNS_LAN_ACCESS_DSH_FIELD, CODINGNS_FILE_MANAGEMENT_FIELD, CODINGNS_MODULES_FIELD, CODINGNS_SETTINGS_NAMESPACE, DEFAULT_CODINGNS_CONTROL_BASE_URL, DEFAULT_CODINGNS_CONTROL_BASE_URLS, DEFAULT_CODINGNS_SETTINGS, DEFAULT_FILE_MANAGEMENT_SETTINGS, enabledFeatureNames, isFeatureEnabled, } from './shared/contracts/config.js';
22
22
  export * from './dsh-capabilities/index.js';
23
23
  export { dispatchCodingNsRpc } from './dsh-capabilities/host/connection-rpc-adapter.js';
24
24
  export { FeatureRegistry, FeatureRegistryError, FeatureResourceScopeImpl, } from './features/registry.js';