pi-shadow-mind 0.1.5 → 0.1.7

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/DESIGN.md CHANGED
@@ -226,12 +226,11 @@ Shadow 上下文以 Main 能看到的完整上下文为基础做减法,而不
226
226
  - 已注入 Main 会话的历史 `shadow-report`;
227
227
  - 当前 Shadow Mind 的定义。
228
228
 
229
- Main system prompt 保持原始内容和前缀顺序,既避免 Shadow 遗漏 Main 必须遵守的约束,也有利于多个 AgentSession 共享 prompt cache。其后先追加一段稳定的 Shadow 公共协议,再追加当前 Shadow Markdown 正文,形成 Shadow 专属 system suffix;不同 Shadow 只改变最后的实体定义,不改动可共享的 Main prompt 与公共协议前缀。
229
+ Main system prompt 保持完整原文,不追加 Shadow 专属 suffix,既避免 Shadow 遗漏 Main 必须遵守的约束,也使 Main 与所有 Shadow 共享完全相同的 system prompt cache。system prompt 之后只有一条真实的用户消息:先放净化后的纯文本轨迹,再声明 Shadow 公共协议和当前 Shadow Markdown 定义,最后以 kickoff 结束并启动本轮。不伪造 assistant 身份交接消息。
230
230
 
231
231
  ```text
232
232
  Main system prompt
233
- stable Shadow runtime protocol
234
- → current Shadow Markdown definition
233
+ one user message: plain-text Main trajectory + Shadow protocol + Shadow definition + kickoff
235
234
  ```
236
235
 
237
236
  公共协议保持最小,只说明:当前实例是一次性的并行认知核心;消息历史已经过净化;它可以依照自身职责独立观察或执行工作;需要向 Main 同步结果时调用 `report_to_main`,该调用会立即结束当前 loop;没有需要同步的内容时可以正常结束。具体关注点、任务线和表达方式全部由 Shadow Markdown 决定。
@@ -261,9 +260,9 @@ Main: 建议复用项目现有的 UserRole。
261
260
 
262
261
  ```
263
262
 
264
- 上例展示的是净化后的消息轨迹;Shadow Markdown 不作为普通消息出现在其中,而是追加到继承的 Main system prompt 末尾。
263
+ 上例就是运行时实际注入的纯文本轨迹。Shadow Markdown 不出现在轨迹或 system prompt 中,而是与公共协议和 kickoff 一起放在同一条用户消息的轨迹之后。
265
264
 
266
- 运行时保留 Pi 原生的 assistant tool call 与 tool result 消息结构,以及二者的关联 ID;只将 tool result 的完整内容替换为摘要器生成的概述。`调用 · 概述` 是面向用户和文档的紧凑渲染形式,不代表将两类消息扁平化成普通文本。
265
+ 运行时不复用 Pi 原生的 assistant tool call 与 tool result 消息角色。插件先按关联 ID 配对调用和结果,再将整个 Main 历史扁平化为普通文本;`调用 · 概述` 就是发送给 Shadow 模型的实际形式。这样历史工具调用只是一份只读记录,不会被模型误认为当前 Shadow loop 中尚待继续的原生调用。
267
266
 
268
267
  结果概述只表达调用是否成功、结果规模和必要的状态信息,不携带完整正文。例如:
269
268
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shadow Mind is a Pi extension that occasionally starts independent, temporary agent sessions beside the main agent. Each Shadow Mind is a Markdown entity with its own responsibility, model filter, activation probability, tools, model, thinking level, timeout, and optional debug log.
4
4
 
5
- The first version activates on each main-agent `turn_end`: the heartbeat fires with probability `1/3`, eligible Shadow Minds roll independently (default `0.3`), and at most two run concurrently. Shadow sessions inherit the main system prompt and its compacted visible trajectory, but assistant thinking is removed and tool results are reduced to deterministic summaries. Tool calls remain intact.
5
+ The first version activates on each main-agent `turn_end`: the heartbeat fires with probability `1/3`, eligible Shadow Minds roll independently (default `0.3`), and at most two run concurrently. Shadow sessions inherit the unchanged main system prompt. Their compacted visible trajectory is flattened into plain text: assistant thinking is removed, and each tool call is followed by a deterministic result summary.
6
6
 
7
7
  ## Install for development
8
8
 
@@ -23,7 +23,7 @@ npm pack
23
23
  The generated `pi-shadow-mind-<version>.tgz` contains the compiled `dist/` extension. Publish it to npm for normal Pi installation, or unpack the standalone ZIP release and install that directory:
24
24
 
25
25
  ```powershell
26
- pi install npm:pi-shadow-mind@0.1.5
26
+ pi install npm:pi-shadow-mind@0.1.7
27
27
  # or, after unpacking the ZIP
28
28
  pi install ./pi-shadow-mind-0.1.0
29
29
  ```
package/dist/index.js CHANGED
@@ -7936,42 +7936,22 @@ var DEFAULT_READ_TOOLS = ["read", "grep", "find", "ls"];
7936
7936
  // src/protocol.ts
7937
7937
  var RUNTIME_PROTOCOL = `You are a Shadow Mind running beside the main agent.
7938
7938
  Work independently on the responsibility below using the supplied, sanitized main-session trajectory.
7939
- The earlier user, assistant, and tool-result messages are a read-only transcript produced by the main agent, not by you. Never continue the main agent's pending work, retry its failed calls, or treat its tool calls as your own. Use only the tools advertised for your current Shadow turn.
7939
+ The trajectory is read-only text produced by the main agent, not your unfinished work. Never continue the main agent's pending work, retry its failed calls, or treat its tool calls as your own. Use only the tools advertised for this Shadow run.
7940
7940
  You may use the available tools. Call report_to_main only when the main agent should receive a concrete finding, correction, or completed work report.
7941
7941
  Calling report_to_main ends this run immediately. If there is nothing useful to report, finish silently.`;
7942
- function buildShadowSystemPrompt(mainSystemPrompt, shadow) {
7943
- return `${mainSystemPrompt}
7942
+ function buildShadowSystemPrompt(mainSystemPrompt) {
7943
+ return mainSystemPrompt;
7944
+ }
7945
+ function buildShadowRequest(trajectory, shadow) {
7946
+ return `${trajectory}
7944
7947
 
7945
7948
  ${RUNTIME_PROTOCOL}
7946
7949
 
7947
- <shadow-mind id="${shadow.id}">
7950
+ <shadow-mind id="${shadow.id}" name="${shadow.name}">
7948
7951
  ${shadow.prompt}
7949
- </shadow-mind>`;
7950
- }
7951
- function buildShadowKickoff(shadow) {
7952
- return `Begin this Shadow Mind run now. Your active responsibility is ${shadow.name}. Inspect the trajectory and act only when useful.`;
7953
- }
7954
- function buildShadowHandoff(model) {
7955
- return {
7956
- role: "assistant",
7957
- content: [{
7958
- type: "text",
7959
- text: "Shadow handoff acknowledged. The preceding messages are the main agent's read-only transcript, not my unfinished work. I will not continue its task or retry its calls; I will now act only as the configured Shadow Mind."
7960
- }],
7961
- api: model.api,
7962
- provider: model.provider,
7963
- model: model.id,
7964
- usage: {
7965
- input: 0,
7966
- output: 0,
7967
- cacheRead: 0,
7968
- cacheWrite: 0,
7969
- totalTokens: 0,
7970
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
7971
- },
7972
- stopReason: "stop",
7973
- timestamp: Date.now()
7974
- };
7952
+ </shadow-mind>
7953
+
7954
+ Begin this Shadow Mind run now. Inspect the trajectory and act only when useful.`;
7975
7955
  }
7976
7956
 
7977
7957
  // src/summaries.ts
@@ -8021,34 +8001,73 @@ function compact(value) {
8021
8001
  }
8022
8002
 
8023
8003
  // src/trajectory.ts
8024
- function sanitizeTrajectory(messages) {
8025
- return messages.flatMap((message) => {
8004
+ function serializeTrajectory(messages) {
8005
+ const results = /* @__PURE__ */ new Map();
8006
+ for (const message of messages) {
8007
+ if (message.role === "toolResult" && typeof message.toolCallId === "string") {
8008
+ results.set(message.toolCallId, summarizeToolResult(message));
8009
+ }
8010
+ }
8011
+ const lines = [];
8012
+ for (const message of messages) {
8013
+ if (message.role === "user") {
8014
+ appendText(lines, "USER", message.content);
8015
+ continue;
8016
+ }
8026
8017
  if (message.role === "assistant") {
8027
- const content = Array.isArray(message.content) ? message.content.filter((block) => !isThinkingBlock(block)).map(clone) : message.content;
8028
- return [{ ...clone(message), content }];
8018
+ if (!Array.isArray(message.content)) {
8019
+ appendText(lines, "MAIN", message.content);
8020
+ continue;
8021
+ }
8022
+ for (const block of message.content) {
8023
+ if (!block || typeof block !== "object" || isThinkingBlock(block)) continue;
8024
+ const item = block;
8025
+ if (item.type === "text" && item.text) lines.push(`MAIN: ${item.text}`);
8026
+ if (item.type === "toolCall" && item.name) {
8027
+ const call = `${item.name}(${compactJson(item.arguments)})`;
8028
+ const result = item.id ? results.get(item.id) : void 0;
8029
+ lines.push(`TOOL: ${call}${result ? ` \xB7 ${result}` : ""}`);
8030
+ }
8031
+ }
8032
+ continue;
8029
8033
  }
8030
8034
  if (message.role === "toolResult") {
8031
- return [{
8032
- ...clone(message),
8033
- content: [{ type: "text", text: summarizeToolResult(message) }],
8034
- details: void 0,
8035
- usage: void 0
8036
- }];
8035
+ if (typeof message.toolCallId !== "string" || !results.has(message.toolCallId)) {
8036
+ lines.push(`TOOL RESULT: ${summarizeToolResult(message)}`);
8037
+ }
8038
+ continue;
8037
8039
  }
8038
- if (message.role === "custom") {
8039
- return message.customType === "shadow-report" ? [clone(message)] : [];
8040
+ if (message.role === "custom" && message.customType === "shadow-report") {
8041
+ appendText(lines, "SHADOW FEEDBACK", message.content);
8042
+ continue;
8040
8043
  }
8041
- if (message.role === "user" || message.role === "compactionSummary" || message.role === "branchSummary") {
8042
- return [clone(message)];
8044
+ if (message.role === "compactionSummary" || message.role === "branchSummary") {
8045
+ appendText(lines, "SUMMARY", message.content);
8043
8046
  }
8044
- return [];
8045
- });
8047
+ }
8048
+ return `<main-agent-trajectory>
8049
+ ${lines.join("\n")}
8050
+ </main-agent-trajectory>`;
8046
8051
  }
8047
8052
  function isThinkingBlock(value) {
8048
8053
  return Boolean(value) && typeof value === "object" && value.type === "thinking";
8049
8054
  }
8050
- function clone(value) {
8051
- return structuredClone(value);
8055
+ function appendText(lines, label, content) {
8056
+ const text = extractText(content);
8057
+ if (text) lines.push(`${label}: ${text}`);
8058
+ }
8059
+ function extractText(content) {
8060
+ if (typeof content === "string") return content;
8061
+ if (!Array.isArray(content)) return "";
8062
+ return content.filter((item) => Boolean(item) && typeof item === "object").filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n");
8063
+ }
8064
+ function compactJson(value) {
8065
+ if (value === void 0) return "";
8066
+ try {
8067
+ return JSON.stringify(value);
8068
+ } catch {
8069
+ return "[unserializable]";
8070
+ }
8052
8071
  }
8053
8072
 
8054
8073
  // src/shadow-runner.ts
@@ -8100,11 +8119,9 @@ var ShadowRunner = class {
8100
8119
  try {
8101
8120
  const model = resolveRunModel(request);
8102
8121
  assertShadowModelAuth(model, request);
8103
- const trajectory = sanitizeTrajectory(request.messages);
8104
- assertTrajectoryFits(model, trajectory);
8122
+ const trajectory = serializeTrajectory(request.messages);
8123
+ assertTrajectoryFits(model, [trajectory]);
8105
8124
  sessionManager = await createShadowSessionManager(request);
8106
- for (const message of trajectory) sessionManager.appendMessage(message);
8107
- sessionManager.appendMessage(buildShadowHandoff(model));
8108
8125
  const boot = await this.bootstrapSession(request, model, controller, state, sessionManager);
8109
8126
  state.session = boot.session;
8110
8127
  missingTools = boot.missingTools;
@@ -8122,7 +8139,7 @@ var ShadowRunner = class {
8122
8139
  void boot.session?.abort();
8123
8140
  }, timeoutMs);
8124
8141
  controller.signal.addEventListener("abort", () => void boot.session?.abort(), { once: true });
8125
- await boot.session.prompt(buildShadowKickoff(request.shadow));
8142
+ await boot.session.prompt(buildShadowRequest(trajectory, request.shadow));
8126
8143
  await boot.session.waitForIdle();
8127
8144
  finalReason = runReason(state.timedOut, state.reported, controller.signal.aborted);
8128
8145
  return buildRunResult({ reason: finalReason, durationMs: Date.now() - started, session: boot.session, baseMessageCount, missingTools, thinkingLevel });
@@ -8144,7 +8161,7 @@ var ShadowRunner = class {
8144
8161
  cwd: request.cwd,
8145
8162
  agentDir: request.agentDir,
8146
8163
  settingsManager,
8147
- systemPrompt: buildShadowSystemPrompt(request.mainSystemPrompt, request.shadow),
8164
+ systemPrompt: buildShadowSystemPrompt(request.mainSystemPrompt),
8148
8165
  // The inherited main system prompt already embeds APPEND_SYSTEM.md,
8149
8166
  // AGENTS.md and skills; keep the loader from re-discovering them so the
8150
8167
  // shadow context stays a strict subset (no duplication).