@cola1900/dsh-ppt 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  从 [dataelement/dsh-desktop](https://github.com/dataelement/dsh-desktop) 提取的独立 **DSH / DeepSeek Harness 插件**。一个包包含 PPT 按钮、模板选择、PPTD 编写与校验、预览和可编辑 PPTX 导出,无需 Electron 或原 Desktop 仓库。
4
4
 
5
5
  - 16 套模板、192 个版式,保留英文/中文 PPTD 示例和本地预览图。
6
- - 仅在会话启用 PPT 模式时自动注入创作指引。
6
+ - 提供原生 DSH **PPT 预设**;只有选择该预设时才注入 PPT 创作指引。
7
7
  - 保留原有文件授权、路径限制、写入哈希检查、版本记录和导出校验。
8
8
  - 提供 `dsh-pptd` 命令行和 `@cola1900/dsh-ppt/pptd` JavaScript API。
9
9
 
@@ -17,9 +17,9 @@
17
17
  dsh plugin --profile web add @cola1900/dsh-ppt
18
18
  ```
19
19
 
20
- 把 `web` 替换为实际 profile,然后重启该 profile。新会话中点击输入框底部工具栏的 **PPT**,在输入框下方选择模板,再描述需求。选中后面板收起,按钮以灰黑色选中态显示模板名,不常驻显示模板缩略图。过长的模板名显示省略号,悬停可查看完整名称。再次点击可更换模板或退出 PPT 模式;仅打开、关闭面板不会改变当前模式。
20
+ 把 `web` 替换为实际 profile,然后重启该 profile。新会话顶部的原生预设选择器会出现 **PPT**;选择后,输入框下方显示模板面板,在首次发送前选择模板,再描述需求。首次发送后预设和模板固定,新会话可以重新选择。插件首次启动会把内置 preset 写入 `$DSH_HOME/.agent-presets/ppt`,无需手动复制或配置。
21
21
 
22
- 界面使用官方 `conversation.input.left` `conversation.input.dock` 插槽,只设置插件自身样式,不修改原 DSH。模板面板始终在输入框下方,列表内部可滚动;窗口较矮时可滚动页面,不会自动翻到上方。
22
+ 界面只使用官方 `conversation.input.dock` 插槽,只设置插件自身样式,不修改原 DSH。模板名称继续在卡片内显示省略号,输入框底部不再显示 `PPT · 模板名` 控件。
23
23
 
24
24
  如果目标 Desktop 已内置 `dsh-ppt-composer`,先在目标 profile 的插件配置中停用原 PPT bundle,再加载本包。不要同时启用两套 PPT bundle,否则会重复注册工具、路由和输入框按钮。本包已经合并 composer,无需再安装 `dsh-ppt-composer`。
25
25
 
@@ -40,7 +40,7 @@ dsh-pptd render /path/to/deck -o /path/to/output.pptx --json
40
40
 
41
41
  ## 开发
42
42
 
43
- `lib/` 是上游维护的 JavaScript 运行时代码;`src/client.js` 是输入框客户端的构建输入。完整原始 TypeScript 源码不在上游提取基线中,本项目没有把声明文件冒充实现源码。
43
+ `lib/` 是上游维护的 JavaScript 运行时代码;`src/client.js` 是输入框客户端的构建输入。`presets/ppt/` 是随插件安装的原生 Agent Preset 定义,首次启动会同步到 DSH 的用户 preset 根目录。完整原始 TypeScript 源码不在上游提取基线中,本项目没有把声明文件冒充实现源码。
44
44
 
45
45
  `skills/dsh-ppt/references/` 保存模板元数据、设计说明、双语源文件和预览。修改元数据或预览后执行 `npm run build`,生成模板目录、预览白名单和客户端预览映射。构建使用已随包保存的预览,不依赖网络或原桌面工程。修改版式后可用 `dsh-pptd screenshot` 重新生成图片,再更新对应的 JPG 预览。
46
46
 
package/UPSTREAM.md CHANGED
@@ -8,7 +8,7 @@
8
8
  - 模板:`packages/ppt-runtime/templates/`
9
9
  - 模板许可证与来源资料:`packages/ppt-runtime/upstream/`
10
10
 
11
- 独立化改动:合并核心和 composer 为单个 `dsh-ppt` 包;客户端模块标识统一为 `dsh-ppt`;将 Desktop 专有输入框插槽替换为官方 `conversation.input.left` `conversation.input.dock`,按钮放在输入框底部工具栏,模板面板始终在输入框下方展开,选中后收起并在按钮显示模板名;移除常驻的选中缩略图,区分收起面板和退出 PPT 模式;移除指向缺失文件的 `client-standard` 导出,并使用匹配实际客户端的类型声明;将构建改为读取包内模板、生成目录和预览白名单;移除未使用的 TypeScript、Zod 运行依赖;增加独立安装、打包说明及运行验证。
11
+ 独立化改动:合并核心和 composer 为单个 `dsh-ppt` 包;客户端模块标识统一为 `dsh-ppt`;改用 DSH 原生 Agent Preset,随包提供 `presets/ppt/` 并在首次启动时同步到用户 preset 根目录;模板面板只在 PPT 预设的空白会话中通过官方 `conversation.input.dock` 插槽显示,首次发送后固定预设和模板;移除输入框底部 `PPT · 模板名` 控件;移除常驻的选中缩略图;移除指向缺失文件的 `client-standard` 导出,并使用匹配实际客户端的类型声明;将构建改为读取包内模板、生成目录和预览白名单;移除未使用的 TypeScript、Zod 运行依赖;增加独立安装、打包说明及运行验证。
12
12
 
13
13
  输入框布局只通过插件自身 CSS 和标准会话插槽实现,不修改原 DSH 源码或宿主元素样式。
14
14
 
package/cordis.patch.yml CHANGED
@@ -6,3 +6,4 @@
6
6
  name: '@cola1900/dsh-ppt'
7
7
  config:
8
8
  root: !!js dshHomePath('kimi-ppt')
9
+ presetRoot: !!js dshHomePath('.agent-presets')
package/lib/client.js CHANGED
@@ -30,21 +30,9 @@ window.__ModuleLoader__.load({
30
30
  tag.dataset.plugin = "dsh-ppt";
31
31
  tag.dataset.pluginCss = tagId;
32
32
  tag.textContent = css + `
33
- .dsh-ppt-control{display:inline-flex;align-items:center;flex:none;min-width:0}
34
- .dsh-ppt-trigger{box-sizing:border-box;display:inline-flex;align-items:center;gap:6px;max-width:220px;min-height:30px;padding:5px 9px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#626875);font:inherit;font-size:12px;line-height:18px;cursor:pointer}
35
- .dsh-ppt-trigger:hover,.dsh-ppt-trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}
36
- .dsh-ppt-trigger[data-selected=true]{color:var(--dsw-alias-label-primary,#20232a);background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}
37
- .dsh-ppt-trigger-mode{flex:none;white-space:nowrap}
38
- .dsh-ppt-trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
39
- .dsh-ppt-trigger .desktop-ppt-icon{width:16px;height:16px;flex:none}
40
- .dsh-ppt-chevron{font-size:10px;flex:none;opacity:.7}
41
33
  .dsh-ppt-panel{order:1;align-self:center;display:flex;flex-direction:column;flex-shrink:0;margin:0;padding:0;box-sizing:border-box;width:calc(100% - 2 * var(--dsh-composer-side-clearance,16px));max-height:400px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,#e4e6eb);border-radius:14px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#20232a);box-shadow:0 12px 40px #0000001a,0 2px 8px #00000008;font:13px/1.5 var(--font-sans,system-ui,sans-serif)}
42
34
  .dsh-ppt-panel-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px 8px;flex:none}
43
35
  .dsh-ppt-panel-title{font-size:14px;font-weight:600;margin:0}
44
- .dsh-ppt-panel-actions{display:flex;align-items:center;gap:8px}
45
- .dsh-ppt-exit,.dsh-ppt-close{border:0;background:transparent;color:var(--dsw-alias-label-secondary,#626875);font:inherit;cursor:pointer;border-radius:6px;padding:4px 8px}
46
- .dsh-ppt-close{font-size:20px;line-height:20px;padding:2px 7px}
47
- .dsh-ppt-exit:hover,.dsh-ppt-close:hover{background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}
48
36
  .dsh-ppt-panel ._2S_x-q_categoryTabs{padding:0 12px 10px;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2-darkmode-thin,#eceef1)}
49
37
  .dsh-ppt-panel ._2S_x-q_categoryTabs button{font-size:12px;padding:4px 9px}
50
38
  .dsh-ppt-panel ._2S_x-q_categoryTabs button[data-selected=true]{color:var(--dsw-alias-label-primary,#20232a);background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}
@@ -55,10 +43,10 @@ window.__ModuleLoader__.load({
55
43
  .dsh-ppt-panel ._2S_x-q_templateName{font-size:12px;text-align:left;padding:6px 2px 0;width:100%;box-sizing:border-box}
56
44
  .dsh-ppt-panel ._2S_x-q_previewViewport{border-radius:8px}
57
45
  .dsh-ppt-selected{position:absolute;top:8px;right:8px;z-index:3;display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:var(--dsw-alias-label-primary,#20232a);color:var(--dsw-alias-bg-base,#fff);font-size:12px;box-shadow:0 1px 4px #0002;pointer-events:none}
58
- .dsh-ppt-trigger:focus-visible,.dsh-ppt-panel button:focus-visible{outline:2px solid var(--dsw-alias-label-primary,#20232a);outline-offset:2px}
46
+ .dsh-ppt-panel button:focus-visible{outline:2px solid var(--dsw-alias-label-primary,#20232a);outline-offset:2px}
59
47
  .dsh-ppt-panel button:disabled{cursor:wait;opacity:.55}
60
48
  .dsh-ppt-panel-notice{margin:0 16px 8px;color:var(--dsw-alias-label-secondary,#626875);font-size:12px}
61
- @media(max-width:600px){.dsh-ppt-panel ._2S_x-q_templateGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.dsh-ppt-trigger{max-width:170px}}
49
+ @media(max-width:600px){.dsh-ppt-panel ._2S_x-q_templateGrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
62
50
  `;
63
51
  document.head.appendChild(tag);
64
52
  }
@@ -418,57 +406,29 @@ window.__ModuleLoader__.load({
418
406
  ]
419
407
  });
420
408
  }
421
- /** The picker is independent of PPT activation; closing it never changes the session. */
422
- function OfficePptChooser({ client, mode, sessionId, t, placement }) {
409
+ /** Template picker shown only for the native PPT agent preset. */
410
+ function OfficePptChooser({ client, mode, sessionId, t }) {
423
411
  const state = useMode(mode, sessionId);
424
- const open = state.pickerOpen;
425
- const setOpen = value => mode.setPickerOpen(sessionId, value);
426
412
  const [category, setCategory] = react.useState("all");
427
- const panel = react.useRef(null);
428
- const panelId = `dsh-ppt-picker-${encodeURIComponent(sessionId)}`;
429
- const triggerId = `${panelId}-trigger`;
430
- const active = state.activeMode === "ppt";
431
- const selected = state.templates.find(template => template.id === state.selectedId);
432
- const label = active && selected ? `${t("mode.label")} · ${selected.name}` : t("mode.label");
433
- const close = () => { setOpen(false); document.getElementById(triggerId)?.focus({ preventScroll: true }); };
434
413
 
435
414
  react.useEffect(() => {
436
- if (placement !== "trigger" || mode.snapshot(sessionId).loading || state.templates.length > 0 || state.error !== "") return;
415
+ if (mode.snapshot(sessionId).loading || state.templates.length > 0 || state.error !== "") return;
437
416
  mode.setLoading(sessionId, true);
438
417
  loadTemplateState(client, t("templates.loadTimeout")).then(next => {
439
418
  mode.setTemplates(sessionId, next.templates);
440
419
  const template = next.templates.find(item => item.id === next.selectedTemplateId);
441
- const activeMode = next.presentationMode === "ppt" ? "ppt" : null;
442
- if (template) mode.select(sessionId, template, activeMode);
443
- else mode.deselect(sessionId, activeMode);
420
+ if (template) mode.select(sessionId, template, "ppt");
421
+ else mode.deselect(sessionId, "ppt");
444
422
  mode.setNotice(sessionId, next.templateMigration?.reason === "template-retired");
445
423
  }).catch(reason => mode.setError(sessionId, reason instanceof Error ? reason.message : String(reason)));
446
- }, [client, mode, sessionId, placement, state.loading, state.templates.length, state.error, t]);
447
-
448
- react.useLayoutEffect(() => {
449
- if (!open || placement !== "panel") return;
450
- panel.current.querySelector('[data-office-ppt-template-viewport] button[aria-pressed="true"], [data-ppt-category]')?.focus({ preventScroll: true });
451
- }, [open, placement]);
424
+ }, [client, mode, sessionId, state.loading, state.templates.length, state.error, t]);
452
425
 
453
426
  const choose = async template => {
454
- if (state.loading) return;
455
- if (active && template.id === state.selectedId) { close(); return; }
427
+ if (state.loading || template.id === state.selectedId) return;
456
428
  mode.setLoading(sessionId, true);
457
429
  try {
458
430
  await client.call("template/select", { templateId: template.id, mode: "ppt" });
459
431
  mode.select(sessionId, template, "ppt");
460
- close();
461
- } catch (reason) {
462
- mode.setError(sessionId, reason instanceof Error ? reason.message : String(reason));
463
- }
464
- };
465
- const exitPpt = async () => {
466
- mode.setLoading(sessionId, true);
467
- try {
468
- await client.call("presentation/mode", { mode: null });
469
- mode.setMode(sessionId, null);
470
- mode.setLoading(sessionId, false);
471
- close();
472
432
  } catch (reason) {
473
433
  mode.setError(sessionId, reason instanceof Error ? reason.message : String(reason));
474
434
  }
@@ -477,28 +437,13 @@ window.__ModuleLoader__.load({
477
437
  const categories = ["all", ...new Set(templates.map(templateCategory))];
478
438
  const visible = templates.filter(template => category === "all" || templateCategory(template) === category);
479
439
  const h = react.createElement;
480
- if (placement === "trigger") return h("span", { className: "dsh-ppt-control" },
481
- h("button", {
482
- id: triggerId, type: "button", className: "dsh-ppt-trigger", title: label, "aria-label": label,
483
- "data-desktop-ppt": "", "data-selected": active || undefined,
484
- "aria-expanded": open, "aria-pressed": active,
485
- "aria-controls": open ? panelId : undefined,
486
- onClick: () => setOpen(!open)
487
- }, h(DesktopPptIcon), h("span", { className: "dsh-ppt-trigger-mode" }, t("mode.label") + (active && selected ? " ·" : "")),
488
- active && selected && h("span", { className: "dsh-ppt-trigger-label" }, selected.name),
489
- h("span", { className: "dsh-ppt-chevron", "aria-hidden": true }, "⌄")));
490
- if (!open) return null;
491
440
  return h("section", {
492
- ref: panel, id: panelId, "aria-label": t("templates.title"),
493
- className: "dsh-ppt-panel", "data-office-ppt-template-panel": "", "data-placement": "bottom",
494
- onKeyDown: event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(); } }
441
+ "aria-label": t("templates.title"),
442
+ className: "dsh-ppt-panel", "data-office-ppt-template-panel": "", "data-placement": "bottom"
495
443
  },
496
444
  h("div", { className: "dsh-ppt-panel-header" },
497
- h("h2", { className: "dsh-ppt-panel-title" }, t("templates.title")),
498
- h("div", { className: "dsh-ppt-panel-actions" },
499
- active && h("button", { type: "button", className: "dsh-ppt-exit", disabled: state.loading, onClick: exitPpt }, t("mode.exit")),
500
- h("button", { type: "button", className: "dsh-ppt-close", "aria-label": t("templates.close"), onClick: close }, "×"))),
501
- state.notice && active && h("p", { className: "dsh-ppt-panel-notice", role: "status" }, t("templates.migrated")),
445
+ h("h2", { className: "dsh-ppt-panel-title" }, t("templates.title"))),
446
+ state.notice && h("p", { className: "dsh-ppt-panel-notice", role: "status" }, t("templates.migrated")),
502
447
  h("div", { className: OfficePptHero_module_css_default.categoryTabs, role: "group", "aria-label": t("templates.categories") },
503
448
  categories.map(item => h("button", {
504
449
  key: item, type: "button", "data-ppt-category": item, "aria-pressed": category === item,
@@ -509,13 +454,19 @@ window.__ModuleLoader__.load({
509
454
  state.templates.length === 0 && h("button", { type: "button", className: OfficePptHero_module_css_default.retryButton, onClick: () => mode.setError(sessionId, "") }, t("templates.retry"))),
510
455
  state.loading && templates.length === 0 ? h("div", { className: OfficePptHero_module_css_default.panelState }, t("status.loading")) :
511
456
  h("div", { className: OfficePptHero_module_css_default.templateGrid }, visible.map(template => h(TemplateCard, {
512
- key: template.id, template, selected: active && template.id === state.selectedId, choose, disabled: state.loading
457
+ key: template.id, template, selected: template.id === state.selectedId, choose, disabled: state.loading
513
458
  })))));
514
459
  }
515
460
  function OfficePptComposer(props) {
516
- const blank = props.useSession(session => session.blank);
517
- if (!blank) return null;
518
- return react_jsx_runtime.jsx(OfficePptChooser, { ...props }, props.sessionId);
461
+ const blank = props.useSession(current => current.blank);
462
+ const agentPreset = props.useSessions(current => {
463
+ const summary = current.byId[props.sessionId];
464
+ return summary?.projectionValues?.agentPreset ?? summary?.agentPreset;
465
+ });
466
+ if (!blank || agentPreset !== "ppt") return null;
467
+ return react_jsx_runtime.jsx(OfficePptChooser, {
468
+ client: props.client, mode: props.mode, sessionId: props.sessionId, t: props.t
469
+ }, props.sessionId);
519
470
  }
520
471
  //#endregion
521
472
  //#region ../office-ppt/src/client/locales.ts
@@ -697,25 +648,22 @@ window.__ModuleLoader__.load({
697
648
  mode
698
649
  });
699
650
  }
700
- /**
701
- * Register the compact PPT picker in the public composer toolbar.
702
- * @param ctx - Browser context that owns the standard Composer slots.
703
- */
651
+ /** Register the template picker below the composer for the native PPT preset. */
704
652
  function applyStandard(ctx) {
705
653
  const injectHero = officePptHeroInjection(ctx);
706
- // The standard composer is a flex column. Ordering only our dock last keeps
707
- // the picker below the input in normal flow, including blank sessions.
708
- for (const [name, placement] of [["conversation.input.left", "trigger"], ["conversation.input.dock", "panel"]]) {
709
- ctx.slots.inject(name, () => ctx.slots.register({
710
- name, id: "dsh-ppt", order: 20, locale: NS, inject: injectHero
711
- }, props => react_jsx_runtime.jsx(OfficePptComposer, { ...props, placement })));
712
- }
654
+ const name = "conversation.input.dock";
655
+ ctx.slots.inject(name, () => ctx.slots.register({
656
+ name, id: "dsh-ppt", order: 20, locale: NS, inject: injectHero
657
+ }, props => react_jsx_runtime.jsx(OfficePptComposer, {
658
+ client: props.client, mode: props.mode, sessionId: props.sessionId,
659
+ useSession: props.useSession, useSessions: props.useSessions, t: props.t
660
+ }, props.sessionId)));
713
661
  }
714
662
  //#endregion
715
663
  //#region src/client/index.ts
716
664
  /** Required standard browser services. */
717
665
  const inject = standardInject;
718
- /** Mount the PPT toolbar button and the collapsible picker below the input card. */
666
+ /** Mount the native-PPT-preset template picker below the input card. */
719
667
  function apply(ctx) {
720
668
  applyStandard(ctx);
721
669
  }
package/lib/index.js CHANGED
@@ -11,7 +11,7 @@ import { definitions as DSH_PPT_TEMPLATE_DEFINITIONS, semantics as DSH_PPT_TEMPL
11
11
  import { i as renderPptdProject, n as loadPptdProject, o as recommendedTextCapacity, r as parsePptdProject, t as checkPptdProject } from "./pptd-2VqVzr_T.js";
12
12
  import z from "@deepseek-ai/schemastery";
13
13
  import { fileURLToPath } from "node:url";
14
- import { access, appendFile, link, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
14
+ import { access, appendFile, copyFile, link, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
15
15
  import path from "node:path";
16
16
  import { BUNDLED_SKILL_RANK, renderSkillContent } from "@deepseek-ai/dsh-skill";
17
17
  import { createHash, randomUUID } from "node:crypto";
@@ -758,8 +758,7 @@ var PptService = class {
758
758
  state: {
759
759
  ...state,
760
760
  selectedTemplateId: template.id,
761
- templateMigration: undefined,
762
- presentationMode: "ppt"
761
+ templateMigration: undefined
763
762
  },
764
763
  value: template,
765
764
  summary: `已选择模板 ${template.name}`,
@@ -3143,6 +3142,20 @@ function pptComposerContext(state) {
3143
3142
  "workflow: direct local PPTD authoring with bounded pptd_* tools and final pptd_render conversion"
3144
3143
  ].join("\n");
3145
3144
  }
3145
+ /** Resolve the preset currently running this session, including blank-session switches. */
3146
+ function sessionAgentPreset(session) {
3147
+ if (session === void 0 || typeof session !== "object") return void 0;
3148
+ for (let index = (session.events?.length ?? 0) - 1; index >= 0; index -= 1) {
3149
+ const event = session.events[index];
3150
+ if (event?.type === "agent-preset/selected" && typeof event.data?.agentPreset === "string") return event.data.agentPreset;
3151
+ }
3152
+ return typeof session.header?.agentPreset === "string" ? session.header.agentPreset : void 0;
3153
+ }
3154
+ /** New sessions use the native preset; the state flag remains for old sessions. */
3155
+ function isPptSession(agent, state) {
3156
+ const preset = sessionAgentPreset(agent?.session);
3157
+ return preset === "ppt" || (preset === void 0 && state.presentationMode === "ppt");
3158
+ }
3146
3159
  function hasActiveSkill(agent) {
3147
3160
  return agent.session.deriveMessages().some((message) => {
3148
3161
  if (message.role !== "user") return false;
@@ -3207,7 +3220,7 @@ function registerPptTools(ctx, service) {
3207
3220
  });
3208
3221
  // Resolve the persisted composer state for this assembly, never a process-wide toggle.
3209
3222
  ctx.on("system-prompt/assemble", async (_assembly, { agent }, next) => {
3210
- const active = agent !== void 0 && (await service.state(agent.id)).presentationMode === "ppt";
3223
+ const active = agent !== void 0 && isPptSession(agent, await service.state(agent.id));
3211
3224
  const assembly = await next();
3212
3225
  return active ? assembly : {
3213
3226
  ...assembly,
@@ -3217,7 +3230,8 @@ function registerPptTools(ctx, service) {
3217
3230
  ctx.on("agent/pre-step", async ({ agent, step, signal }, next) => {
3218
3231
  const decision = await next();
3219
3232
  if (decision.kind === "reject" || signal.aborted) return decision;
3220
- const context = pptComposerContext(await service.state(agent.id));
3233
+ const state = await service.state(agent.id);
3234
+ const context = isPptSession(agent, state) ? pptComposerContext({ ...state, presentationMode: "ppt" }) : void 0;
3221
3235
  if (signal.aborted) return decision;
3222
3236
  if (context === void 0) {
3223
3237
  clearAutomaticPptContext(agent);
@@ -3604,6 +3618,7 @@ const Config = z.object({
3604
3618
  maxDecksPerSession: z.natural().min(1).default(50),
3605
3619
  maxActivities: z.natural().min(1).default(200),
3606
3620
  kimiPptSkillRoot: z.string(),
3621
+ presetRoot: z.string(),
3607
3622
  pptSkillRoot: z.string()
3608
3623
  });
3609
3624
  /** Compose storage, browser RPC, and bounded model tools. */
@@ -3663,8 +3678,29 @@ function registerPptRpcRoute(webCtx, channel, rpcHandler) {
3663
3678
  }), `dsh-ppt: ${channel} rpc route`);
3664
3679
  }
3665
3680
 
3681
+ /** Make the package preset visible to the native DSH roster on first install. */
3682
+ async function ensurePptPreset(root) {
3683
+ if (typeof root !== "string" || !path.isAbsolute(root)) return;
3684
+ const source = fileURLToPath(new URL("../presets/ppt/", import.meta.url));
3685
+ const target = path.join(root, "ppt");
3686
+ await mkdir(target, { recursive: true });
3687
+ for (const file of ["agent.cordis.yml", "preset.yml"]) {
3688
+ const destination = path.join(target, file);
3689
+ try {
3690
+ await access(destination);
3691
+ } catch {
3692
+ await copyFile(path.join(source, file), destination);
3693
+ }
3694
+ }
3695
+ }
3696
+
3666
3697
  async function apply(ctx, config) {
3667
3698
  const bundledPptSkillRoot = fileURLToPath(new URL("../skills/dsh-ppt", import.meta.url));
3699
+ try {
3700
+ await ensurePptPreset(config.presetRoot ?? path.join(path.dirname(config.root), ".agent-presets"));
3701
+ } catch (error) {
3702
+ ctx.logger.warn(`dsh-ppt: could not install native PPT preset: ${error instanceof Error ? error.message : String(error)}`);
3703
+ }
3668
3704
  registerPreviewAssets(ctx, previewFiles, path.join(bundledPptSkillRoot, "references"));
3669
3705
  registerPptSkill(ctx, config.pptSkillRoot ?? config.kimiPptSkillRoot ?? process.env.DSH_PPT_SKILL_ROOT ?? process.env.DSH_KIMI_PPT_SKILL_ROOT ?? bundledPptSkillRoot);
3670
3706
  const service = new PptService(new PptStore(config.root, {
@@ -3685,4 +3721,3 @@ async function apply(ctx, config) {
3685
3721
  }
3686
3722
  //#endregion
3687
3723
  export { Config, apply, inject, name };
3688
-
package/lib/preset.js ADDED
@@ -0,0 +1,4 @@
1
+ /** Native agent-preset marker for @cola1900/dsh-ppt. */
2
+ export const name = 'dsh-ppt-preset'
3
+ export const inject = []
4
+ export function apply() {}
@@ -15,6 +15,8 @@ export interface Config {
15
15
  maxDecksPerSession?: number;
16
16
  /** Maximum recent activity records retained in session state. */
17
17
  maxActivities?: number;
18
+ /** Absolute DSH agent-preset root used for the bundled PPT preset. */
19
+ presetRoot?: string;
18
20
  /** Absolute DSH PPT Skill override. The package-bundled Skill is the default. */
19
21
  pptSkillRoot?: string;
20
22
  }
@@ -22,4 +24,4 @@ export interface Config {
22
24
  export declare const Config: z<Config>;
23
25
  /** Compose storage, browser RPC, and bounded model tools. */
24
26
  export declare function apply(ctx: Context, config: Config): Promise<void>;
25
- export type { PptService } from './ppt-service.ts';
27
+ export type { PptService } from './ppt-service.ts';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cola1900/dsh-ppt",
3
3
  "description": "Standalone DSH plugin for PPT authoring, template selection, validation and editable PPTX export",
4
- "version": "0.1.3",
4
+ "version": "0.2.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/boe1900/dsh-ppt.git"
@@ -43,7 +43,8 @@
43
43
  "default": "./lib/pptd.js"
44
44
  },
45
45
  "./cordis.patch.yml": "./cordis.patch.yml",
46
- "./package.json": "./package.json"
46
+ "./package.json": "./package.json",
47
+ "./preset": "./lib/preset.js"
47
48
  },
48
49
  "dsh": {
49
50
  "bundle": {
@@ -93,6 +94,7 @@
93
94
  "files": [
94
95
  "lib",
95
96
  "skills",
97
+ "presets",
96
98
  "cordis.patch.yml",
97
99
  "LICENSE",
98
100
  "THIRD_PARTY_NOTICES.md",
@@ -0,0 +1,4 @@
1
+ # The PPT tools and prompt are supplied by the installed dsh-ppt bundle.
2
+ # This tiny entry makes the package's native PPT preset discoverable without
3
+ # mounting a second copy of the bundle's server and tool registrations.
4
+ - name: '@cola1900/dsh-ppt/preset'
@@ -0,0 +1,4 @@
1
+ name: PPT
2
+ # 固定 PPT 创作工具和提示词;首次发送前选择一个模板。
3
+ description: 使用 PPT 工作流,并在首次发送前选择模板。
4
+ order: 30
@@ -7,7 +7,7 @@ description: DSH 演示文稿:编写本地 PPTD 工程并输出可编辑 PPTX
7
7
 
8
8
  <!-- DSH-PPT-AUTHORING-20260907-V3 -->
9
9
 
10
- 本 Skill 仅由用户选中的 PPT 模式启用。
10
+ 本 Skill 仅由用户选中的 PPT 预设启用;预设一旦开始对话便保持不变。
11
11
 
12
12
  ## 工作过程
13
13