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 +3 -3
- package/README.zh.md +1 -1
- package/client.js +41 -0
- package/host/ptc-guard.js +114 -0
- package/host/teams.js +1 -0
- package/index.js +12 -1
- package/package.json +1 -1
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
|
|
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
|
-
- **派发池**——六张卡片两行三列:上排轻量、机械、主力,下排高难、多模态、复审。卡片按供应商分组勾候选模型;勾上「手动序」变成编号优先列表,用
|
|
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),
|
|
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.
|
|
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": [
|