dsh-switchman 1.11.2 → 1.11.4

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
@@ -72,7 +72,7 @@ Worth installing even with a single model — watermark control and tiered verif
72
72
 
73
73
  - **Interface** — the plugin's own UI language. The dropdown in the shot sits at English — a live preview of switching; the shipped default is Auto (follows the DSH app language). Picking a language flips the whole page on the spot and is remembered after save; switch back to Auto anytime.
74
74
  - **Languages** — the languages of agent output. The scope in the shot is "Global (this profile)" (per-project works too, each reading `.switchman/lang.json`); the reply / code comments / documents dropdowns in the shot all sit at English, each displayed as "endonym (tag)" — e.g. "English (en)" — with a "current: …" status line beneath each. Leaving all three unset is fine too: asked once on first use, remembered from then on.
75
- - **Dispatch pools** — six cards in two rows of three: economy, mechanical, main on top; hard, vision, review below. Tick candidate models grouped by provider; tick "manual order" and a card becomes a numbered priority list reorderable with ↑ ↓ ×; every route can pin a thinking effort (default "follow lane"). The summary line at the top updates live — in the shot: "4/6 pools configured · 2 ranked · mode advice".
75
+ - **Dispatch pools** — six cards in two rows of three: economy, mechanical, main on top; hard, vision, review below. Tick candidate models grouped by provider; tick "manual order" and a card becomes a numbered priority list reorderable with ⤒ (Move to top), ↑, ↓, ×; every route can pin a thinking effort (default "follow lane"). The summary line at the top updates live — in the shot: "4/6 pools configured · 2 ranked · mode advice".
76
76
  - **Capability ranking + enforcement mode** — the union of selected models across the six pools, ranked by capability, strongest first (the shot anchors glm-5.3 at S and glm-5.3-flash at A), reorderable and removable; enforcement mode advice / force (force = out-of-pool models rejected outright).
77
77
  - **Agent teams** — the two toggles for team mode and whitelist sync, both off by default; in the shot they're on, with a sync status line beneath the toggle.
78
78
  - **Context watermark** — the three thresholds (80000 / 120000 / 150000 in the shot), the single-read budget (3000), hard-tier behavior (throttle-pass / block), the auto-handover toggle, and subagent independent caps all live here. Bottom command row: `/ctx-pause` pause intervention · `/ctx-resume` resume · `/ctx-handover` write the handover doc and compact now (it steers the session to an idle boundary before compacting — the result can take a few minutes).
@@ -154,7 +154,7 @@ All keys live in Settings → dsh-switchman; defaults from `host/config.js`:
154
154
 
155
155
  ## How it works
156
156
 
157
- - The host half (`index.js` + `host/`) injects the byte-stable system-prompt sections (language / lanes / watermark protocol / teams) plus the live watermark line through the tail runtime-context snapshot (a channel that appends instead of rewriting the prompt head, so the provider prompt cache stays intact), the read-budget plus enforce double gate, and four slash commands (the ctx trio + `/vision`). A saved setting takes effect at the very next prompt assembly — no restart.
157
+ - The host half (`index.js` + `host/`) injects the byte-stable system-prompt sections (language / lanes / watermark protocol / teams / PTC guard) plus the live watermark line through the tail runtime-context snapshot (a channel that appends instead of rewriting the prompt head, so the provider prompt cache stays intact), the read-budget plus enforce double gate, and four slash commands (the ctx trio + `/vision`). A saved setting takes effect at the very next prompt assembly — no restart. In PTC sessions (programmatic tool calls) the PTC guard renders as the final section and restates that every named tool must be invoked inside a `run_code` program (`await tools.name(args)`), making `unknown tool` rejections much less likely when doctrines or skill catalogs name tools directly (a prompt-level mitigation, not a hard guarantee).
158
158
  - The client half (`client.js`) renders the settings page, the ⚡ badge and ◇ model marker in the session header, and the live handover indicator; config snapshots are read and written through the Host route family.
159
159
  - The "Switchman Dispatch Center" entry in the home sidebar opens that same settings page as a center panel in one click; the original settings entry stays.
160
160
  - `cordis.patch.yml` keeps the factory preset plugin list fully intact and only extends the persona suffix; the Agent Teams tools themselves come from the factory bundle and switch on automatically with teams mode.
@@ -162,7 +162,7 @@ All keys live in Settings → dsh-switchman; defaults from `host/config.js`:
162
162
  ## Maintenance notes
163
163
 
164
164
  - After a DSH upgrade, if the factory preset plugin list changed, re-sync `cordis.patch.yml` from the new `presets/*.patch.yml` (keeping the doctrine suffix and the compaction arbitration — `auto: false` on the `compaction-basic` rows), then reinstall.
165
- - Protocol lines (`[SWITCHMAN:LANG|POOLS|WATERMARK|TEAMS]`) are deliberately English and byte-stable — don't localize them.
165
+ - Protocol lines (`[SWITCHMAN:LANG|POOLS|WATERMARK|TEAMS|PTC]`) are deliberately English and byte-stable — don't localize them.
166
166
  - `npm pack --dry-run` should keep the audited 60-file / ~380 kB tarball shape (~1.26 MB unpacked). `docs/` stays out of the package (it is not in `package.json files`), while the 11 `README.*` translations are always shipped by npm.
167
167
  - **Zero runtime dependencies — a hard invariant.** Host-plane packages (`@deepseek-ai/schemastery`, cordis, …) are declared as `peerDependencies` only, and the running DSH satisfies them from its own copy. An ordinary `dependencies` entry is physically installed into the profile workspace (`<profile>/node_modules/…`), where it shadows the host's copy for every other profile-resident plugin — this is exactly what crashed the dshmarket plugin market while 1.7.0 carried schemastery as a dependency (two schemastery instances in one process; the market's schemas no longer matched the host's). `scripts/validate.mjs` fails if a `dependencies` entry ever reappears, so installing, updating, or removing dsh-switchman can never touch what dshmarket or any other community bundle resolves at runtime.
168
168
 
package/README.zh.md CHANGED
@@ -72,7 +72,7 @@ dsh-switchman 是一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepse
72
72
 
73
73
  - **界面**——插件自己的界面语言。图中「自动」即跟随 DSH 应用语言;选具体语言则当场整页切换,保存后记住,随时切回「自动」。
74
74
  - **语言**——智能体产出物的语言。作用域图中为「全局(本 profile)」(也可按项目,各自读 `.switchman/lang.json`);回复 / 代码注释 / 文档三个下拉图中都定在简体中文,每项下方有「当前:…」状态行。全不设也行,首次用到问一次、记住即用。
75
- - **派发池**——六张卡片两行三列:上排轻量、机械、主力,下排高难、多模态、复审。卡片按供应商分组勾候选模型;勾上「手动序」变成编号优先列表,用 ↑ ↓ × 调顺序;每条路线可钉思考强度(默认「跟随泳道」)。顶部汇总行实时更新,图中即「已配置 4/6 池 · 排名 2 项 · 模式 建议」。
75
+ - **派发池**——六张卡片两行三列:上排轻量、机械、主力,下排高难、多模态、复审。卡片按供应商分组勾候选模型;勾上「手动序」变成编号优先列表,用 ⤒(一键登顶)、↑、↓、× 调顺序;每条路线可钉思考强度(默认「跟随泳道」)。顶部汇总行实时更新,图中即「已配置 4/6 池 · 排名 2 项 · 模式 建议」。
76
76
  - **能力排序 + 执行模式**——六池选中模型的并集按能力排序、最强在前(图中 glm-5.3 锚 S 档、glm-5.3-flash 锚 A 档),可调序、可删减;执行模式建议 / 强制(强制 = 池外模型直接拒绝)。
77
77
  - **智能体团队**——团队模式与白名单同步两个开关,默认全关;图中为开启状态,开关下方有同步状态行。
78
78
  - **上下文水位**——三档阈值(图中 80000 / 120000 / 150000)、单次读取预算(3000)、硬档行为(限流放行 / 拦截)、自动交接开关、子智能体独立上限,都在这区。底部一行命令:`/ctx-pause` 暂停干预 · `/ctx-resume` 恢复 · `/ctx-handover` 立即交接(它会把会话引导到空闲边界再压缩,结果可能要等几分钟)。
package/client.js CHANGED
@@ -845,6 +845,7 @@ window.__ModuleLoader__.load({
845
845
  '开启后按列表顺序派发(首个即首选);未开启按能力排序自动排。',
846
846
  poolManualSelected: '已选 · 派发优先序',
847
847
  poolManualAddable: '可添加',
848
+ poolMoveTop: '登顶',
848
849
  poolMoveUp: '上移',
849
850
  poolMoveDown: '下移',
850
851
  poolRemoveOne: '移除',
@@ -1113,6 +1114,7 @@ window.__ModuleLoader__.load({
1113
1114
  'When on, dispatch follows the listed order (head = first choice); when off, lanes are ordered by capability automatically.',
1114
1115
  poolManualSelected: 'Selected · dispatch priority',
1115
1116
  poolManualAddable: 'Add models',
1117
+ poolMoveTop: 'Move to top',
1116
1118
  poolMoveUp: 'Move up',
1117
1119
  poolMoveDown: 'Move down',
1118
1120
  poolRemoveOne: 'Remove',
@@ -1397,6 +1399,7 @@ window.__ModuleLoader__.load({
1397
1399
  poolManualHelp: '開啟後按清單順序派發(首個即首選);未開啟按能力排序自動排。',
1398
1400
  poolManualSelected: '已選 · 派發優先序',
1399
1401
  poolManualAddable: '可新增',
1402
+ poolMoveTop: '登頂',
1400
1403
  poolMoveUp: '上移',
1401
1404
  poolMoveDown: '下移',
1402
1405
  poolRemoveOne: '移除',
@@ -1644,6 +1647,7 @@ window.__ModuleLoader__.load({
1644
1647
  poolManualHelp: 'オンにするとリストの順序でディスパッチします(先頭=第一候補)。オフなら能力順で自動的に並びます。',
1645
1648
  poolManualSelected: '選択済み · ディスパッチ優先順',
1646
1649
  poolManualAddable: 'モデルを追加',
1650
+ poolMoveTop: '先頭へ',
1647
1651
  poolMoveUp: '上へ',
1648
1652
  poolMoveDown: '下へ',
1649
1653
  poolRemoveOne: '削除',
@@ -1875,6 +1879,7 @@ window.__ModuleLoader__.load({
1875
1879
  poolManualHelp: '켜면 목록 순서대로 디스패치합니다(맨 앞이 제1순위). 끄면 능력 순으로 자동 정렬됩니다.',
1876
1880
  poolManualSelected: '선택됨 · 디스패치 우선순위',
1877
1881
  poolManualAddable: '모델 추가',
1882
+ poolMoveTop: '맨 위로',
1878
1883
  poolMoveUp: '위로',
1879
1884
  poolMoveDown: '아래로',
1880
1885
  poolRemoveOne: '제거',
@@ -2105,6 +2110,7 @@ window.__ModuleLoader__.load({
2105
2110
  poolManualHelp: 'Ein: Der Versand folgt der gelisteten Reihenfolge (oben = erste Wahl); aus: Lanes werden automatisch nach Fähigkeit sortiert.',
2106
2111
  poolManualSelected: 'Ausgewählt · Dispatch-Priorität',
2107
2112
  poolManualAddable: 'Modelle hinzufügen',
2113
+ poolMoveTop: 'Ganz nach oben',
2108
2114
  poolMoveUp: 'Nach oben',
2109
2115
  poolMoveDown: 'Nach unten',
2110
2116
  poolRemoveOne: 'Entfernen',
@@ -2338,6 +2344,7 @@ window.__ModuleLoader__.load({
2338
2344
  poolManualHelp: 'Activado, el despacho sigue el orden de la lista (el primero es la primera opción); desactivado, los carriles se ordenan automáticamente por capacidad.',
2339
2345
  poolManualSelected: 'Seleccionados · prioridad de despacho',
2340
2346
  poolManualAddable: 'Añadir modelos',
2347
+ poolMoveTop: 'Mover al principio',
2341
2348
  poolMoveUp: 'Subir',
2342
2349
  poolMoveDown: 'Bajar',
2343
2350
  poolRemoveOne: 'Quitar',
@@ -2572,6 +2579,7 @@ window.__ModuleLoader__.load({
2572
2579
  poolManualHelp: 'Activé, le dispatch suit l’ordre affiché (la tête = premier choix) ; désactivé, les couloirs sont ordonnés par capacité automatiquement.',
2573
2580
  poolManualSelected: 'Sélectionnés · priorité de dispatch',
2574
2581
  poolManualAddable: 'Ajouter des modèles',
2582
+ poolMoveTop: 'Placer en tête',
2575
2583
  poolMoveUp: 'Monter',
2576
2584
  poolMoveDown: 'Descendre',
2577
2585
  poolRemoveOne: 'Retirer',
@@ -2806,6 +2814,7 @@ window.__ModuleLoader__.load({
2806
2814
  poolManualHelp: 'Attivo, il dispatch segue l’ordine elencato (il primo della lista = prima scelta); spento, le corsie vengono ordinate per capacità automaticamente.',
2807
2815
  poolManualSelected: 'Selezionati · priorità di dispatch',
2808
2816
  poolManualAddable: 'Aggiungi modelli',
2817
+ poolMoveTop: 'Porta in cima',
2809
2818
  poolMoveUp: 'Sposta su',
2810
2819
  poolMoveDown: 'Sposta giù',
2811
2820
  poolRemoveOne: 'Rimuovi',
@@ -3040,6 +3049,7 @@ window.__ModuleLoader__.load({
3040
3049
  poolManualHelp: 'Ligado, o despacho segue a ordem listada (o primeiro = primeira escolha); desligado, as faixas são ordenadas por capacidade automaticamente.',
3041
3050
  poolManualSelected: 'Selecionados · prioridade de despacho',
3042
3051
  poolManualAddable: 'Adicionar modelos',
3052
+ poolMoveTop: 'Mover para o topo',
3043
3053
  poolMoveUp: 'Mover para cima',
3044
3054
  poolMoveDown: 'Mover para baixo',
3045
3055
  poolRemoveOne: 'Remover',
@@ -3274,6 +3284,7 @@ window.__ModuleLoader__.load({
3274
3284
  poolManualHelp: 'Включено — диспетчеризация идёт по списку (голова списка = первый выбор); выключено — полосы упорядочиваются по силе автоматически.',
3275
3285
  poolManualSelected: 'Выбрано · приоритет диспетчеризации',
3276
3286
  poolManualAddable: 'Добавить модели',
3287
+ poolMoveTop: 'В начало',
3277
3288
  poolMoveUp: 'Выше',
3278
3289
  poolMoveDown: 'Ниже',
3279
3290
  poolRemoveOne: 'Убрать',
@@ -4613,6 +4624,7 @@ window.__ModuleLoader__.load({
4613
4624
  onToggleManual,
4614
4625
  onSetEffort,
4615
4626
  onMove,
4627
+ onMoveTop,
4616
4628
  onRemove,
4617
4629
  onSelectAll,
4618
4630
  onClear,
@@ -4774,6 +4786,20 @@ window.__ModuleLoader__.load({
4774
4786
  h(
4775
4787
  'div',
4776
4788
  { style: STYLE.rankActions },
4789
+ h(
4790
+ 'button',
4791
+ {
4792
+ type: 'button',
4793
+ onClick: () => {
4794
+ onMoveTop(index);
4795
+ },
4796
+ disabled: !editable || index === 0,
4797
+ title: t('poolMoveTop'),
4798
+ 'aria-label': t('poolMoveTop'),
4799
+ style: STYLE.iconButton,
4800
+ },
4801
+ '⤒',
4802
+ ),
4777
4803
  h(
4778
4804
  'button',
4779
4805
  {
@@ -5268,6 +5294,18 @@ window.__ModuleLoader__.load({
5268
5294
  })));
5269
5295
  }
5270
5296
 
5297
+ /** Manual order: move the route at index to the head of the
5298
+ * pool in one step (boundary-guarded: a no-op when already first). */
5299
+ function movePoolRouteToTop(field, index) {
5300
+ const pool = values[field];
5301
+ if (index <= 0 || index >= pool.length) return;
5302
+ const next = [pool[index], ...pool.slice(0, index), ...pool.slice(index + 1)];
5303
+ edit(field, next.map((route) => ({
5304
+ provider: route.provider,
5305
+ model: route.model,
5306
+ })));
5307
+ }
5308
+
5271
5309
  /** Manual order: remove the route at index. */
5272
5310
  function removePoolRoute(field, index) {
5273
5311
  const next = [...values[field]];
@@ -6383,6 +6421,9 @@ window.__ModuleLoader__.load({
6383
6421
  onMove: (index, delta) => {
6384
6422
  movePoolRoute(field, index, delta);
6385
6423
  },
6424
+ onMoveTop: (index) => {
6425
+ movePoolRouteToTop(field, index);
6426
+ },
6386
6427
  onRemove: (index) => {
6387
6428
  removePoolRoute(field, index);
6388
6429
  },
@@ -0,0 +1,114 @@
1
+ /** dsh-switchman PTC direct-call guard layer.
2
+ *
3
+ * Symptom (observed 2026-11-12, session-level): in PTC sessions the model
4
+ * calls a named tool DIRECTLY — e.g. `spawn_teammate` or `skill` after the
5
+ * teams doctrine or the skill catalog names it — and the harness rejects the
6
+ * whole call with `unknown tool "x": only \`run_code\` is callable directly`.
7
+ * The PTC executor's own `PTC_ONLY` section (order 800) states the rule once
8
+ * near the top, but every later doctrine that names tools directly erodes it.
9
+ *
10
+ * Second symptom (same day, session 22ced69f): the model sets `sandbox_permissions`
11
+ * on a call while the session already runs `danger-full-access`; the escalation
12
+ * request is rejected outright with `sandbox escalation to "workspace-write" is
13
+ * not strictly wider than this call's current "danger-full-access" mode` — an
14
+ * error with no correction hint. The clause below also states the never-set
15
+ * default and the retry-without-the-field fix.
16
+ *
17
+ * Fix: one extra dynamic systemPrompt section (order 10510 — AFTER every
18
+ * other dsh-switchman section: teams 10250, lang 10300, dispatch 10400,
19
+ * context-trim 10450, context-watch 10500) that re-states the transport rule
20
+ * as the LAST word whenever the viewing scope actually resolves to the PTC
21
+ * executor. Native scopes render an empty string (status quo).
22
+ *
23
+ * Detection mirrors the ToolsService's own per-scope resolution: the section
24
+ * text closure receives the assemble context, whose `scope` is the viewing
25
+ * Agent/preset scope key; `ctx.tools.selectExecutor(scope)` (TS-private but
26
+ * present at runtime — verified in the packaged app, `selectExecutor` survives
27
+ * bundling) resolves the nearest scoped executor or the deployment default.
28
+ * The executor is then discriminated BEHAVIORALLY via its public `describe()`:
29
+ * the PTC executor declares `knownNames: ["run_code"]` exactly; the native
30
+ * executor returns no `knownNames` at all. No class-name sniffing, no config
31
+ * reads, no false positives for other executor implementations (they simply
32
+ * get no guard).
33
+ *
34
+ * Fail-open everywhere: a missing/renamed private API, a throwing describe(),
35
+ * or an unavailable executor selection all render "" — the session keeps its
36
+ * current behavior and only loses the mitigation. Zero new dependencies.
37
+ *
38
+ * The clause is English, byte-stable protocol text (same convention as the
39
+ * lang layer) with a `[SWITCHMAN:PTC]` anchor, `interpolate: false` because
40
+ * the text contains braces.
41
+ */
42
+
43
+ /** Reserved transport-tool name the PTC executor declares as its only direct call. */
44
+ const RUN_CODE_NAME = "run_code";
45
+
46
+ /** Final-say section position: after context-watch (10500), the last existing section. */
47
+ const PTC_GUARD_ORDER = 10510;
48
+
49
+ /** True when an executor description declares exactly the reserved run_code transport. */
50
+ function isPtcDescription(description) {
51
+ if (description === null || typeof description !== "object") return false;
52
+ const known = description.knownNames;
53
+ return Array.isArray(known) && known.length === 1 && known[0] === RUN_CODE_NAME;
54
+ }
55
+
56
+ /** Selected executor for one assemble scope, or null when the private probe
57
+ * is unusable (missing service, missing/renamed method, unavailable selection). */
58
+ function selectedExecutor(ctx, scope) {
59
+ const tools = ctx?.tools;
60
+ if (tools === null || typeof tools !== "object") return null;
61
+ if (typeof tools.selectExecutor !== "function") return null;
62
+ try {
63
+ const executor = tools.selectExecutor(scope);
64
+ return executor === null || typeof executor !== "object" ? null : executor;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /** True when the scope's selected executor is the PTC executor. `describe([])`
71
+ * is safe for every known executor: the PTC implementation lazily creates its
72
+ * transport there (the ToolsService calls it on every model call anyway) and
73
+ * the native one maps over the empty list. */
74
+ function isPtcScope(ctx, scope) {
75
+ const executor = selectedExecutor(ctx, scope);
76
+ if (executor === null || typeof executor.describe !== "function") return false;
77
+ try {
78
+ return isPtcDescription(executor.describe([]));
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /** Byte-stable protocol clause. The SDK call shape `await tools.name(args)` is
85
+ * quoted verbatim from the run_code tool's own model-facing description. */
86
+ function renderPtcGuard() {
87
+ return [
88
+ `[SWITCHMAN:PTC] This session runs in PTC (programmatic tool call) mode: \`${RUN_CODE_NAME}\` is the ONLY tool callable directly.`,
89
+ `- Every other tool MUST be invoked as an awaited SDK function inside a ${RUN_CODE_NAME} program: \`await tools.name(args)\` — e.g. \`await tools.skill({ name: "git-commit-message" })\` or \`await tools.spawn_teammate({ ... })\`.`,
90
+ `- A direct call fails with: unknown tool "x": only \`${RUN_CODE_NAME}\` is callable directly — call \`x\` from inside a \`${RUN_CODE_NAME}\` program instead. Never retry the direct form; wrap the call.`,
91
+ `- Wherever this prompt says "call the X tool" (skill catalog, doctrines, per-turn reminders), read it as "call X inside a ${RUN_CODE_NAME} program". Batch independent calls as parallel awaits inside ONE program.`,
92
+ `- Never set \`sandbox_permissions\` on any call, direct or SDK: it is a privilege-escalation request and fails with "not strictly wider than this call's current ... mode" whenever the session already runs at least that access level — retry without the field.`,
93
+ ].join("\n");
94
+ }
95
+
96
+ /** Mount the PTC guard section. Section registration itself relies on the
97
+ * injected systemPrompt service (same as every other layer); the text closure
98
+ * is fully defensive so an assembly in an unexpected shape renders "". */
99
+ function applyPtcGuard(ctx) {
100
+ ctx.systemPrompt.section({
101
+ name: "switchman:ptc-guard",
102
+ order: PTC_GUARD_ORDER,
103
+ interpolate: false,
104
+ text: (context) => {
105
+ try {
106
+ return isPtcScope(ctx, context?.scope) ? renderPtcGuard() : "";
107
+ } catch {
108
+ return "";
109
+ }
110
+ },
111
+ });
112
+ }
113
+
114
+ export { applyPtcGuard, isPtcDescription, isPtcScope, renderPtcGuard, selectedExecutor };
package/host/teams.js CHANGED
@@ -86,6 +86,7 @@ const TEAMS_SECTION_TEXT = [
86
86
  "- teams 不可用即降级,不重试:工具目录没有 spawn_teammate/list_agents(bundle 未生效或本会话不支持),或 spawn_teammate 报 provisioning failed / 创建失败时,直接改用 subagent/subagent_fork 派发并继续工作,不要反复重试 teams 通道。你自身是 teammate(非 Lead)时维持原规则:不 spawn_teammate,需要子委派时用 subagent。workflow 仅在用户明确要求工作流或脚本化大规模编排时使用,默认不主动使用。",
87
87
  "- 写作域隔离:共享任务上写明互不重叠的 write_scopes;用 team_task_* 维护任务板与依赖顺序;Lead 汇总最终 diff、复核关键改动并运行测试;用 wait_agent 等到必需的 teammate 完成,绝不在结果未回前下最终结论。",
88
88
  "- 子智能体消息通道:subagent/workflow 返回的子会话 id 只能经 child_message 桥接送达——send_message 只解析 teammate 名字,对子会话 id 必报 active teammate「…」not found;该报错不代表子会话死亡,先改用 child_message,不要据此重派或判定死亡。",
89
+ "- run_code 并行隔离:spawn_teammate / send_message 这类会改变团队环境(成员目录、工具表)的调用不要与文件/搜索工具放进同一 run_code 程序里 Promise.all 并行——并发窗口内其它工具绑定可能误报 unknown tool「…」(UNKNOWN_TOOL);让这类调用单独占一步执行。某工具此前可用却突然报 UNKNOWN_TOOL 时,下一步原样重试即可,不要换工具名或放弃任务。",
89
90
  ].join("\n");
90
91
 
91
92
  // ---- shared volatile-read helpers (mirrors dispatch.js) --------------------
package/index.js CHANGED
@@ -51,6 +51,13 @@
51
51
  * defensive ctx.get reads (NOT declared in inject): a deployment without the
52
52
  * llm service leaves this layer a warn-once no-op — a documented runtime
53
53
  * precondition, like sessionQuery/pluginManager above.
54
+ * The PTC direct-call guard (host/ptc-guard.js) is a final-say prompt section
55
+ * (order 10510) rendered only when the viewing scope resolves to the PTC tool
56
+ * executor — detected per assembly via ctx.tools.selectExecutor(context.scope)
57
+ * plus the executor's describe() knownNames — restating that every named tool
58
+ * must be called inside a run_code program (`await tools.name(args)`), so
59
+ * doctrines and skill catalogs that name tools directly can no longer trigger
60
+ * `unknown tool` rejections in PTC sessions.
54
61
  */
55
62
 
56
63
  import { readdirSync, readFileSync } from "node:fs";
@@ -70,6 +77,8 @@ import { applyDispatch } from "./host/dispatch.js";
70
77
  // Agent Teams layer (teams-mode doctrine section + bundle/whitelist
71
78
  // orchestration; the clause text extracted from the cordis.patch.yml suffix).
72
79
  import { applyTeams } from "./host/teams.js";
80
+ // PTC direct-call guard (final-say transport rule for PTC-executor scopes).
81
+ import { applyPtcGuard } from "./host/ptc-guard.js";
73
82
  // Subagent model-visibility layer (post-execute `model:` annotation).
74
83
  import { applySubagentModel } from "./host/subagent-model.js";
75
84
 
@@ -197,7 +206,8 @@ function loadSkillCandidates(root, warn) {
197
206
  /** Register the bundled dsh-switchman skill provider on `ctx.skills` and
198
207
  * mount the language (lang), dispatch (dispatch), teams (teams), watermark
199
208
  * (context-watch), vision (vision), intent routing (intent), subagent model
200
- * visibility (subagent-model), and client-bridge (routes) layers. */
209
+ * visibility (subagent-model), client-bridge (routes), and the PTC
210
+ * direct-call guard (ptc-guard) layers. */
201
211
  function apply(ctx, config) {
202
212
  const root = fileURLToPath(new URL("./skills/", import.meta.url));
203
213
  const warn = (message) => ctx.logger.warn(`dsh-switchman: ${message}`);
@@ -214,6 +224,7 @@ function apply(ctx, config) {
214
224
  applyLanguage(ctx, config);
215
225
  applyDispatch(ctx, config);
216
226
  applyTeams(ctx, config);
227
+ applyPtcGuard(ctx);
217
228
  applyContextWatch(ctx, config);
218
229
  applyContextTrim(ctx, config);
219
230
  applyVision(ctx, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-switchman",
3
- "version": "1.11.2",
3
+ "version": "1.11.4",
4
4
  "description": "Autonomous Agent Teams doctrine for the four shipped DeepSeek Harness presets, plus language preferences, dispatch pools with model ranking, and context watermark control.",
5
5
  "license": "MIT",
6
6
  "keywords": [