dsh-as-aistudio 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/client.js +259 -12
- package/src/components.js +1 -1
- package/src/vendor/host/_platform.js +5 -8
- package/src/vendor/host/dsh-edit-turn.js +4 -71
- package/src/vendor/host/dsh-edit-turn.patch.js +1 -1
- package/src/vendor/host/dsh-rerun-turn.js +37 -5
- package/src/vendor/versions.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## 中文
|
|
6
6
|
|
|
7
|
+
**0.4.0** —— 少要一个平台包;四个组件跟进到最新。
|
|
8
|
+
|
|
9
|
+
- **不再要求 `dsh-tools`**:本包内置的宿主半部以前要 `@deepseek-ai/dsh-tools`,唯一原因是 `dsh-edit-turn` 向模型暴露了一个工具(`edit_turn_targets`)。那个工具已经删掉——编辑是靠 UI 面板做的,模型不需要知道这件事——于是整族插件都不再声明模型可见的工具,宿主半部的解析只需要 `dsh-session`。少一个平台包,就少一处跟宿主版本绑定的地方。
|
|
10
|
+
|
|
11
|
+
- **组件跟进**:`dsh-edit-turn` **0.5.6**、`dsh-rerun-turn` **0.2.6**;另两个不变(`dsh-delete-turn` 0.1.15、`dsh-markdown-bubble` 0.1.5)。
|
|
12
|
+
|
|
13
|
+
- `dsh-edit-turn` 0.5.5 把模型工具拿掉;0.5.6 给会话日志裁剪工具加了"先验后写"(那个工具在 `tools/` 下、不随 npm 包发布,本包也不含它,只是版本号跟着走)。
|
|
14
|
+
- `dsh-rerun-turn` 0.2.6 修 `sourceEventSeqs`:写入前摊平嵌套数组、去重、排序,并且**只接受真正的数字**——之前写出的形状(嵌套数组、缺项、倒序区间)会被宿主的校验器整条拒收,**整份会话日志都加载不了**。这是实测出来的:一个 29,497 事件的会话里有三条这样的记录,宿主三层校验全拒。
|
|
15
|
+
|
|
7
16
|
**0.3.0** —— 设置页搬到插件行自己的配置箭头;组件设置卡片直接复用组件自己的那一个。
|
|
8
17
|
|
|
9
18
|
- **设置页换了位置**:以前是「设置 → 插件 → AI Studio」里多一个标签页,和别的插件都不一样。现在注册到 `plugins.row.config`,键是 `dsh-as-aistudio#dsh-as-aistudio`(`<包名>#<行 id>` 的约定),于是**本包那一行**右侧多了配置箭头 —— 和这个 profile 里其它插件完全一致。点进去是整页:四个组件的实时状态,加下面的「组件设置」。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-as-aistudio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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, and the four components are vendored inside it - installing the studio installs nothing else.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/client.js
CHANGED
|
@@ -43,7 +43,7 @@ window.__ModuleLoader__.load({
|
|
|
43
43
|
const RERUN_PREFIX = '/dsh-rerun-turn'
|
|
44
44
|
|
|
45
45
|
/** Keep in sync with package.json and lib/index.js. */
|
|
46
|
-
const PLUGIN_VERSION = '0.5.
|
|
46
|
+
const PLUGIN_VERSION = '0.5.6'
|
|
47
47
|
|
|
48
48
|
// --- copy -----------------------------------------------------------------
|
|
49
49
|
|
|
@@ -58,6 +58,8 @@ window.__ModuleLoader__.load({
|
|
|
58
58
|
'copy': '复制',
|
|
59
59
|
'copied': '复制成功',
|
|
60
60
|
'action.copied': '复制成功',
|
|
61
|
+
'action.copy': '复制这条回答',
|
|
62
|
+
'action.tail-actions': '这一轮的动作',
|
|
61
63
|
'editor.title': '编辑这条消息',
|
|
62
64
|
'editor.title-reply': '编辑这条回答',
|
|
63
65
|
'editor.placeholder': '改成你希望自己当时说的内容。保存后就地替换这条消息,后面的对话保持不变。',
|
|
@@ -145,6 +147,8 @@ window.__ModuleLoader__.load({
|
|
|
145
147
|
'copy': 'Copy',
|
|
146
148
|
'copied': 'Copied',
|
|
147
149
|
'action.copied': 'Copied',
|
|
150
|
+
'action.copy': 'Copy this reply',
|
|
151
|
+
'action.tail-actions': 'Actions for this turn',
|
|
148
152
|
'editor.title': 'Edit this message',
|
|
149
153
|
'editor.title-reply': 'Edit this reply',
|
|
150
154
|
'editor.placeholder': 'Write what you want to have said. Saving replaces this message in place; the conversation after it stays as it is.',
|
|
@@ -422,6 +426,18 @@ window.__ModuleLoader__.load({
|
|
|
422
426
|
// variable resolves on the action bar (and everywhere above it); the
|
|
423
427
|
// fallback is the value it resolves to in the default theme.
|
|
424
428
|
'.dshet-action{width:28px;height:28px;padding:6px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#81858c);cursor:pointer;transition:background-color .12s,color .12s}',
|
|
429
|
+
// The fallback strip for a turn with no closing body.
|
|
430
|
+
//
|
|
431
|
+
// The gate is the host's OWN marker: TurnTailNodeView renders
|
|
432
|
+
// `data-actions-reveal` on the tail container exactly on the branch that
|
|
433
|
+
// also draws the official action row, and never on the branch that has
|
|
434
|
+
// none. So one rule hides this strip wherever the official one exists -
|
|
435
|
+
// the two can never be drawn together, and no observer is needed.
|
|
436
|
+
'.dshet-tail{display:inline-flex;align-items:center;gap:2px;margin-left:4px}',
|
|
437
|
+
'[data-turn-tail][data-actions-reveal] .dshet-tail{display:none}',
|
|
438
|
+
'.dshet-tail[data-reveal=always]{opacity:1}',
|
|
439
|
+
'@media (hover:hover){.dshet-tail[data-reveal=hover]{opacity:0;transition:opacity 80ms}[data-turn-tail]:hover .dshet-tail[data-reveal=hover],[data-turn-tail]:focus-within .dshet-tail[data-reveal=hover]{opacity:1}}',
|
|
440
|
+
'.dshet-tail-action[data-copied=1]{color:var(--dshet-accent,#4d6bfe)}',
|
|
425
441
|
'.dshet-action:hover:not(:disabled){background:var(--dshet-chip);box-shadow:inset 0 0 0 1px var(--dshet-line);color:var(--dsw-alias-label-primary,var(--dshet-ink))}',
|
|
426
442
|
'.dshet-action:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
|
|
427
443
|
'.dshet-action:disabled{cursor:default;opacity:.4}',
|
|
@@ -577,12 +593,18 @@ window.__ModuleLoader__.load({
|
|
|
577
593
|
].join('')
|
|
578
594
|
|
|
579
595
|
const TAG_ID = 'dsh-edit-turn/edit-turn.css'
|
|
580
|
-
if (typeof document !== 'undefined'
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
tag
|
|
585
|
-
|
|
596
|
+
if (typeof document !== 'undefined') {
|
|
597
|
+
// Reused, not skipped: an upgrade over a live page finds the tag the
|
|
598
|
+
// PREVIOUS build injected, and leaving it alone kept the new rules out of
|
|
599
|
+
// the page entirely.
|
|
600
|
+
let tag = document.querySelector('style[data-plugin-css=' + JSON.stringify(TAG_ID) + ']')
|
|
601
|
+
if (tag === null) {
|
|
602
|
+
tag = document.createElement('style')
|
|
603
|
+
tag.dataset.plugin = NS
|
|
604
|
+
tag.dataset.pluginCss = TAG_ID
|
|
605
|
+
document.head.appendChild(tag)
|
|
606
|
+
}
|
|
607
|
+
if (tag.textContent !== CSS) tag.textContent = CSS
|
|
586
608
|
}
|
|
587
609
|
|
|
588
610
|
// --- icon -----------------------------------------------------------------
|
|
@@ -3695,9 +3717,12 @@ window.__ModuleLoader__.load({
|
|
|
3695
3717
|
error.className = 'dshet-error'
|
|
3696
3718
|
error.setAttribute('role', 'status')
|
|
3697
3719
|
const detail = typeof view.failureDetail === 'string' && view.failureDetail !== '' ? view.failureDetail : null
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3720
|
+
// The host's own sentence is always shown: a code-only message leaves the
|
|
3721
|
+
// reader with nothing to act on, and it is what made a real 400 impossible
|
|
3722
|
+
// to diagnose.
|
|
3723
|
+
error.textContent = detail === null
|
|
3724
|
+
? errorText(t, failureCode)
|
|
3725
|
+
: `${errorText(t, failureCode)}(${detail.slice(0, 300)})`
|
|
3701
3726
|
box.appendChild(error)
|
|
3702
3727
|
}
|
|
3703
3728
|
|
|
@@ -4531,6 +4556,211 @@ window.__ModuleLoader__.load({
|
|
|
4531
4556
|
})
|
|
4532
4557
|
}
|
|
4533
4558
|
|
|
4559
|
+
// --- the fallback strip ---------------------------------------------------
|
|
4560
|
+
//
|
|
4561
|
+
// TurnTailNodeView, when a turn's model reply has no closing body
|
|
4562
|
+
// (`closing === null`), returns the turnTail slot alone and never renders
|
|
4563
|
+
// `conversation.chat.assistant-actions`. Every action on that row goes with
|
|
4564
|
+
// it - this plugin's pencil, and the platform's own copy, branch, feedback
|
|
4565
|
+
// and usage buttons too. Nothing a plugin registers inside a strip that is
|
|
4566
|
+
// not rendered can bring those back, so the fallback lives in the one slot
|
|
4567
|
+
// that branch DOES render.
|
|
4568
|
+
//
|
|
4569
|
+
// It is not a second bar for every turn: the host puts `data-actions-reveal`
|
|
4570
|
+
// on the tail container only on the branch that draws its own strip, and one
|
|
4571
|
+
// CSS rule hides this one wherever that attribute is present.
|
|
4572
|
+
|
|
4573
|
+
/**
|
|
4574
|
+
* The reply this turn's fallback strip acts on.
|
|
4575
|
+
*
|
|
4576
|
+
* A turn runs several steps and the strip belongs at the end of the turn,
|
|
4577
|
+
* so the LAST rewriteable reply of that turn wins - the same row the host
|
|
4578
|
+
* would have hung its own strip on.
|
|
4579
|
+
*
|
|
4580
|
+
* @param view - the controller snapshot (see readReplyView).
|
|
4581
|
+
* @param turn - the TurnLocation the slot hands over.
|
|
4582
|
+
* @returns the reply entry, or undefined when this turn has none.
|
|
4583
|
+
*/
|
|
4584
|
+
function replyForTurn(view, turn) {
|
|
4585
|
+
if (view === null || view === undefined || typeof turn !== 'number') return undefined
|
|
4586
|
+
const replies = view.replies
|
|
4587
|
+
if (replies === null || replies === undefined || typeof replies.values !== 'function') return undefined
|
|
4588
|
+
if (view.hidden === null || view.hidden === undefined || typeof view.hidden.has !== 'function') return undefined
|
|
4589
|
+
let best
|
|
4590
|
+
for (const reply of replies.values()) {
|
|
4591
|
+
if (reply === null || typeof reply !== 'object') continue
|
|
4592
|
+
if (typeof reply.seq !== 'number' || reply.turn !== turn) continue
|
|
4593
|
+
if (view.hidden.has(reply.seq)) continue
|
|
4594
|
+
if (best === undefined || reply.seq > best.seq) best = reply
|
|
4595
|
+
}
|
|
4596
|
+
return best
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
/**
|
|
4600
|
+
* What a copy press on the fallback strip writes.
|
|
4601
|
+
*
|
|
4602
|
+
* The model's own answer when the turn has one, and its reasoning when the
|
|
4603
|
+
* whole reply went into thinking - the case this strip exists for. Blocks
|
|
4604
|
+
* are joined the way they read, blank ones dropped so a trailing empty
|
|
4605
|
+
* block does not become a trailing newline.
|
|
4606
|
+
*
|
|
4607
|
+
* @param entry - one reply entry.
|
|
4608
|
+
* @returns the text to copy; empty when there is nothing readable.
|
|
4609
|
+
*/
|
|
4610
|
+
function replyCopyText(entry) {
|
|
4611
|
+
const blocks = entry !== null && entry !== undefined && Array.isArray(entry.blocks) ? entry.blocks : []
|
|
4612
|
+
const gather = (kind) =>
|
|
4613
|
+
blocks
|
|
4614
|
+
.filter((block) => block !== null && typeof block === 'object' && block.kind === kind)
|
|
4615
|
+
.map((block) => (typeof block.text === 'string' ? block.text : ''))
|
|
4616
|
+
.filter((text) => text.trim() !== '')
|
|
4617
|
+
.join('\n\n')
|
|
4618
|
+
const answer = gather('text')
|
|
4619
|
+
return answer !== '' ? answer : gather('reasoning')
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
function CopyIcon() {
|
|
4623
|
+
return jsx('svg', {
|
|
4624
|
+
width: 16,
|
|
4625
|
+
height: 16,
|
|
4626
|
+
viewBox: '0 0 16 16',
|
|
4627
|
+
fill: 'none',
|
|
4628
|
+
'aria-hidden': true,
|
|
4629
|
+
children: [
|
|
4630
|
+
jsx('rect', { x: 5.2, y: 5.2, width: 8, height: 8, rx: 1.6, stroke: 'currentColor', strokeWidth: 1.2 }, 0),
|
|
4631
|
+
jsx('path', { d: 'M10.8 5.2V3.6A1.6 1.6 0 0 0 9.2 2H3.6A1.6 1.6 0 0 0 2 3.6v5.6a1.6 1.6 0 0 0 1.6 1.6h1.6', stroke: 'currentColor', strokeWidth: 1.2, strokeLinecap: 'round' }, 1),
|
|
4632
|
+
],
|
|
4633
|
+
})
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
function CheckIcon() {
|
|
4637
|
+
return jsx('svg', {
|
|
4638
|
+
width: 16,
|
|
4639
|
+
height: 16,
|
|
4640
|
+
viewBox: '0 0 16 16',
|
|
4641
|
+
fill: 'none',
|
|
4642
|
+
'aria-hidden': true,
|
|
4643
|
+
children: jsx('path', { d: 'M3.2 8.4l3.2 3.2 6.4-6.4', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round' }, 0),
|
|
4644
|
+
})
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
/**
|
|
4648
|
+
* The fallback strip for a turn the host draws no action row for.
|
|
4649
|
+
*
|
|
4650
|
+
* @param props - the slot's owner props ({ turn, seq, openFile }) plus the
|
|
4651
|
+
* injected controller, the standard `useChat` and this plugin's `t`.
|
|
4652
|
+
*/
|
|
4653
|
+
function TailActionEntry(input) {
|
|
4654
|
+
const props = input !== null && typeof input === 'object' ? input : {}
|
|
4655
|
+
const turn = props.turn
|
|
4656
|
+
const controller = props.controller
|
|
4657
|
+
const t = typeof props.t === 'function' ? props.t : null
|
|
4658
|
+
const useChat = props.useChat
|
|
4659
|
+
const [view, setView] = react.useState(() => {
|
|
4660
|
+
try {
|
|
4661
|
+
return readReplyView(controller)
|
|
4662
|
+
} catch {
|
|
4663
|
+
return null
|
|
4664
|
+
}
|
|
4665
|
+
})
|
|
4666
|
+
const [copied, setCopied] = react.useState(false)
|
|
4667
|
+
react.useEffect(() => {
|
|
4668
|
+
let unsubscribe = null
|
|
4669
|
+
try {
|
|
4670
|
+
setView(readReplyView(controller))
|
|
4671
|
+
if (controller !== null && controller !== undefined && typeof controller.subscribe === 'function') {
|
|
4672
|
+
unsubscribe = controller.subscribe(() => setView(readReplyView(controller)))
|
|
4673
|
+
}
|
|
4674
|
+
} catch {
|
|
4675
|
+
setView(null)
|
|
4676
|
+
}
|
|
4677
|
+
return () => {
|
|
4678
|
+
if (typeof unsubscribe === 'function') unsubscribe()
|
|
4679
|
+
}
|
|
4680
|
+
}, [controller])
|
|
4681
|
+
// The host reveals the latest turn's row always and older ones on hover.
|
|
4682
|
+
// The same rule, from the same source it uses (the last turn in the
|
|
4683
|
+
// timeline), so the strip does not behave differently from the row it
|
|
4684
|
+
// stands in for.
|
|
4685
|
+
const lastTurn = typeof useChat === 'function'
|
|
4686
|
+
? useChat((state) => {
|
|
4687
|
+
if (state === null || typeof state !== 'object') return undefined
|
|
4688
|
+
const timeline = state.timeline
|
|
4689
|
+
const order = timeline === null || timeline === undefined ? null : timeline.turnOrder
|
|
4690
|
+
if (!Array.isArray(order) || order.length === 0) return undefined
|
|
4691
|
+
return order[order.length - 1]
|
|
4692
|
+
})
|
|
4693
|
+
: undefined
|
|
4694
|
+
const entry = replyForTurn(view, turn)
|
|
4695
|
+
// Nothing this plugin can act on: the strip stays out of the way rather
|
|
4696
|
+
// than drawing an empty row.
|
|
4697
|
+
if (entry === undefined) return null
|
|
4698
|
+
const label = t === null ? '' : t('action.edit-reply')
|
|
4699
|
+
const copyLabel = t === null ? '' : t(copied ? 'action.copied' : 'action.copy')
|
|
4700
|
+
const open = (event) => {
|
|
4701
|
+
event.preventDefault()
|
|
4702
|
+
event.stopPropagation()
|
|
4703
|
+
controller.open({
|
|
4704
|
+
anchor: event.currentTarget,
|
|
4705
|
+
seq: entry.seq,
|
|
4706
|
+
mode: 'reply',
|
|
4707
|
+
turn: entry.turn,
|
|
4708
|
+
messageId: entry.messageId,
|
|
4709
|
+
text: typeof entry.text === 'string' ? entry.text : '',
|
|
4710
|
+
attachments: typeof entry.attachments === 'number' ? entry.attachments : 0,
|
|
4711
|
+
...(Array.isArray(entry.blocks) ? { blocks: entry.blocks, patch: true } : { blocks: [] }),
|
|
4712
|
+
})
|
|
4713
|
+
}
|
|
4714
|
+
const copy = (event) => {
|
|
4715
|
+
event.preventDefault()
|
|
4716
|
+
event.stopPropagation()
|
|
4717
|
+
const text = replyCopyText(entry)
|
|
4718
|
+
if (text === '') return
|
|
4719
|
+
const write = primitives !== null && primitives !== undefined && typeof primitives.writeClipboard === 'function'
|
|
4720
|
+
? primitives.writeClipboard
|
|
4721
|
+
: typeof navigator !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.writeText === 'function'
|
|
4722
|
+
? (value) => navigator.clipboard.writeText(value)
|
|
4723
|
+
: null
|
|
4724
|
+
if (write === null) return
|
|
4725
|
+
Promise.resolve()
|
|
4726
|
+
.then(() => write(text))
|
|
4727
|
+
.then(
|
|
4728
|
+
() => {
|
|
4729
|
+
setCopied(true)
|
|
4730
|
+
window.setTimeout(() => setCopied(false), 1200)
|
|
4731
|
+
},
|
|
4732
|
+
() => {},
|
|
4733
|
+
)
|
|
4734
|
+
}
|
|
4735
|
+
return jsxs('div', {
|
|
4736
|
+
className: 'dshet-tail',
|
|
4737
|
+
'data-reveal': lastTurn === turn ? 'always' : 'hover',
|
|
4738
|
+
role: 'toolbar',
|
|
4739
|
+
'aria-label': t === null ? undefined : t('action.tail-actions'),
|
|
4740
|
+
children: [
|
|
4741
|
+
jsx('button', {
|
|
4742
|
+
type: 'button',
|
|
4743
|
+
className: 'dshet-action dshet-tail-action',
|
|
4744
|
+
'aria-label': label,
|
|
4745
|
+
title: label,
|
|
4746
|
+
onClick: open,
|
|
4747
|
+
key: 'edit',
|
|
4748
|
+
children: jsx(PencilIcon, {}),
|
|
4749
|
+
}),
|
|
4750
|
+
jsx('button', {
|
|
4751
|
+
type: 'button',
|
|
4752
|
+
className: 'dshet-action dshet-tail-action',
|
|
4753
|
+
'data-copied': copied ? '1' : undefined,
|
|
4754
|
+
'aria-label': copyLabel,
|
|
4755
|
+
title: copyLabel,
|
|
4756
|
+
onClick: copy,
|
|
4757
|
+
key: 'copy',
|
|
4758
|
+
children: copied ? jsx(CheckIcon, {}) : jsx(CopyIcon, {}),
|
|
4759
|
+
}),
|
|
4760
|
+
],
|
|
4761
|
+
})
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4534
4764
|
// --- react entries --------------------------------------------------------
|
|
4535
4765
|
|
|
4536
4766
|
function OverlayEntry({ useChat, useEditTurn, controller, t }) {
|
|
@@ -5012,6 +5242,23 @@ window.__ModuleLoader__.load({
|
|
|
5012
5242
|
// The row's configure arrow in the plugin manager. Without this the
|
|
5013
5243
|
// component page is a read-only overview (the card cannot be opened); the
|
|
5014
5244
|
// key is the manager's `<bundle package>#<row id>` contract.
|
|
5245
|
+
// The fallback strip, for turns the host draws no action row for (see
|
|
5246
|
+
// TailActionEntry). Order 40 keeps it after the shipped contributors
|
|
5247
|
+
// (schedule sits at 20). It is hidden by CSS wherever the host's own strip
|
|
5248
|
+
// exists, so ordering only matters on the turns it appears at all.
|
|
5249
|
+
ctx.slots.inject('conversation.chat.turnTail', () =>
|
|
5250
|
+
ctx.slots.register(
|
|
5251
|
+
{
|
|
5252
|
+
name: 'conversation.chat.turnTail',
|
|
5253
|
+
id: 'edit-turn-tail',
|
|
5254
|
+
order: 40,
|
|
5255
|
+
locale: NS,
|
|
5256
|
+
inject: (sessionId) => ({ controller: controllerFor(sessionId) }),
|
|
5257
|
+
},
|
|
5258
|
+
TailActionEntry,
|
|
5259
|
+
),
|
|
5260
|
+
)
|
|
5261
|
+
|
|
5015
5262
|
ctx.slots.inject('plugins.row.config', () =>
|
|
5016
5263
|
ctx.slots.register(
|
|
5017
5264
|
{
|
|
@@ -5062,7 +5309,7 @@ window.__ModuleLoader__.load({
|
|
|
5062
5309
|
const ROUTE_PREFIX = '/dsh-rerun-turn'
|
|
5063
5310
|
|
|
5064
5311
|
/** Keep in sync with package.json and lib/index.js. */
|
|
5065
|
-
const PLUGIN_VERSION = '0.2.
|
|
5312
|
+
const PLUGIN_VERSION = '0.2.6'
|
|
5066
5313
|
|
|
5067
5314
|
// --- copy -----------------------------------------------------------------
|
|
5068
5315
|
|
|
@@ -8732,7 +8979,7 @@ window.__ModuleLoader__.load({
|
|
|
8732
8979
|
const NS = 'dsh-as-aistudio'
|
|
8733
8980
|
|
|
8734
8981
|
/** Keep in sync with package.json and src/components.js. */
|
|
8735
|
-
const PLUGIN_VERSION = '0.
|
|
8982
|
+
const PLUGIN_VERSION = '0.4.0'
|
|
8736
8983
|
|
|
8737
8984
|
/** Presence marker the live verifier reads. */
|
|
8738
8985
|
const DEBUG_KEY = '__DSH_AS_AISTUDIO__'
|
package/src/components.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export const STUDIO_ID = 'dsh-as-aistudio'
|
|
27
27
|
|
|
28
28
|
/** Keep in sync with package.json and src/client.js. */
|
|
29
|
-
export const STUDIO_VERSION = '0.
|
|
29
|
+
export const STUDIO_VERSION = '0.4.0'
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* The AI Studio action strip this studio composes, in the order the user meets
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// A component copied into this package must NOT get its own copy of
|
|
4
4
|
// `@deepseek-ai/dsh-session`: two module instances mean two registries, and the
|
|
5
5
|
// second copy's `appendPluginRecord` cannot touch a Session the host created
|
|
6
|
-
// (the brand check inside it throws).
|
|
7
|
-
//
|
|
8
|
-
// expects.
|
|
6
|
+
// (the brand check inside it throws). So the copies import THIS file, and this
|
|
7
|
+
// file resolves that one package from the running harness.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// (`@deepseek-ai/dsh-tools` used to be here as well, for the agent tool that
|
|
10
|
+
// dsh-edit-turn registered. That tool is gone - the family exposes no model-facing
|
|
11
|
+
// tools - so the studio no longer needs the package at all.) The harness lives in the `dsh` installation, so the base
|
|
12
12
|
// is found by walking up from this file to the nearest node_modules that has it,
|
|
13
13
|
// with the profile's own tree as the second try. A failure here is a failure of
|
|
14
14
|
// the whole component row and says exactly which package was missing - silence
|
|
@@ -125,8 +125,5 @@ async function importPlatformOrThrow(spec) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/** `@deepseek-ai/dsh-tools`, resolved from the running harness. */
|
|
129
|
-
export const dshTools = await importPlatformOrThrow('@deepseek-ai/dsh-tools')
|
|
130
|
-
|
|
131
128
|
/** `@deepseek-ai/dsh-session`, resolved from the running harness. */
|
|
132
129
|
export const dshSession = await importPlatformOrThrow('@deepseek-ai/dsh-session')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-edit-turn host half (dsh-edit-turn.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-edit-turn/lib/index.js (v0.5.
|
|
3
|
+
// Source: ../dsh-edit-turn/lib/index.js (v0.5.6, sha256 c4da00d187ff461d)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -46,9 +46,8 @@
|
|
|
46
46
|
// is why an `assistant/message` carrier is impossible (it forbids
|
|
47
47
|
// `sourceEventSeqs` outright).
|
|
48
48
|
//
|
|
49
|
-
// The module resolves `@deepseek-ai/schemastery` defensively.
|
|
50
|
-
//
|
|
51
|
-
// package is a regular dependency: when it cannot be resolved - a pruned
|
|
49
|
+
// The module resolves `@deepseek-ai/schemastery` defensively. The schema package
|
|
50
|
+
// is a regular dependency: when it cannot be resolved - a pruned
|
|
52
51
|
// package cache, an installation that skipped dependencies - a static import
|
|
53
52
|
// takes the whole plugin down and the feature simply disappears from the UI
|
|
54
53
|
// with nothing but a line on the host's stdout to explain it. A guarded require
|
|
@@ -57,7 +56,6 @@
|
|
|
57
56
|
import { randomUUID } from 'node:crypto'
|
|
58
57
|
import { readFile } from 'node:fs/promises'
|
|
59
58
|
import { createRequire } from 'node:module'
|
|
60
|
-
import { dshTools } from './_platform.js'
|
|
61
59
|
import { dshSession as sessionApi } from './_platform.js'
|
|
62
60
|
import {
|
|
63
61
|
PATCH_TYPE,
|
|
@@ -70,9 +68,6 @@ import {
|
|
|
70
68
|
projectedBlocks,
|
|
71
69
|
} from './dsh-edit-turn.patch.js'
|
|
72
70
|
|
|
73
|
-
const { defineTool } = dshTools
|
|
74
|
-
|
|
75
|
-
|
|
76
71
|
const nodeRequire = createRequire(import.meta.url)
|
|
77
72
|
const schemaPackage = (() => {
|
|
78
73
|
try {
|
|
@@ -90,7 +85,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
|
|
|
90
85
|
export const name = PLUGIN_ID
|
|
91
86
|
|
|
92
87
|
/** Keep in sync with package.json and lib/client.js. */
|
|
93
|
-
export const PLUGIN_VERSION = '0.5.
|
|
88
|
+
export const PLUGIN_VERSION = '0.5.6'
|
|
94
89
|
|
|
95
90
|
// A ring of the last few requests, answering "did the browser half even ask, and
|
|
96
91
|
// what did it get?" without needing its console. Read-only diagnostics: the
|
|
@@ -2345,62 +2340,6 @@ function sessionIdFromExec(exec) {
|
|
|
2345
2340
|
return undefined
|
|
2346
2341
|
}
|
|
2347
2342
|
|
|
2348
|
-
function registerTool(ctx, config) {
|
|
2349
|
-
const tools = ctx.get('tools')
|
|
2350
|
-
if (!tools || typeof tools.register !== 'function') return
|
|
2351
|
-
tools.register(
|
|
2352
|
-
defineTool({
|
|
2353
|
-
name: 'edit_turn_targets',
|
|
2354
|
-
description:
|
|
2355
|
-
'List the user turns and model replies of a DeepSeek Harness session that the edit-turn plugin can rewrite, with each message\'s current text and, for a reply, every editable block (output text and reasoning). A prompt is replaced where it stands; a reply block is patched in place when the host supports projections, otherwise the reply correction is appended and the later turns are replayed behind it. Read-only: it changes nothing.',
|
|
2356
|
-
parameters: {
|
|
2357
|
-
sessionId: {
|
|
2358
|
-
type: 'string',
|
|
2359
|
-
description: 'Session id to inspect. Defaults to the session this agent is running in.',
|
|
2360
|
-
},
|
|
2361
|
-
limit: { type: 'number', description: 'Maximum number of turns to return (default 20).' },
|
|
2362
|
-
},
|
|
2363
|
-
output: {
|
|
2364
|
-
schema: { type: 'string' },
|
|
2365
|
-
render: (_args, value) => [{ type: 'text', text: value }],
|
|
2366
|
-
},
|
|
2367
|
-
async execute(args, exec) {
|
|
2368
|
-
const sessionId = typeof args.sessionId === 'string' && args.sessionId !== '' ? args.sessionId : sessionIdFromExec(exec)
|
|
2369
|
-
if (sessionId === undefined) {
|
|
2370
|
-
throw new Error('no sessionId was supplied and the calling agent has no session')
|
|
2371
|
-
}
|
|
2372
|
-
const events = await readEvents(ctx, sessionId)
|
|
2373
|
-
if (!events) throw new Error(`no session log for ${sessionId}`)
|
|
2374
|
-
const nodes = surfaceOf(ctx, sessionId, events)
|
|
2375
|
-
const patches = patchesOf(events)
|
|
2376
|
-
const turns = editableTurns(events, nodes)
|
|
2377
|
-
const replies = editableReplies(events, nodes, { patches, patch: config && config.patch === true })
|
|
2378
|
-
const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.floor(args.limit) : 20
|
|
2379
|
-
const shown = turns.slice(-limit)
|
|
2380
|
-
const shownReplies = replies.slice(-limit)
|
|
2381
|
-
const lines = [
|
|
2382
|
-
`session ${sessionId}: ${turns.length} editable turn(s), ${replies.length} editable reply/replies, ${isBusy(events) ? 'BUSY' : 'idle'}`,
|
|
2383
|
-
]
|
|
2384
|
-
for (const turn of shown) {
|
|
2385
|
-
const preview = turn.text.length > 160 ? `${turn.text.slice(0, 160)}...` : turn.text
|
|
2386
|
-
lines.push(`- turn ${turn.turn} (seq ${turn.seq}, ${turn.attachments} attachment(s)): ${preview}`)
|
|
2387
|
-
}
|
|
2388
|
-
if (turns.length > shown.length) lines.push(`(${turns.length - shown.length} earlier turn(s) omitted)`)
|
|
2389
|
-
for (const reply of shownReplies) {
|
|
2390
|
-
const preview = reply.text.length > 160 ? `${reply.text.slice(0, 160)}...` : reply.text
|
|
2391
|
-
const blocks = Array.isArray(reply.blocks)
|
|
2392
|
-
? ` [${reply.blocks.map((block) => `${block.index}:${block.kind}${block.patched ? '*' : ''}`).join(', ')}]`
|
|
2393
|
-
: ''
|
|
2394
|
-
lines.push(`- reply (seq ${reply.seq}, ${reply.attachments} non-text part(s))${blocks}: ${preview}`)
|
|
2395
|
-
}
|
|
2396
|
-
if (replies.length > shownReplies.length) lines.push(`(${replies.length - shownReplies.length} earlier reply/replies omitted)`)
|
|
2397
|
-
lines.push('editing a turn replaces its wording in place (no model call); editing a reply block patches it in place when the host supports plugin-record projections, otherwise it replaces the reply and replays the tail. Re-running a turn is dsh-rerun-turn\'s button.')
|
|
2398
|
-
return lines.join('\n')
|
|
2399
|
-
},
|
|
2400
|
-
}),
|
|
2401
|
-
)
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
2343
|
// --- http --------------------------------------------------------------------
|
|
2405
2344
|
|
|
2406
2345
|
function isLoopbackAddress(address) {
|
|
@@ -2802,12 +2741,6 @@ export function apply(ctx, config) {
|
|
|
2802
2741
|
ctx.inject(['webServer'], (sub) => registerRoutes(sub.webServer, sub))
|
|
2803
2742
|
}
|
|
2804
2743
|
|
|
2805
|
-
const tools = ctx.get('tools')
|
|
2806
|
-
if (tools) {
|
|
2807
|
-
registerTool(ctx, resolved)
|
|
2808
|
-
} else {
|
|
2809
|
-
ctx.inject(['tools'], (sub) => registerTool(sub, resolved))
|
|
2810
|
-
}
|
|
2811
2744
|
}
|
|
2812
2745
|
|
|
2813
2746
|
export default apply
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-edit-turn host half (dsh-edit-turn.patch.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-edit-turn/lib/patch.js (v0.5.
|
|
3
|
+
// Source: ../dsh-edit-turn/lib/patch.js (v0.5.6, sha256 5e1e8cce1d168f6c)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// dsh-rerun-turn host half (dsh-rerun-turn.js), VENDORED by tools/vendor.mjs - DO NOT EDIT.
|
|
2
2
|
//
|
|
3
|
-
// Source: ../dsh-rerun-turn/lib/index.js (v0.2.
|
|
3
|
+
// Source: ../dsh-rerun-turn/lib/index.js (v0.2.6, sha256 3e20b1a0a07cf213)
|
|
4
4
|
// Regenerate with `npm run vendor`; test/sync.test.js fails when this is stale.
|
|
5
5
|
//
|
|
6
6
|
// src/shell.js imports the copy named after the component, which is why the
|
|
@@ -90,7 +90,7 @@ export const PLUGIN_ID = 'dsh-rerun-turn'
|
|
|
90
90
|
export const name = PLUGIN_ID
|
|
91
91
|
|
|
92
92
|
/** Keep in sync with package.json and lib/client.js. */
|
|
93
|
-
export const PLUGIN_VERSION = '0.2.
|
|
93
|
+
export const PLUGIN_VERSION = '0.2.6'
|
|
94
94
|
|
|
95
95
|
const ROUTE_PREFIX = '/dsh-rerun-turn'
|
|
96
96
|
// A session id is an OPAQUE string to DSH: the host's own schema is
|
|
@@ -1182,6 +1182,35 @@ export const ADAPTER_DROPS_EMPTY_USER_CONTENT = probeAdapterDropsEmptyUserConten
|
|
|
1182
1182
|
* injectable so the shape is testable without this machine's adapter cache.
|
|
1183
1183
|
* @returns a one-element `{ type, data }` list for `session.append`.
|
|
1184
1184
|
*/
|
|
1185
|
+
/**
|
|
1186
|
+
* Normalize a shadowed-seq list to the shape the host's contract demands.
|
|
1187
|
+
*
|
|
1188
|
+
* `assertSourceEventReferences` requires a non-empty array of non-negative safe
|
|
1189
|
+
* integers, no duplicates, every entry earlier than the citing event. A list that
|
|
1190
|
+
* is nested (a range written as a pair), holds a non-number, or repeats a seq makes
|
|
1191
|
+
* the whole event unadoptable: the host's strict readers (`adoptSessionEvent`,
|
|
1192
|
+
* `snapshotSessionEvent`, `foldSurface`) reject it outright, so a log carrying one
|
|
1193
|
+
* can only be opened by the lenient path.
|
|
1194
|
+
*
|
|
1195
|
+
* @param shadowed - candidate list from a plan or a recovered record.
|
|
1196
|
+
* @returns a flat, ascending, de-duplicated list of seqs.
|
|
1197
|
+
*/
|
|
1198
|
+
export function normalizeShadowed(shadowed) {
|
|
1199
|
+
const flat = []
|
|
1200
|
+
const visit = (value) => {
|
|
1201
|
+
if (Array.isArray(value)) {
|
|
1202
|
+
for (const inner of value) visit(inner)
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1205
|
+
// 只认真正的数字:`Number(null)` 是 0,而 0 在 0 基日志里是个合法 seq,
|
|
1206
|
+
// 用 Number() 强转等于往引用列表里塞一条假引用。
|
|
1207
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) return
|
|
1208
|
+
flat.push(value)
|
|
1209
|
+
}
|
|
1210
|
+
visit(shadowed)
|
|
1211
|
+
return [...new Set(flat)].sort((a, b) => a - b)
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1185
1214
|
export function buildShadowWrites(
|
|
1186
1215
|
plan,
|
|
1187
1216
|
rerunId,
|
|
@@ -1192,7 +1221,8 @@ export function buildShadowWrites(
|
|
|
1192
1221
|
{
|
|
1193
1222
|
type: 'user/message',
|
|
1194
1223
|
surfaceOp: { op: 'replace', startSeq: plan.startSeq, endSeq: plan.endSeq },
|
|
1195
|
-
|
|
1224
|
+
// 写入前规范化:嵌套数组/重复项/非整数都会让宿主的严格校验器拒绝整条事件。
|
|
1225
|
+
sourceEventSeqs: normalizeShadowed(plan.shadowed),
|
|
1196
1226
|
data: {
|
|
1197
1227
|
id: randomUUID(),
|
|
1198
1228
|
role: 'user',
|
|
@@ -1978,8 +2008,10 @@ async function performReplay(ctx, sessionId, rerunId, config) {
|
|
|
1978
2008
|
logTo: record.logTo ?? record.endSeq,
|
|
1979
2009
|
promptSeq: record.promptSeq ?? record.startSeq,
|
|
1980
2010
|
// The replay copies exactly these surface nodes (and nothing the log
|
|
1981
|
-
// happens to hold between them), so the shadowed list must travel
|
|
1982
|
-
|
|
2011
|
+
// happens to hold between them), so the shadowed list must travel - and it
|
|
2012
|
+
// travels normalized, because a record read back from an older log may hold
|
|
2013
|
+
// the nested shape this plugin used to write.
|
|
2014
|
+
shadowed: normalizeShadowed(record.shadowed),
|
|
1983
2015
|
}
|
|
1984
2016
|
const startSeq = session.seq
|
|
1985
2017
|
const writes = buildReplayWrites(events, plan, baseTurn, rerunId, startSeq, !promptLanded)
|
package/src/vendor/versions.json
CHANGED