pi-shadow-mind 0.1.21 → 0.1.22

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.
@@ -61,3 +61,9 @@
61
61
  - 发生:PR #11 与 issue #10 记录到同一用户任务内的反复整改:终检报告触发 Main 修订后,新的最终回复再次触发同一 Shadow,缺少停止条件;heartbeat 报告接力也会持续唤醒整改轮。
62
62
  - 分析:跳过由 shadow-report 触发的修复轮会失去对修订结果的复核,统一预算又涉及整个调度层、不适合放进单个 Shadow 定义。因此停止条件放在单个 Shadow 的完成检查上:整批审查进入终态后才提交轮次,被新输入、新报告或会话切换失效的检查不消耗配额;超时或错误的运行同样提交一次尝试,避免对同一用户任务重复重试。存在待交付的 heartbeat 报告时先交付已知问题,避免在旧文本上启动终检并浪费有限的验收轮次。缺省与 0 都表示不限制,维持现有默认行为。
63
63
  - 改变:新增 `final_response_rounds` frontmatter 字段与调度过滤;独立的 FinalResponseBudget 拥有轮次语义、计数与重置,Runtime 只接线 Session 生命周期与整批完成回调;CompletionReview 增加完成回调,FinalResponseQueue 增加启动门禁,batcher 有待交付报告时优先刷新。heartbeat 接力、跨 Shadow 统一预算与 stop 后不自动 follow-up 仍留在 issue #10。
64
+
65
+ ## 2026-09-15 · active_for_models 支持排除语法与模型短名
66
+
67
+ - 发生:用户希望仅对特定主力模型(如 `gpt-6-astra`)关闭某 Shadow,而对其余模型保持启用。
68
+ - 分析:此前仅支持全量 `*` 或显式枚举白名单,针对特定模型禁用必须穷举所有其他合法模型,配置维护成本高;应支持以 `!` 开头的排除规则,支持模型短名与 glob 通配,并在纯排除配置时默认全量模型候选。
69
+ - 改变:`matchesModel` 区分正向与否定规则;命中任一排除规则立即过滤;纯排除列表默认继承 `*`;补充短名、通配、格式破坏与调度过滤回归。
package/DESIGN.md CHANGED
@@ -111,7 +111,7 @@ frontmatter 包含以下运行字段:
111
111
  | `trigger` | 激活方式,可包含 `heartbeat`、`final_response` 或两者;默认 `[heartbeat]` |
112
112
  | `final_response_rounds` | 主 Agent 输出结束后的最大运行轮数。设为 `1` 时仅运行一次,避免反馈循环导致持续对话;缺省或 `0` 表示不限制 |
113
113
  | `activation_tools` | heartbeat 候选工具过滤;该 Main 轮次命中任意指定工具名即可,默认 `[]` 表示不限制;final_response 独立于此过滤 |
114
- | `active_for_models` | 适用于哪些 Main 模型;`"*"` 表示全部模型,省略时默认 `["*"]` |
114
+ | `active_for_models` | 适用于哪些 Main 模型;`"*"` 表示全部模型,省略时默认 `["*"]`;支持 `"!pattern"` 排除语法及通配符 |
115
115
  | `run_with_model` | Shadow 自己使用的模型;省略时使用插件默认模型 |
116
116
  | `thinking_level` | Shadow 使用的 thinking level;省略时使用插件默认值,再回退到 Main 会话当前生效等级 |
117
117
  | `timeout_seconds` | Shadow 单次运行超时;省略时使用插件默认超时 |
@@ -119,7 +119,7 @@ frontmatter 包含以下运行字段:
119
119
 
120
120
  `name` 只用于 `shadow-report` 和状态界面展示,不参与身份判断;省略时回退到最终解析出的 `id`。Markdown 正文就是 Shadow 的认知定义、长期职责和行为要求。
121
121
 
122
- `active_for_models` 绑定的是被观察的 Main 模型,`run_with_model` 则指定 Shadow 自己运行时使用的模型。匹配前由 Pi 将 Main 的别名或简写解析为完整 `provider/model-id`;模型过滤使用完整 ID 和精确值 `"*"` 进行匹配。模型选择优先级为:Shadow 的 `run_with_model` → 插件的 `default_shadow_model` → 激活时的当前 Main 模型。
122
+ `active_for_models` 绑定的是被观察的 Main 模型,`run_with_model` 则指定 Shadow 自己运行时使用的模型。匹配前由 Pi 将 Main 的别名或简写解析为完整 `provider/model-id`;模型过滤使用完整 ID、纯模型短名、精确值 `"*"` 及通配符进行匹配;支持以 `!` 开头的排除规则(如 `"!gpt-6-astra"` 或 `"!openai-codex/*"`),命中任一排除项即跳过该 Shadow;当列表仅包含排除规则时,默认正向范围为全量 `*`。模型选择优先级为:Shadow 的 `run_with_model` → 插件的 `default_shadow_model` → 激活时的当前 Main 模型。
123
123
 
124
124
  如果显式配置的 `run_with_model` 当前不存在、未认证或不可用,本次激活失败并写入轻量运行事件,不自动换用其他模型。只有省略该字段时才使用插件默认 Shadow 模型。
125
125
 
package/dist/index.js CHANGED
@@ -7826,7 +7826,49 @@ function decideFinalResponse(options) {
7826
7826
  };
7827
7827
  }
7828
7828
  function matchesModel(shadow, fullModelId) {
7829
- return shadow.activeForModels.includes("*") || shadow.activeForModels.includes(fullModelId);
7829
+ if (!shadow.activeForModels || shadow.activeForModels.length === 0) {
7830
+ return false;
7831
+ }
7832
+ const negatives = [];
7833
+ const positives = [];
7834
+ for (const item of shadow.activeForModels) {
7835
+ const trimmed = item.trim();
7836
+ if (!trimmed) continue;
7837
+ if (trimmed.startsWith("!")) {
7838
+ const pattern = trimmed.slice(1).trim();
7839
+ if (pattern) negatives.push(pattern);
7840
+ } else {
7841
+ positives.push(trimmed);
7842
+ }
7843
+ }
7844
+ for (const neg of negatives) {
7845
+ if (modelPatternMatches(neg, fullModelId)) {
7846
+ return false;
7847
+ }
7848
+ }
7849
+ if (positives.length === 0) {
7850
+ return negatives.length > 0;
7851
+ }
7852
+ return positives.some((pos) => modelPatternMatches(pos, fullModelId));
7853
+ }
7854
+ function modelPatternMatches(pattern, fullModelId) {
7855
+ if (pattern === "*") return true;
7856
+ if (pattern === fullModelId) return true;
7857
+ const slashIndex = fullModelId.indexOf("/");
7858
+ const modelId = slashIndex !== -1 ? fullModelId.slice(slashIndex + 1) : fullModelId;
7859
+ if (!pattern.includes("/") && pattern === modelId) {
7860
+ return true;
7861
+ }
7862
+ if (pattern.includes("*")) {
7863
+ const regex = wildcardToRegex(pattern);
7864
+ if (regex.test(fullModelId)) return true;
7865
+ if (!pattern.includes("/") && regex.test(modelId)) return true;
7866
+ }
7867
+ return false;
7868
+ }
7869
+ function wildcardToRegex(pattern) {
7870
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
7871
+ return new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
7830
7872
  }
7831
7873
  function matchesActivationTools(shadow, executedTools) {
7832
7874
  if (shadow.activationTools.length === 0) {
@@ -8919,10 +8961,7 @@ var ShadowMindRuntime = class {
8919
8961
  const fullModelId = `${ctx.model.provider}/${ctx.model.id}`;
8920
8962
  const decision = decideHeartbeat({
8921
8963
  heartbeatProbability: config.heartbeatProbability,
8922
- availableSlots: Math.max(
8923
- 0,
8924
- config.maxParallelShadows - this.active.size
8925
- ),
8964
+ availableSlots: Math.max(0, config.maxParallelShadows - this.active.size),
8926
8965
  shadows: snapshot.shadows,
8927
8966
  activeShadowIds: new Set(
8928
8967
  [...this.active.values()].map(({ shadow }) => shadow.id)