dsh-monkeyai-sessions 0.1.1 → 0.1.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.
package/README.md CHANGED
@@ -61,7 +61,7 @@ From a checkout:
61
61
  npm install
62
62
  npm run build
63
63
  npm pack
64
- dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.1.tgz
64
+ dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.2.tgz
65
65
  ```
66
66
 
67
67
  Verified against a real DSH `0.2.2-rc.1` web profile and a live MonkeyAI runtime: authorization, full history loading, prompt delivery, model switching, and turn cancellation all work end to end.
package/README.zh.md CHANGED
@@ -28,7 +28,7 @@ https://artenx.github.io/dsh-monkeyai-sessions/index.json
28
28
  npm install
29
29
  npm run build
30
30
  npm pack
31
- dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.1.tgz
31
+ dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.2.tgz
32
32
  ```
33
33
 
34
34
  已在真实的 DSH `0.2.2-rc.1` web profile 与在线 MonkeyAI runtime 上端到端验证:授权、完整历史加载、指令下发、模型切换、回合取消均正常工作。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-monkeyai-sessions",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "在 DeepSeek Harness 中读取和继续 MonkeyAI 会话:查看完整对话历史、下发新指令,无缝接续之前在 MonkeyAI 客户端上的工作",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -390,6 +390,45 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
390
390
  return this.exchangeAuthorization(auth, callbackUrl)
391
391
  }
392
392
 
393
+ // The runtime requires an expectedRevision on every configuration write and
394
+ // rejects a stale one with configuration_conflict. The MonkeyAI client (or
395
+ // another panel) can advance the revision at any time, so read the current
396
+ // value from the conflict payload — or from the HTTP session state when we
397
+ // have none — and retry instead of failing the switch.
398
+ async setSessionModel({ acp, sessionId, model, httpBase, ticket, revision, signal, onRevision }) {
399
+ let expected = revision
400
+ for (let attempt = 0; attempt < 3; attempt += 1) {
401
+ if (!Number.isSafeInteger(expected)) {
402
+ expected = await readSessionConfigurationRevision({ httpBase, ticket, sessionId, signal })
403
+ }
404
+ if (!Number.isSafeInteger(expected)) throw new Error('无法读取会话配置版本,暂时不能切换模型')
405
+ try {
406
+ const result = await acp.request('_monkeyai/session/model/set', {
407
+ sessionId,
408
+ model,
409
+ _meta: { expectedRevision: expected },
410
+ }, { signal, timeoutMs: 120_000 })
411
+ const next = result?._meta?.configurationRevision
412
+ if (Number.isSafeInteger(next)) onRevision(next)
413
+ return typeof result?.model === 'string' ? result.model : model
414
+ } catch (error) {
415
+ if (signal?.aborted) throw error
416
+ const current = error?.data?.configurationRevision
417
+ if (error?.data?.reason === 'configuration_conflict' && Number.isSafeInteger(current) && current !== expected) {
418
+ expected = current
419
+ onRevision(current)
420
+ continue
421
+ }
422
+ if (error?.data?.reason === 'configuration_version_required') {
423
+ expected = undefined
424
+ continue
425
+ }
426
+ throw error
427
+ }
428
+ }
429
+ throw new Error('会话配置持续被其他客户端修改,请重试切换模型')
430
+ }
431
+
393
432
  async exchangeAuthorization(auth, callbackUrl) {
394
433
  const callback = parseAuthorizationCallback(callbackUrl, auth)
395
434
  const tokens = await requestJson(auth.serverOrigin, '/oauth/token', {
@@ -977,14 +1016,16 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
977
1016
 
978
1017
  if (command.type === 'switch_model') {
979
1018
  try {
980
- const result = await currentAcp.request('_monkeyai/session/model/set', {
1019
+ const model = await this.setSessionModel({
1020
+ acp: currentAcp,
981
1021
  sessionId: selectedSession,
982
1022
  model: command.model,
983
- ...(httpConfigurationRevision === undefined ? {} : { _meta: { expectedRevision: httpConfigurationRevision } }),
984
- }, { signal, timeoutMs: 120_000 })
985
- const nextRevision = result?._meta?.configurationRevision
986
- if (Number.isSafeInteger(nextRevision)) httpConfigurationRevision = nextRevision
987
- const model = typeof result?.model === 'string' ? result.model : command.model
1023
+ httpBase: ticket.httpBase,
1024
+ ticket: ticketValue,
1025
+ revision: httpConfigurationRevision,
1026
+ signal,
1027
+ onRevision: value => { httpConfigurationRevision = value },
1028
+ })
988
1029
  currentLoadedModel = model
989
1030
  emit({ type: 'model_switched', sessionId: selectedSession, data: { model } })
990
1031
  } catch (switchError) {
@@ -1223,6 +1264,16 @@ async function readHistoryBodyText(response, maxBytes = HISTORY_PAGE_MAX_BYTES)
1223
1264
  return new TextDecoder().decode(joined)
1224
1265
  }
1225
1266
 
1267
+ // Reads the session's current configuration revision over the HTTP read
1268
+ // channel. Configuration writes must carry it, and the value changes whenever
1269
+ // any client edits the session.
1270
+ async function readSessionConfigurationRevision({ httpBase, ticket, sessionId, signal }) {
1271
+ const timeout = AbortSignal.any([AbortSignal.timeout(20_000), ...(signal ? [signal] : [])])
1272
+ const result = await readSessionHistoryPage({ httpBase, ticket, sessionId, limit: 1, signal: timeout })
1273
+ const revision = result.session?.configurationRevision
1274
+ return Number.isSafeInteger(revision) ? revision : undefined
1275
+ }
1276
+
1226
1277
  async function readSessionHistoryPage({ httpBase, ticket, sessionId, cursor, direction, limit = 50, signal }) {
1227
1278
  const params = { sessionId, limit, ...(cursor ? { cursor, direction } : {}) }
1228
1279
  const response = await fetch(`${httpBase.endsWith('/') ? httpBase : `${httpBase}/`}read/session`, {