dsh-as-aistudio 0.2.2 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-as-aistudio",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
4
4
  "description": "Google-AI-Studio-style turn controls for DeepSeek Harness, in one install: edit a prompt and re-run it, re-run a reply in place, delete a turn from the model context, and render sent prompts as Markdown. Self-contained: the plugin list shows this one row; the four components are dependencies mounted by it. Each still installs alone and works alone.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -41,13 +41,15 @@
41
41
  "vendor": "node tools/vendor.mjs",
42
42
  "build": "node tools/vendor.mjs && node tools/build.mjs",
43
43
  "test": "node --test test/*.test.js",
44
- "check": "npm run build && npm test",
44
+ "check": "npm run verify:manifest && npm run build && npm test",
45
+ "bump": "node tools/bump.mjs",
45
46
  "verify:vendor": "node tools/vendor.mjs --check",
46
- "verify:build": "node tools/build.mjs --check"
47
+ "verify:build": "node tools/build.mjs --check",
48
+ "verify:manifest": "node tools/verify-manifest.mjs"
47
49
  },
48
50
  "dependencies": {
49
- "dsh-delete-turn": "^0.1.7",
50
- "dsh-edit-turn": "^0.2.25",
51
+ "dsh-delete-turn": "^0.1.8",
52
+ "dsh-edit-turn": "^0.2.16",
51
53
  "dsh-markdown-bubble": "^0.1.4",
52
54
  "dsh-rerun-turn": "^0.1.24"
53
55
  },
@@ -71,7 +73,7 @@
71
73
  "compatibility": {
72
74
  "dshReleases": {
73
75
  "0.1.6-alpha.2": "compatible",
74
- "0.2.2-rc.1": "compatible"
76
+ "0.2.0-rc.2": "compatible"
75
77
  }
76
78
  }
77
79
  }
package/src/client.js CHANGED
@@ -32,7 +32,7 @@ window.__ModuleLoader__.load({
32
32
  const RERUN_PREFIX = '/dsh-rerun-turn'
33
33
 
34
34
  /** Keep in sync with package.json and lib/index.js. */
35
- const PLUGIN_VERSION = '0.2.15'
35
+ const PLUGIN_VERSION = '0.2.16'
36
36
 
37
37
  // --- copy -----------------------------------------------------------------
38
38
 
@@ -3131,6 +3131,8 @@ window.__ModuleLoader__.load({
3131
3131
  this.animateOnce = false
3132
3132
  this.view = Object.freeze({
3133
3133
  hidden: new Map(),
3134
+ // replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
3135
+ hiddenVia: new Map(),
3134
3136
  surface: new Set(),
3135
3137
  replyTurns: new Set(),
3136
3138
  edits: new Map(),
@@ -3195,6 +3197,7 @@ window.__ModuleLoader__.load({
3195
3197
  for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
3196
3198
  if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : 'message')
3197
3199
  }
3200
+ const hiddenVia = hiddenViaOf(data.hidden)
3198
3201
  const surface = new Set()
3199
3202
  for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
3200
3203
  const replyTurns = new Set()
@@ -3208,7 +3211,7 @@ window.__ModuleLoader__.load({
3208
3211
  if (typeof turn === 'number') markerTurns.add(turn)
3209
3212
  }
3210
3213
  const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
3211
- this.publish({ hidden, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
3214
+ this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
3212
3215
  })
3213
3216
  .catch(() => {
3214
3217
  this.publish({ loadError: true })
@@ -3260,8 +3263,10 @@ window.__ModuleLoader__.load({
3260
3263
  for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
3261
3264
  if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : target.mode)
3262
3265
  }
3266
+ const hiddenVia = new Map(this.view.hiddenVia)
3267
+ for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
3263
3268
  this.animateOnce = true
3264
- this.publish({ pending: false, dialog: null, hidden, loaded: true, loadError: false })
3269
+ this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
3265
3270
  } catch {
3266
3271
  this.publish({ pending: false, failure: 'generic' })
3267
3272
  }
@@ -3419,9 +3424,27 @@ window.__ModuleLoader__.load({
3419
3424
  return seq === target.seq ? target : { ...target, seq }
3420
3425
  }
3421
3426
 
3422
- function isRowHidden(hidden, seqs) {
3427
+ // Index a deletion by the REPLACEMENT it landed, not only by the seq it
3428
+ // shadowed. A deleted message leaves a carrier standing in its row, and the
3429
+ // transcript row names the live node (the carrier), so a row checked against
3430
+ // 'hidden' alone never matches: the deletion succeeds, the model context
3431
+ // loses the message, and the row is simply never hidden - an empty bubble
3432
+ // left behind. The link is already on every hidden entry ('replacement'), so
3433
+ // it is indexed here rather than derived from 'edits': that chain
3434
+ // deliberately excludes this plugin's own carriers (they rewrite context,
3435
+ // not content) and so cannot bridge the gap.
3436
+ function hiddenViaOf(items) {
3437
+ const via = new Map()
3438
+ for (const item of Array.isArray(items) ? items : []) {
3439
+ if (item && typeof item.seq === 'number' && typeof item.replacement === 'number') via.set(item.replacement, item.seq)
3440
+ }
3441
+ return via
3442
+ }
3443
+
3444
+ function isRowHidden(hidden, hiddenVia, seqs) {
3423
3445
  for (const seq of seqs) {
3424
3446
  if (hidden.has(seq)) return true
3447
+ if (hiddenVia !== undefined && hiddenVia.has(seq)) return true
3425
3448
  }
3426
3449
  return false
3427
3450
  }
@@ -3684,13 +3707,13 @@ window.__ModuleLoader__.load({
3684
3707
  const anchor = anchorKey === undefined ? undefined : snapshot.nodes.get(anchorKey)
3685
3708
  if (anchor !== undefined) {
3686
3709
  const seqs = expandSeqs(seqsFor(anchor), view.edits)
3687
- setRowHidden(row, isRowHidden(view.hidden, seqs), animate)
3710
+ setRowHidden(row, isRowHidden(view.hidden, view.hiddenVia, seqs), animate)
3688
3711
  }
3689
3712
  continue
3690
3713
  }
3691
3714
  const seqs = expandSeqs(seqsFor(node), view.edits)
3692
3715
  for (const seq of seqs) if (typeof seq === 'number' && seq > maxSeq) maxSeq = seq
3693
- const hidden = isRowHidden(view.hidden, seqs)
3716
+ const hidden = isRowHidden(view.hidden, view.hiddenVia, seqs)
3694
3717
  setRowHidden(row, hidden, animate)
3695
3718
  const target = hidden ? null : liveTarget(targetFor(node), view.edits)
3696
3719
  const covered = target !== null || slotCoversRow(node)
@@ -4795,7 +4818,7 @@ window.__ModuleLoader__.load({
4795
4818
  const NS = 'dsh-as-aistudio'
4796
4819
 
4797
4820
  /** Keep in sync with package.json and src/components.js. */
4798
- const PLUGIN_VERSION = '0.2.2'
4821
+ const PLUGIN_VERSION = '0.2.5'
4799
4822
 
4800
4823
  /** Presence marker the live verifier reads. */
4801
4824
  const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
package/src/components.js CHANGED
@@ -19,7 +19,7 @@
19
19
  export const STUDIO_ID = 'dsh-as-aistudio'
20
20
 
21
21
  /** Keep in sync with package.json and src/client.js. */
22
- export const STUDIO_VERSION = '0.2.2'
22
+ export const STUDIO_VERSION = '0.2.5'
23
23
 
24
24
  /**
25
25
  * The AI Studio action strip this studio composes, in the order the user meets
package/src/studio.js CHANGED
@@ -49,7 +49,7 @@ window.__ModuleLoader__.load({
49
49
  const NS = 'dsh-as-aistudio'
50
50
 
51
51
  /** Keep in sync with package.json and src/components.js. */
52
- const PLUGIN_VERSION = '0.2.2'
52
+ const PLUGIN_VERSION = '0.2.5'
53
53
 
54
54
  /** Presence marker the live verifier reads. */
55
55
  const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
@@ -1,6 +1,6 @@
1
1
  // dsh-delete-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
2
2
  //
3
- // Source: ../dsh-delete-turn/src/client.js (v0.1.7, sha256 516518d8b6b8e5e4)
3
+ // Source: ../dsh-delete-turn/src/client.js (v0.1.8, sha256 09de24fd97ecd4ec)
4
4
  // Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
5
5
  //
6
6
  // The component registers its factory with the host module loader; inside this
@@ -154,6 +154,8 @@ export default (require) => {
154
154
  this.animateOnce = false
155
155
  this.view = Object.freeze({
156
156
  hidden: new Map(),
157
+ // replacement seq -> the seq it was recorded as shadowing. See hiddenViaOf.
158
+ hiddenVia: new Map(),
157
159
  surface: new Set(),
158
160
  replyTurns: new Set(),
159
161
  edits: new Map(),
@@ -218,6 +220,7 @@ export default (require) => {
218
220
  for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
219
221
  if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : 'message')
220
222
  }
223
+ const hiddenVia = hiddenViaOf(data.hidden)
221
224
  const surface = new Set()
222
225
  for (const seq of Array.isArray(data.surface) ? data.surface : []) surface.add(seq)
223
226
  const replyTurns = new Set()
@@ -231,7 +234,7 @@ export default (require) => {
231
234
  if (typeof turn === 'number') markerTurns.add(turn)
232
235
  }
233
236
  const surfaceThrough = typeof data.lastSeq === 'number' ? data.lastSeq : -1
234
- this.publish({ hidden, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
237
+ this.publish({ hidden, hiddenVia, surface, replyTurns, edits, markerTurns, surfaceReady: true, surfaceThrough, loaded: true, loadError: false })
235
238
  })
236
239
  .catch(() => {
237
240
  this.publish({ loadError: true })
@@ -283,8 +286,10 @@ export default (require) => {
283
286
  for (const item of Array.isArray(data.hidden) ? data.hidden : []) {
284
287
  if (item && typeof item.seq === 'number') hidden.set(item.seq, typeof item.mode === 'string' ? item.mode : target.mode)
285
288
  }
289
+ const hiddenVia = new Map(this.view.hiddenVia)
290
+ for (const [via, seq] of hiddenViaOf(data.hidden)) hiddenVia.set(via, seq)
286
291
  this.animateOnce = true
287
- this.publish({ pending: false, dialog: null, hidden, loaded: true, loadError: false })
292
+ this.publish({ pending: false, dialog: null, hidden, hiddenVia, loaded: true, loadError: false })
288
293
  } catch {
289
294
  this.publish({ pending: false, failure: 'generic' })
290
295
  }
@@ -442,9 +447,27 @@ export default (require) => {
442
447
  return seq === target.seq ? target : { ...target, seq }
443
448
  }
444
449
 
445
- function isRowHidden(hidden, seqs) {
450
+ // Index a deletion by the REPLACEMENT it landed, not only by the seq it
451
+ // shadowed. A deleted message leaves a carrier standing in its row, and the
452
+ // transcript row names the live node (the carrier), so a row checked against
453
+ // 'hidden' alone never matches: the deletion succeeds, the model context
454
+ // loses the message, and the row is simply never hidden - an empty bubble
455
+ // left behind. The link is already on every hidden entry ('replacement'), so
456
+ // it is indexed here rather than derived from 'edits': that chain
457
+ // deliberately excludes this plugin's own carriers (they rewrite context,
458
+ // not content) and so cannot bridge the gap.
459
+ function hiddenViaOf(items) {
460
+ const via = new Map()
461
+ for (const item of Array.isArray(items) ? items : []) {
462
+ if (item && typeof item.seq === 'number' && typeof item.replacement === 'number') via.set(item.replacement, item.seq)
463
+ }
464
+ return via
465
+ }
466
+
467
+ function isRowHidden(hidden, hiddenVia, seqs) {
446
468
  for (const seq of seqs) {
447
469
  if (hidden.has(seq)) return true
470
+ if (hiddenVia !== undefined && hiddenVia.has(seq)) return true
448
471
  }
449
472
  return false
450
473
  }
@@ -707,13 +730,13 @@ export default (require) => {
707
730
  const anchor = anchorKey === undefined ? undefined : snapshot.nodes.get(anchorKey)
708
731
  if (anchor !== undefined) {
709
732
  const seqs = expandSeqs(seqsFor(anchor), view.edits)
710
- setRowHidden(row, isRowHidden(view.hidden, seqs), animate)
733
+ setRowHidden(row, isRowHidden(view.hidden, view.hiddenVia, seqs), animate)
711
734
  }
712
735
  continue
713
736
  }
714
737
  const seqs = expandSeqs(seqsFor(node), view.edits)
715
738
  for (const seq of seqs) if (typeof seq === 'number' && seq > maxSeq) maxSeq = seq
716
- const hidden = isRowHidden(view.hidden, seqs)
739
+ const hidden = isRowHidden(view.hidden, view.hiddenVia, seqs)
717
740
  setRowHidden(row, hidden, animate)
718
741
  const target = hidden ? null : liveTarget(targetFor(node), view.edits)
719
742
  const covered = target !== null || slotCoversRow(node)
@@ -1,6 +1,6 @@
1
1
  // dsh-edit-turn browser half, VENDORED by tools/vendor.mjs - DO NOT EDIT.
2
2
  //
3
- // Source: ../dsh-edit-turn/lib/client.js (v0.2.15, sha256 805626f0a2ff50d3)
3
+ // Source: ../dsh-edit-turn/lib/client.js (v0.2.16, sha256 3ccc92a337c0541e)
4
4
  // Regenerate with `npm run vendor`; test/vendor-sync.test.js fails when this is stale.
5
5
  //
6
6
  // The component registers its factory with the host module loader; inside this
@@ -24,7 +24,7 @@ export default (require) => {
24
24
  const RERUN_PREFIX = '/dsh-rerun-turn'
25
25
 
26
26
  /** Keep in sync with package.json and lib/index.js. */
27
- const PLUGIN_VERSION = '0.2.15'
27
+ const PLUGIN_VERSION = '0.2.16'
28
28
 
29
29
  // --- copy -----------------------------------------------------------------
30
30
 
package/tools/bump.mjs ADDED
@@ -0,0 +1,82 @@
1
+ // Bump this package's version in the three places that legitimately carry it -
2
+ // and assert that NOTHING ELSE in those files changed.
3
+ //
4
+ // The rule this tool exists to enforce. Do not bump with a blind replace:
5
+ //
6
+ // s.split('0.2.1').join('0.2.2') // WRONG
7
+ //
8
+ // That also rewrites every other string containing the old version. '^0.2.15'
9
+ // contains '0.2.1', so a dependency range silently became '^0.2.25' - a version
10
+ // that has never existed - and the package shipped unable to install at all.
11
+ // It also rewrote a DSH release key ('0.2.0-rc.1' -> '0.2.1-rc.1'), which has
12
+ // nothing to do with this package's version.
13
+ //
14
+ // node tools/bump.mjs 0.2.3 # write
15
+ // node tools/bump.mjs --check # verify the three sites agree, write nothing
16
+ import { readFileSync, writeFileSync } from 'node:fs'
17
+ import { dirname, join } from 'node:path'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
21
+
22
+ /** file, the one regex that matches its version literal, and a human name. */
23
+ const SITES = [
24
+ { file: 'package.json', pattern: /(^\s*"version":\s*")([^"]+)(")/m, label: 'package.json version' },
25
+ { file: 'src/components.js', pattern: /(export const STUDIO_VERSION = ')([^']+)(')/, label: 'STUDIO_VERSION' },
26
+ { file: 'src/studio.js', pattern: /(const PLUGIN_VERSION = ')([^']+)(')/, label: 'PLUGIN_VERSION' },
27
+ ]
28
+
29
+ function currentOf(site) {
30
+ const text = readFileSync(join(root, site.file), 'utf8')
31
+ const match = site.pattern.exec(text)
32
+ if (match === null) throw new Error(site.label + ': the version literal is not in ' + site.file)
33
+ return match[2]
34
+ }
35
+
36
+ const versions = SITES.map((site) => ({ ...site, current: currentOf(site) }))
37
+ const distinct = [...new Set(versions.map((v) => v.current))]
38
+
39
+ if (process.argv.includes('--check')) {
40
+ if (distinct.length !== 1) {
41
+ console.error('the three version sites disagree: ' + versions.map((v) => v.label + '=' + v.current).join(', '))
42
+ process.exit(1)
43
+ }
44
+ console.log('the version is one number in three places: ' + distinct[0])
45
+ process.exit(0)
46
+ }
47
+
48
+ const next = process.argv[2]
49
+ if (!/^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(next || '')) {
50
+ console.error('usage: node tools/bump.mjs <semver> (for example 0.2.3)')
51
+ process.exit(2)
52
+ }
53
+ if (distinct.length !== 1) {
54
+ console.error('refusing to bump: the three sites already disagree (' + distinct.join(', ') + '); fix them first')
55
+ process.exit(2)
56
+ }
57
+ const from = distinct[0]
58
+ if (from === next) {
59
+ console.error('already at ' + next)
60
+ process.exit(2)
61
+ }
62
+
63
+ for (const site of versions) {
64
+ const path = join(root, site.file)
65
+ const before = readFileSync(path, 'utf8')
66
+ const after = before.replace(site.pattern, (_m, head, _old, tail) => head + next + tail)
67
+ if (before === after) throw new Error(site.label + ': nothing changed in ' + site.file)
68
+ // The whole point: exactly one line may differ, and it must be the version line.
69
+ const beforeLines = before.split('\n')
70
+ const afterLines = after.split('\n')
71
+ if (beforeLines.length !== afterLines.length) throw new Error(site.file + ': the line count changed')
72
+ const changed = beforeLines.map((line, index) => (line === afterLines[index] ? null : index)).filter((index) => index !== null)
73
+ if (changed.length !== 1) {
74
+ throw new Error(site.file + ': expected exactly 1 changed line, got ' + changed.length + '; refusing to write')
75
+ }
76
+ writeFileSync(path, after)
77
+ console.log(' ' + site.file + ':' + (changed[0] + 1) + ' ' + site.label + ' ' + from + ' -> ' + next)
78
+ }
79
+
80
+ console.log('')
81
+ console.log('bumped ' + from + ' -> ' + next + ' in ' + versions.length + ' places, nothing else touched.')
82
+ console.log('Now: node tools/verify-manifest.mjs, npm run build, npm test.')
@@ -0,0 +1,77 @@
1
+ // Gate the manifest against the registry before publishing.
2
+ //
3
+ // This is the check whose absence shipped a broken release: dsh-as-aistudio@0.2.2
4
+ // declared 'dsh-edit-turn': '^0.2.25'. No such version exists (the latest is
5
+ // 0.2.15), so `pnpm add dsh-as-aistudio@0.2.2` failed with
6
+ // ERR_PNPM_NO_MATCHING_VERSION and the package could not be installed at all -
7
+ // while every test that exercised the bundle inside it passed, because unpacking
8
+ // a tarball never resolves its dependencies.
9
+ //
10
+ // node tools/verify-manifest.mjs # exit 1 on any unresolvable range
11
+ import { execFileSync } from 'node:child_process'
12
+ import { readFileSync } from 'node:fs'
13
+ import { dirname, join } from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+
16
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
17
+ const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
18
+ let failed = 0
19
+
20
+ // 1. every dependency range must resolve to a published version
21
+ const deps = Object.entries(manifest.dependencies || {})
22
+ if (deps.length === 0) {
23
+ console.error('no dependencies declared; that is not the shape of this package')
24
+ failed += 1
25
+ }
26
+ for (const [name, range] of deps) {
27
+ let resolved = ''
28
+ try {
29
+ resolved = execFileSync('npm', ['view', name + '@' + range, 'version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split('\n').pop().trim()
30
+ } catch (error) {
31
+ resolved = ''
32
+ }
33
+ if (resolved === '') {
34
+ console.error(' UNRESOLVABLE ' + name + '@' + range + ' <- no published version satisfies this')
35
+ failed += 1
36
+ } else {
37
+ console.log(' ok ' + name + '@' + range + ' -> ' + resolved)
38
+ }
39
+ }
40
+
41
+ // 2. the version must be one number in the three places that carry it
42
+ const sites = [
43
+ ['package.json', /(^\s*"version":\s*")([^"]+)(")/m],
44
+ ['src/components.js', /(export const STUDIO_VERSION = ')([^']+)(')/],
45
+ ['src/studio.js', /(const PLUGIN_VERSION = ')([^']+)(')/],
46
+ ]
47
+ const found = sites.map(([file, pattern]) => {
48
+ const match = pattern.exec(readFileSync(join(root, file), 'utf8'))
49
+ return { file, version: match === null ? null : match[2] }
50
+ })
51
+ const distinct = [...new Set(found.map((s) => s.version))]
52
+ if (distinct.length !== 1 || distinct[0] === null) {
53
+ console.error(' VERSION DRIFT ' + found.map((s) => s.file + '=' + s.version).join(', '))
54
+ failed += 1
55
+ } else {
56
+ console.log(' ok version ' + distinct[0] + ' in all three places')
57
+ }
58
+
59
+ // 3. DSH compatibility keys must look like DSH releases, not like this package
60
+ const releases = Object.keys(manifest.dsh?.compatibility?.dshReleases || {})
61
+ if (releases.length === 0) {
62
+ console.error(' NO DSH RELEASES declared in dsh.compatibility.dshReleases')
63
+ failed += 1
64
+ }
65
+ for (const release of releases) {
66
+ if (!/^0\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$/.test(release)) {
67
+ console.error(' BAD DSH KEY ' + release + ' does not look like a DSH release')
68
+ failed += 1
69
+ }
70
+ }
71
+
72
+ console.log('')
73
+ if (failed > 0) {
74
+ console.error(failed + ' manifest problem(s); do not publish')
75
+ process.exit(1)
76
+ }
77
+ console.log('manifest ok: ' + deps.length + ' dependency ranges resolve, version ' + distinct[0] + ' consistent')