pi-shadow-mind 0.1.5 → 0.1.8
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 +5 -6
- package/README.md +2 -2
- package/dist/index.js +75 -55
- package/dist/index.js.map +2 -2
- package/dist/protocol.d.ts +2 -4
- package/dist/protocol.js +9 -28
- package/dist/protocol.js.map +1 -1
- package/dist/shadow-runner.js +6 -9
- package/dist/shadow-runner.js.map +1 -1
- package/dist/trajectory.d.ts +1 -0
- package/dist/trajectory.js +74 -0
- package/dist/trajectory.js.map +1 -1
- package/package.json +1 -1
package/DESIGN.md
CHANGED
|
@@ -226,15 +226,14 @@ Shadow 上下文以 Main 能看到的完整上下文为基础做减法,而不
|
|
|
226
226
|
- 已注入 Main 会话的历史 `shadow-report`;
|
|
227
227
|
- 当前 Shadow Mind 的定义。
|
|
228
228
|
|
|
229
|
-
Main system 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
|
-
→
|
|
234
|
-
→ current Shadow Markdown definition
|
|
233
|
+
→ one user message: plain-text Main trajectory + Shadow protocol + Shadow definition + kickoff
|
|
235
234
|
```
|
|
236
235
|
|
|
237
|
-
公共协议保持最小,只说明:当前实例是一次性的并行认知核心;消息历史已经过净化;它可以依照自身职责独立观察或执行工作;需要向 Main 同步结果时调用 `report_to_main`,该调用会立即结束当前 loop
|
|
236
|
+
公共协议保持最小,只说明:当前实例是一次性的并行认知核心;消息历史已经过净化;它可以依照自身职责独立观察或执行工作;需要向 Main 同步结果时调用 `report_to_main`,该调用会立即结束当前 loop;没有需要同步的内容时可以正常结束。kickoff 首先要求判断轨迹是否与当前 Shadow 职责相关:无关时只回复固定词 `NOT_RELEVANT` 并立即结束,不调用任何工具;相关时才继续执行职责。具体关注点、任务线和表达方式全部由 Shadow Markdown 决定。
|
|
238
237
|
|
|
239
238
|
轨迹范围覆盖当前 Main Session 从开始到激活时刻的全部历史,而不只包含当前用户 epoch。这样 Shadow 能看到早期用户约束和已经形成的会话决策。epoch 只用于判断 Shadow 的迟到结果能否介入,不参与轨迹裁剪。
|
|
240
239
|
|
|
@@ -261,9 +260,9 @@ Main: 建议复用项目现有的 UserRole。
|
|
|
261
260
|
|
|
262
261
|
```
|
|
263
262
|
|
|
264
|
-
|
|
263
|
+
上例就是运行时实际注入的纯文本轨迹。Shadow Markdown 不出现在轨迹或 system prompt 中,而是与公共协议和 kickoff 一起放在同一条用户消息的轨迹之后。
|
|
265
264
|
|
|
266
|
-
|
|
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
|
|
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.
|
|
26
|
+
pi install npm:pi-shadow-mind@0.1.8
|
|
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,25 @@ 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
|
|
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
|
-
Calling report_to_main ends this run immediately. If
|
|
7942
|
-
|
|
7943
|
-
|
|
7941
|
+
Calling report_to_main ends this run immediately. If relevant work produces nothing worth reporting, finish silently.`;
|
|
7942
|
+
var KICKOFF = `First decide whether the trajectory is relevant to this Shadow Mind's responsibility.
|
|
7943
|
+
If it is unrelated, reply exactly NOT_RELEVANT and stop immediately. Do not call any tool, including report_to_main.
|
|
7944
|
+
If it is relevant, perform the Shadow Mind's responsibility now. Call report_to_main only when the main agent should receive a result.`;
|
|
7945
|
+
function buildShadowSystemPrompt(mainSystemPrompt) {
|
|
7946
|
+
return mainSystemPrompt;
|
|
7947
|
+
}
|
|
7948
|
+
function buildShadowRequest(trajectory, shadow) {
|
|
7949
|
+
return `${trajectory}
|
|
7944
7950
|
|
|
7945
7951
|
${RUNTIME_PROTOCOL}
|
|
7946
7952
|
|
|
7947
|
-
<shadow-mind id="${shadow.id}">
|
|
7953
|
+
<shadow-mind id="${shadow.id}" name="${shadow.name}">
|
|
7948
7954
|
${shadow.prompt}
|
|
7949
|
-
</shadow-mind
|
|
7950
|
-
|
|
7951
|
-
|
|
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
|
-
};
|
|
7955
|
+
</shadow-mind>
|
|
7956
|
+
|
|
7957
|
+
${KICKOFF}`;
|
|
7975
7958
|
}
|
|
7976
7959
|
|
|
7977
7960
|
// src/summaries.ts
|
|
@@ -8021,34 +8004,73 @@ function compact(value) {
|
|
|
8021
8004
|
}
|
|
8022
8005
|
|
|
8023
8006
|
// src/trajectory.ts
|
|
8024
|
-
function
|
|
8025
|
-
|
|
8007
|
+
function serializeTrajectory(messages) {
|
|
8008
|
+
const results = /* @__PURE__ */ new Map();
|
|
8009
|
+
for (const message of messages) {
|
|
8010
|
+
if (message.role === "toolResult" && typeof message.toolCallId === "string") {
|
|
8011
|
+
results.set(message.toolCallId, summarizeToolResult(message));
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
const lines = [];
|
|
8015
|
+
for (const message of messages) {
|
|
8016
|
+
if (message.role === "user") {
|
|
8017
|
+
appendText(lines, "USER", message.content);
|
|
8018
|
+
continue;
|
|
8019
|
+
}
|
|
8026
8020
|
if (message.role === "assistant") {
|
|
8027
|
-
|
|
8028
|
-
|
|
8021
|
+
if (!Array.isArray(message.content)) {
|
|
8022
|
+
appendText(lines, "MAIN", message.content);
|
|
8023
|
+
continue;
|
|
8024
|
+
}
|
|
8025
|
+
for (const block of message.content) {
|
|
8026
|
+
if (!block || typeof block !== "object" || isThinkingBlock(block)) continue;
|
|
8027
|
+
const item = block;
|
|
8028
|
+
if (item.type === "text" && item.text) lines.push(`MAIN: ${item.text}`);
|
|
8029
|
+
if (item.type === "toolCall" && item.name) {
|
|
8030
|
+
const call = `${item.name}(${compactJson(item.arguments)})`;
|
|
8031
|
+
const result = item.id ? results.get(item.id) : void 0;
|
|
8032
|
+
lines.push(`TOOL: ${call}${result ? ` \xB7 ${result}` : ""}`);
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
continue;
|
|
8029
8036
|
}
|
|
8030
8037
|
if (message.role === "toolResult") {
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
usage: void 0
|
|
8036
|
-
}];
|
|
8038
|
+
if (typeof message.toolCallId !== "string" || !results.has(message.toolCallId)) {
|
|
8039
|
+
lines.push(`TOOL RESULT: ${summarizeToolResult(message)}`);
|
|
8040
|
+
}
|
|
8041
|
+
continue;
|
|
8037
8042
|
}
|
|
8038
|
-
if (message.role === "custom") {
|
|
8039
|
-
|
|
8043
|
+
if (message.role === "custom" && message.customType === "shadow-report") {
|
|
8044
|
+
appendText(lines, "SHADOW FEEDBACK", message.content);
|
|
8045
|
+
continue;
|
|
8040
8046
|
}
|
|
8041
|
-
if (message.role === "
|
|
8042
|
-
|
|
8047
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary") {
|
|
8048
|
+
appendText(lines, "SUMMARY", message.content);
|
|
8043
8049
|
}
|
|
8044
|
-
|
|
8045
|
-
|
|
8050
|
+
}
|
|
8051
|
+
return `<main-agent-trajectory>
|
|
8052
|
+
${lines.join("\n")}
|
|
8053
|
+
</main-agent-trajectory>`;
|
|
8046
8054
|
}
|
|
8047
8055
|
function isThinkingBlock(value) {
|
|
8048
8056
|
return Boolean(value) && typeof value === "object" && value.type === "thinking";
|
|
8049
8057
|
}
|
|
8050
|
-
function
|
|
8051
|
-
|
|
8058
|
+
function appendText(lines, label, content) {
|
|
8059
|
+
const text = extractText(content);
|
|
8060
|
+
if (text) lines.push(`${label}: ${text}`);
|
|
8061
|
+
}
|
|
8062
|
+
function extractText(content) {
|
|
8063
|
+
if (typeof content === "string") return content;
|
|
8064
|
+
if (!Array.isArray(content)) return "";
|
|
8065
|
+
return content.filter((item) => Boolean(item) && typeof item === "object").filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n");
|
|
8066
|
+
}
|
|
8067
|
+
function compactJson(value) {
|
|
8068
|
+
if (value === void 0) return "";
|
|
8069
|
+
try {
|
|
8070
|
+
return JSON.stringify(value);
|
|
8071
|
+
} catch {
|
|
8072
|
+
return "[unserializable]";
|
|
8073
|
+
}
|
|
8052
8074
|
}
|
|
8053
8075
|
|
|
8054
8076
|
// src/shadow-runner.ts
|
|
@@ -8100,11 +8122,9 @@ var ShadowRunner = class {
|
|
|
8100
8122
|
try {
|
|
8101
8123
|
const model = resolveRunModel(request);
|
|
8102
8124
|
assertShadowModelAuth(model, request);
|
|
8103
|
-
const trajectory =
|
|
8104
|
-
assertTrajectoryFits(model, trajectory);
|
|
8125
|
+
const trajectory = serializeTrajectory(request.messages);
|
|
8126
|
+
assertTrajectoryFits(model, [trajectory]);
|
|
8105
8127
|
sessionManager = await createShadowSessionManager(request);
|
|
8106
|
-
for (const message of trajectory) sessionManager.appendMessage(message);
|
|
8107
|
-
sessionManager.appendMessage(buildShadowHandoff(model));
|
|
8108
8128
|
const boot = await this.bootstrapSession(request, model, controller, state, sessionManager);
|
|
8109
8129
|
state.session = boot.session;
|
|
8110
8130
|
missingTools = boot.missingTools;
|
|
@@ -8122,7 +8142,7 @@ var ShadowRunner = class {
|
|
|
8122
8142
|
void boot.session?.abort();
|
|
8123
8143
|
}, timeoutMs);
|
|
8124
8144
|
controller.signal.addEventListener("abort", () => void boot.session?.abort(), { once: true });
|
|
8125
|
-
await boot.session.prompt(
|
|
8145
|
+
await boot.session.prompt(buildShadowRequest(trajectory, request.shadow));
|
|
8126
8146
|
await boot.session.waitForIdle();
|
|
8127
8147
|
finalReason = runReason(state.timedOut, state.reported, controller.signal.aborted);
|
|
8128
8148
|
return buildRunResult({ reason: finalReason, durationMs: Date.now() - started, session: boot.session, baseMessageCount, missingTools, thinkingLevel });
|
|
@@ -8144,7 +8164,7 @@ var ShadowRunner = class {
|
|
|
8144
8164
|
cwd: request.cwd,
|
|
8145
8165
|
agentDir: request.agentDir,
|
|
8146
8166
|
settingsManager,
|
|
8147
|
-
systemPrompt: buildShadowSystemPrompt(request.mainSystemPrompt
|
|
8167
|
+
systemPrompt: buildShadowSystemPrompt(request.mainSystemPrompt),
|
|
8148
8168
|
// The inherited main system prompt already embeds APPEND_SYSTEM.md,
|
|
8149
8169
|
// AGENTS.md and skills; keep the loader from re-discovering them so the
|
|
8150
8170
|
// shadow context stays a strict subset (no duplication).
|