@michengai/dsh-pua 0.3.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/CHANGELOG.md +152 -0
- package/LICENSE +201 -0
- package/NOTICE +38 -0
- package/README.md +150 -0
- package/assets/pua/command-again.md +23 -0
- package/assets/pua/command-done-check.md +21 -0
- package/assets/pua/command-evidence.md +18 -0
- package/assets/pua/flavors.md +388 -0
- package/assets/pua/methodology-alibaba.md +33 -0
- package/assets/pua/methodology-amazon.md +42 -0
- package/assets/pua/methodology-apple.md +42 -0
- package/assets/pua/methodology-baidu.md +33 -0
- package/assets/pua/methodology-bytedance.md +41 -0
- package/assets/pua/methodology-ding.md +75 -0
- package/assets/pua/methodology-huawei.md +95 -0
- package/assets/pua/methodology-jd.md +42 -0
- package/assets/pua/methodology-meituan.md +41 -0
- package/assets/pua/methodology-microsoft.md +138 -0
- package/assets/pua/methodology-netflix.md +41 -0
- package/assets/pua/methodology-pinduoduo.md +33 -0
- package/assets/pua/methodology-tencent.md +41 -0
- package/assets/pua/methodology-tesla.md +42 -0
- package/assets/pua/methodology-xiaomi.md +42 -0
- package/assets/pua/upstream/agents/cto-p10.md +87 -0
- package/assets/pua/upstream/agents/pua-action-executor.md +60 -0
- package/assets/pua/upstream/agents/pua-policy-guardian.md +54 -0
- package/assets/pua/upstream/agents/pua-self-reviewer.md +62 -0
- package/assets/pua/upstream/agents/pua-verifier.md +61 -0
- package/assets/pua/upstream/agents/senior-engineer-p7.md +116 -0
- package/assets/pua/upstream/agents/tech-lead-p9.md +97 -0
- package/assets/pua/upstream/commands/again.md +23 -0
- package/assets/pua/upstream/commands/cancel-pua-loop.md +62 -0
- package/assets/pua/upstream/commands/ding.md +25 -0
- package/assets/pua/upstream/commands/done-check.md +21 -0
- package/assets/pua/upstream/commands/evidence.md +18 -0
- package/assets/pua/upstream/commands/flavor.md +6 -0
- package/assets/pua/upstream/commands/kpi.md +5 -0
- package/assets/pua/upstream/commands/mama.md +5 -0
- package/assets/pua/upstream/commands/off.md +41 -0
- package/assets/pua/upstream/commands/offline.md +38 -0
- package/assets/pua/upstream/commands/on.md +15 -0
- package/assets/pua/upstream/commands/p10.md +5 -0
- package/assets/pua/upstream/commands/p7.md +5 -0
- package/assets/pua/upstream/commands/p9.md +5 -0
- package/assets/pua/upstream/commands/pro.md +5 -0
- package/assets/pua/upstream/commands/pua-loop.md +5 -0
- package/assets/pua/upstream/commands/pua.md +44 -0
- package/assets/pua/upstream/commands/reap-orphans.md +68 -0
- package/assets/pua/upstream/commands/survey.md +9 -0
- package/assets/pua/upstream/commands/team-status.md +56 -0
- package/assets/pua/upstream/commands/teardown-all.md +80 -0
- package/assets/pua/upstream/commands/yes.md +5 -0
- package/assets/pua/upstream/hooks/checkpoint-save.sh +56 -0
- package/assets/pua/upstream/hooks/failure-detector.sh +266 -0
- package/assets/pua/upstream/hooks/flavor-helper.sh +300 -0
- package/assets/pua/upstream/hooks/frustration-trigger.sh +61 -0
- package/assets/pua/upstream/hooks/hooks.json +114 -0
- package/assets/pua/upstream/hooks/integrity-guard.sh +494 -0
- package/assets/pua/upstream/hooks/pua-loop-hook.sh +360 -0
- package/assets/pua/upstream/hooks/runtime-state.py +460 -0
- package/assets/pua/upstream/hooks/sanitize-session.sh +165 -0
- package/assets/pua/upstream/hooks/session-restore.sh +189 -0
- package/assets/pua/upstream/hooks/stop-feedback.sh +51 -0
- package/assets/pua/upstream/hooks/subagent-teardown.sh +56 -0
- package/assets/pua/upstream/skills/ding/SKILL.md +83 -0
- package/assets/pua/upstream/skills/ding/references/ding-reminders.md +77 -0
- package/assets/pua/upstream/skills/ding/references/methodology-ding.md +75 -0
- package/assets/pua/upstream/skills/mama/SKILL.md +117 -0
- package/assets/pua/upstream/skills/p10/SKILL.md +13 -0
- package/assets/pua/upstream/skills/p7/SKILL.md +13 -0
- package/assets/pua/upstream/skills/p9/SKILL.md +15 -0
- package/assets/pua/upstream/skills/pro/SKILL.md +69 -0
- package/assets/pua/upstream/skills/pua/SKILL.md +438 -0
- package/assets/pua/upstream/skills/pua/references/agent-team.md +110 -0
- package/assets/pua/upstream/skills/pua/references/de-escalation-protocol.md +134 -0
- package/assets/pua/upstream/skills/pua/references/ding-reminders.md +77 -0
- package/assets/pua/upstream/skills/pua/references/display-protocol.md +63 -0
- package/assets/pua/upstream/skills/pua/references/evolution-protocol.md +187 -0
- package/assets/pua/upstream/skills/pua/references/flavors.md +388 -0
- package/assets/pua/upstream/skills/pua/references/harness-governance.md +159 -0
- package/assets/pua/upstream/skills/pua/references/methodology-alibaba.md +33 -0
- package/assets/pua/upstream/skills/pua/references/methodology-amazon.md +42 -0
- package/assets/pua/upstream/skills/pua/references/methodology-apple.md +42 -0
- package/assets/pua/upstream/skills/pua/references/methodology-baidu.md +33 -0
- package/assets/pua/upstream/skills/pua/references/methodology-bytedance.md +41 -0
- package/assets/pua/upstream/skills/pua/references/methodology-ding.md +75 -0
- package/assets/pua/upstream/skills/pua/references/methodology-huawei.md +95 -0
- package/assets/pua/upstream/skills/pua/references/methodology-jd.md +42 -0
- package/assets/pua/upstream/skills/pua/references/methodology-meituan.md +41 -0
- package/assets/pua/upstream/skills/pua/references/methodology-microsoft.md +138 -0
- package/assets/pua/upstream/skills/pua/references/methodology-netflix.md +41 -0
- package/assets/pua/upstream/skills/pua/references/methodology-pinduoduo.md +33 -0
- package/assets/pua/upstream/skills/pua/references/methodology-router.md +81 -0
- package/assets/pua/upstream/skills/pua/references/methodology-tencent.md +41 -0
- package/assets/pua/upstream/skills/pua/references/methodology-tesla.md +42 -0
- package/assets/pua/upstream/skills/pua/references/methodology-xiaomi.md +42 -0
- package/assets/pua/upstream/skills/pua/references/p10-protocol.md +127 -0
- package/assets/pua/upstream/skills/pua/references/p7-protocol.md +250 -0
- package/assets/pua/upstream/skills/pua/references/p9-protocol.md +266 -0
- package/assets/pua/upstream/skills/pua/references/platform.md +126 -0
- package/assets/pua/upstream/skills/pua/references/runtime-contract.md +65 -0
- package/assets/pua/upstream/skills/pua/references/survey.md +292 -0
- package/assets/pua/upstream/skills/pua/references/teardown-protocol.md +195 -0
- package/assets/pua/upstream/skills/pua-en/SKILL.md +344 -0
- package/assets/pua/upstream/skills/pua-ja/SKILL.md +378 -0
- package/assets/pua/upstream/skills/pua-loop/SKILL.md +162 -0
- package/assets/pua/upstream/skills/shot/SKILL.md +449 -0
- package/assets/pua/upstream/skills/yes/SKILL.md +76 -0
- package/assets/pua/upstream.json +637 -0
- package/assets/screenshots/pua-global-settings.png +0 -0
- package/assets/screenshots/pua-session-settings.png +0 -0
- package/cordis.patch.yml +5 -0
- package/lib/args.d.ts +34 -0
- package/lib/args.js +149 -0
- package/lib/args.js.map +1 -0
- package/lib/client-refresh.d.ts +6 -0
- package/lib/client-refresh.js +41 -0
- package/lib/client-refresh.js.map +1 -0
- package/lib/client.d.ts +30 -0
- package/lib/client.js +68 -0
- package/lib/client.js.map +7 -0
- package/lib/command.d.ts +18 -0
- package/lib/command.js +118 -0
- package/lib/command.js.map +1 -0
- package/lib/configuration.d.ts +89 -0
- package/lib/configuration.js +31 -0
- package/lib/configuration.js.map +1 -0
- package/lib/content.d.ts +14 -0
- package/lib/content.js +51 -0
- package/lib/content.js.map +1 -0
- package/lib/flavors.d.ts +82 -0
- package/lib/flavors.js +30 -0
- package/lib/flavors.js.map +1 -0
- package/lib/hook-content.d.ts +14 -0
- package/lib/hook-content.js +63 -0
- package/lib/hook-content.js.map +1 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +75 -0
- package/lib/index.js.map +1 -0
- package/lib/remote-contract.d.ts +148 -0
- package/lib/remote-contract.js +20 -0
- package/lib/remote-contract.js.map +1 -0
- package/lib/remote.d.ts +20 -0
- package/lib/remote.js +133 -0
- package/lib/remote.js.map +1 -0
- package/lib/review.d.ts +4 -0
- package/lib/review.js +72 -0
- package/lib/review.js.map +1 -0
- package/lib/runtime.d.ts +61 -0
- package/lib/runtime.js +519 -0
- package/lib/runtime.js.map +1 -0
- package/lib/session-compat.d.ts +3 -0
- package/lib/session-compat.js +9 -0
- package/lib/session-compat.js.map +1 -0
- package/lib/settings.d.ts +46 -0
- package/lib/settings.js +44 -0
- package/lib/settings.js.map +1 -0
- package/lib/source.d.ts +10 -0
- package/lib/source.js +32 -0
- package/lib/source.js.map +1 -0
- package/lib/state.d.ts +36 -0
- package/lib/state.js +187 -0
- package/lib/state.js.map +1 -0
- package/lib/terminal-observation.d.ts +4 -0
- package/lib/terminal-observation.js +12 -0
- package/lib/terminal-observation.js.map +1 -0
- package/lib/tool-order.d.ts +9 -0
- package/lib/tool-order.js +82 -0
- package/lib/tool-order.js.map +1 -0
- package/package.json +172 -0
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Session, UserMessage } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import type { ToolExecutionResult } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import type { SubprocessRuntime } from "@deepseek-ai/dsh-subprocess";
|
|
5
|
+
import type { Action } from "./args.js";
|
|
6
|
+
import { SourceCatalog } from "./source.js";
|
|
7
|
+
import { StateStore } from "./state.js";
|
|
8
|
+
import type { PuaMode } from "./content.js";
|
|
9
|
+
export declare const LOOP_START = "PUA_LOOP_START ";
|
|
10
|
+
type LoopAction = Extract<Action, {
|
|
11
|
+
kind: "loop";
|
|
12
|
+
}>;
|
|
13
|
+
interface LoopState extends LoopAction {
|
|
14
|
+
id: string;
|
|
15
|
+
iteration: number;
|
|
16
|
+
rejections: number;
|
|
17
|
+
status: "active" | "paused" | "cancelled" | "complete" | "max_reached";
|
|
18
|
+
}
|
|
19
|
+
interface RuntimeState {
|
|
20
|
+
failureCount: number;
|
|
21
|
+
failures: string[];
|
|
22
|
+
feedbackCount?: number;
|
|
23
|
+
candidateCount?: number;
|
|
24
|
+
loop?: LoopState;
|
|
25
|
+
}
|
|
26
|
+
export declare function pluginMessage(text: string): UserMessage;
|
|
27
|
+
/** 与原版一样,只接受终端结果的直接结构字段,不从 stdout 或嵌套 JSON 猜失败。 */
|
|
28
|
+
export declare function isTerminalFailure(result: Readonly<ToolExecutionResult>): boolean;
|
|
29
|
+
/** 独立验收执行用户提供的命令,120 秒超时;输出只作为证据,不作为指令。 */
|
|
30
|
+
export declare function verifyLoop(subprocess: Pick<SubprocessRuntime, "spawn">, command: string, cwd: string, signal: AbortSignal, timeoutMs?: number): Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
detail: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** 生命周期移植;完整状态留在日志,通过宿主 surface 替换只向模型提供说明。 */
|
|
35
|
+
export declare class PuaRuntime {
|
|
36
|
+
private readonly ctx;
|
|
37
|
+
private readonly store;
|
|
38
|
+
private readonly lifetime;
|
|
39
|
+
private readonly hooks;
|
|
40
|
+
private readonly activeVerifiers;
|
|
41
|
+
private readonly sessions;
|
|
42
|
+
private readonly cache;
|
|
43
|
+
private readonly normalized;
|
|
44
|
+
private readonly pendingWrites;
|
|
45
|
+
private readonly pendingCandidates;
|
|
46
|
+
private readonly pendingTerminalReviews;
|
|
47
|
+
constructor(ctx: Context, store: StateStore, catalog: SourceCatalog, lifetime: AbortSignal, feedback?: () => {
|
|
48
|
+
offline: boolean;
|
|
49
|
+
frequency: number;
|
|
50
|
+
});
|
|
51
|
+
read(session: Session): RuntimeState;
|
|
52
|
+
private save;
|
|
53
|
+
private flush;
|
|
54
|
+
private hideLegacyRecords;
|
|
55
|
+
cancel(session: Session, persist?: boolean): void;
|
|
56
|
+
cancelAll(): number;
|
|
57
|
+
status(session: Session): string;
|
|
58
|
+
effectiveMode(session: Session, mode: PuaMode): PuaMode;
|
|
59
|
+
private stopping;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
package/lib/runtime.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { HookContent } from "./hook-content.js";
|
|
4
|
+
import { SourceCatalog } from "./source.js";
|
|
5
|
+
import { StateStore } from "./state.js";
|
|
6
|
+
import { hasPendingToolCalls, repairPuaToolOrder } from "./tool-order.js";
|
|
7
|
+
import { replacementSurface } from "./session-compat.js";
|
|
8
|
+
import { terminalTextNeedsReview, TERMINAL_REVIEW_PROMPT } from "./terminal-observation.js";
|
|
9
|
+
const RUNTIME_SOURCE = "@michengai/dsh-pua/runtime";
|
|
10
|
+
const RECORD = "PUA_RUNTIME_V1 ";
|
|
11
|
+
export const LOOP_START = "PUA_LOOP_START ";
|
|
12
|
+
export function pluginMessage(text) {
|
|
13
|
+
return createUserMessage({
|
|
14
|
+
content: [{ type: "text", text }],
|
|
15
|
+
source: { kind: "plugin", plugin: RUNTIME_SOURCE },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const messageText = (message) => message.content
|
|
19
|
+
.filter((block) => block.type === "text")
|
|
20
|
+
.map((block) => block.text ?? "")
|
|
21
|
+
.join("\n");
|
|
22
|
+
/** 与原版一样,只接受终端结果的直接结构字段,不从 stdout 或嵌套 JSON 猜失败。 */
|
|
23
|
+
export function isTerminalFailure(result) {
|
|
24
|
+
if (result.isError)
|
|
25
|
+
return !!result.error.info?.code && !/ABORT|DENIED|PERMISSION|EPERM|EACCES|UNKNOWN_TOOL|BLOCKED|APPROVAL/u.test(result.error.info?.code ?? "");
|
|
26
|
+
const value = result.value;
|
|
27
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
28
|
+
return false;
|
|
29
|
+
return (value.is_error === true ||
|
|
30
|
+
["exitCode", "exit_code"].some((key) => typeof value[key] === "number" && value[key] !== 0));
|
|
31
|
+
}
|
|
32
|
+
/** 独立验收执行用户提供的命令,120 秒超时;输出只作为证据,不作为指令。 */
|
|
33
|
+
export async function verifyLoop(subprocess, command, cwd, signal, timeoutMs = 120_000) {
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
const timeout = new AbortController();
|
|
36
|
+
const timer = setTimeout(() => timeout.abort(), timeoutMs);
|
|
37
|
+
try {
|
|
38
|
+
const combined = AbortSignal.any([signal, timeout.signal]);
|
|
39
|
+
// 脚本是 argv 的单个参数,由宿主 spawn(program, args) 传递,无外层 shell 字符串转义。
|
|
40
|
+
const argv = process.platform === "win32"
|
|
41
|
+
? [
|
|
42
|
+
"powershell.exe",
|
|
43
|
+
"-NoLogo",
|
|
44
|
+
"-NoProfile",
|
|
45
|
+
"-NonInteractive",
|
|
46
|
+
"-Command",
|
|
47
|
+
`[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n$OutputEncoding = [System.Text.Encoding]::UTF8\n$ErrorActionPreference = 'Stop'\n${command}\nif (-not $?) { if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; exit 1 }; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }`,
|
|
48
|
+
]
|
|
49
|
+
: ["/bin/sh", "-c", command];
|
|
50
|
+
const handle = subprocess.spawn({
|
|
51
|
+
argv,
|
|
52
|
+
cwd,
|
|
53
|
+
signal: combined,
|
|
54
|
+
graceMs: 1000,
|
|
55
|
+
stdio: {
|
|
56
|
+
stdin: "ignore",
|
|
57
|
+
stdout: { maxBytes: 8192 },
|
|
58
|
+
stderr: { maxBytes: 8192 },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const result = await handle.done;
|
|
62
|
+
signal.throwIfAborted();
|
|
63
|
+
const stdout = handle.collected.stdout?.readFrom(0);
|
|
64
|
+
const stderr = handle.collected.stderr?.readFrom(0);
|
|
65
|
+
return {
|
|
66
|
+
ok: !timeout.signal.aborted && result.exitCode === 0 && !result.signal,
|
|
67
|
+
detail: JSON.stringify({
|
|
68
|
+
exitCode: result.exitCode,
|
|
69
|
+
timedOut: timeout.signal.aborted,
|
|
70
|
+
stdout: stdout?.text,
|
|
71
|
+
stderr: stderr?.text,
|
|
72
|
+
truncated: stdout?.lossy || stderr?.lossy || false,
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
detail: timeout.signal.aborted
|
|
81
|
+
? "独立验收超时。"
|
|
82
|
+
: `独立验收未执行成功:${error instanceof Error ? error.message : String(error)}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** 生命周期移植;完整状态留在日志,通过宿主 surface 替换只向模型提供说明。 */
|
|
90
|
+
export class PuaRuntime {
|
|
91
|
+
ctx;
|
|
92
|
+
store;
|
|
93
|
+
lifetime;
|
|
94
|
+
hooks;
|
|
95
|
+
activeVerifiers = new Map();
|
|
96
|
+
sessions = new Set();
|
|
97
|
+
cache = new WeakMap();
|
|
98
|
+
normalized = new WeakSet();
|
|
99
|
+
pendingWrites = new Map();
|
|
100
|
+
pendingCandidates = new WeakSet();
|
|
101
|
+
pendingTerminalReviews = new WeakSet();
|
|
102
|
+
constructor(ctx, store, catalog, lifetime, feedback = () => ({
|
|
103
|
+
offline: false,
|
|
104
|
+
frequency: 5,
|
|
105
|
+
})) {
|
|
106
|
+
this.ctx = ctx;
|
|
107
|
+
this.store = store;
|
|
108
|
+
this.lifetime = lifetime;
|
|
109
|
+
this.hooks = new HookContent(catalog);
|
|
110
|
+
ctx.on("agent/pre-step", async ({ agent }, next) => {
|
|
111
|
+
const decision = await next();
|
|
112
|
+
if (decision.kind !== "enter")
|
|
113
|
+
return decision;
|
|
114
|
+
this.hideLegacyRecords(agent.session);
|
|
115
|
+
this.flush(agent.session);
|
|
116
|
+
const state = store.read(agent.session);
|
|
117
|
+
const runtime = this.read(agent.session);
|
|
118
|
+
if (this.pendingTerminalReviews.has(agent.session) && !hasPendingToolCalls(agent.session)) {
|
|
119
|
+
this.pendingTerminalReviews.delete(agent.session);
|
|
120
|
+
if (state.enabled && state.terminalReview)
|
|
121
|
+
decision.messages.push(pluginMessage(TERMINAL_REVIEW_PROMPT));
|
|
122
|
+
}
|
|
123
|
+
if (this.pendingCandidates.has(agent.session) && !hasPendingToolCalls(agent.session)) {
|
|
124
|
+
this.pendingCandidates.delete(agent.session);
|
|
125
|
+
let prompt = '';
|
|
126
|
+
if (state.enabled && state.failureCandidates && (runtime.candidateCount ?? 0) < 4) {
|
|
127
|
+
try {
|
|
128
|
+
prompt = this.hooks.candidate(runtime.failureCount, state);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
ctx.logger.warn('PUA 候选素材不可用,跳过本次提示:%s', error);
|
|
132
|
+
}
|
|
133
|
+
if (prompt)
|
|
134
|
+
runtime.candidateCount = (runtime.candidateCount ?? 0) + 1;
|
|
135
|
+
}
|
|
136
|
+
if (prompt)
|
|
137
|
+
decision.messages.push(pluginMessage(prompt));
|
|
138
|
+
}
|
|
139
|
+
if (!state.enabled || state.mode !== "pua-loop")
|
|
140
|
+
this.cancel(agent.session);
|
|
141
|
+
for (const message of decision.messages) {
|
|
142
|
+
const text = messageText(message);
|
|
143
|
+
if (message.source.kind === "plugin" &&
|
|
144
|
+
message.source.plugin === "@michengai/dsh-pua" &&
|
|
145
|
+
text.startsWith(LOOP_START) &&
|
|
146
|
+
state.enabled &&
|
|
147
|
+
state.mode === "pua-loop") {
|
|
148
|
+
const config = JSON.parse(text.slice(LOOP_START.length).split("\n")[0]);
|
|
149
|
+
if (config.id !== state.loopCommandId)
|
|
150
|
+
continue;
|
|
151
|
+
runtime.loop = {
|
|
152
|
+
...config,
|
|
153
|
+
iteration: 1,
|
|
154
|
+
rejections: 0,
|
|
155
|
+
status: "active",
|
|
156
|
+
};
|
|
157
|
+
this.save(agent.session, runtime, "显式启动 Loop;验证配置以用户命令为准。");
|
|
158
|
+
}
|
|
159
|
+
else if (message.source.kind === "user" && state.enabled) {
|
|
160
|
+
if (runtime.loop?.status === "paused" && state.mode === "pua-loop") {
|
|
161
|
+
runtime.loop = { ...runtime.loop, status: "active" };
|
|
162
|
+
this.save(agent.session, runtime, "用户补充输入,恢复同会话 Loop。");
|
|
163
|
+
}
|
|
164
|
+
if (state.qualityTriggers && this.hooks.trigger.test(text))
|
|
165
|
+
decision.messages.push(pluginMessage(this.hooks.frustrationPrompt(state)));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return decision;
|
|
169
|
+
});
|
|
170
|
+
ctx.inject(["tools"], (child) => {
|
|
171
|
+
child.tools.register({
|
|
172
|
+
name: "pua_reference",
|
|
173
|
+
description: "读取已安装的 PUA 3.5.1 完整原版资料。path=list 列目录;其他值必须为目录中 .md 路径。",
|
|
174
|
+
parameters: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: { path: { type: "string" } },
|
|
177
|
+
required: ["path"],
|
|
178
|
+
additionalProperties: false,
|
|
179
|
+
},
|
|
180
|
+
output: {
|
|
181
|
+
schema: { type: "string" },
|
|
182
|
+
render: (_args, value) => [{ type: "text", text: String(value) }],
|
|
183
|
+
},
|
|
184
|
+
isConcurrencySafe: () => true,
|
|
185
|
+
execute: async (args, exec) => {
|
|
186
|
+
exec.signal.throwIfAborted();
|
|
187
|
+
if (!args ||
|
|
188
|
+
typeof args !== "object" ||
|
|
189
|
+
!("path" in args) ||
|
|
190
|
+
typeof args.path !== "string")
|
|
191
|
+
throw new Error("path 必须为资料路径或 list。");
|
|
192
|
+
if (args.path === "list")
|
|
193
|
+
return catalog.list().join("\n");
|
|
194
|
+
if (!catalog.list().includes(args.path))
|
|
195
|
+
throw new Error("仅允许读取已收录的 Markdown 资料。");
|
|
196
|
+
return catalog.read(args.path);
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
child.on("tools/result", (exec, result) => {
|
|
200
|
+
const agent = exec.agent;
|
|
201
|
+
if (!agent || exec.signal.aborted || !store.read(agent.session).enabled)
|
|
202
|
+
return;
|
|
203
|
+
const tool = child.tools.get(exec.name, agent);
|
|
204
|
+
const terminal = /^(Bash|bash|terminal|exec_command|shell|shell_command|run_terminal_cmd)$/u.test(exec.name) || tool?.presentCall?.(exec.arguments)?.card === "terminal";
|
|
205
|
+
if (!terminal)
|
|
206
|
+
return;
|
|
207
|
+
if (!isTerminalFailure(result)) {
|
|
208
|
+
// 拒绝、取消及无结构化错误码的宿主故障不是成功,不清零已有观察。
|
|
209
|
+
if (result.isError)
|
|
210
|
+
return;
|
|
211
|
+
if (terminalTextNeedsReview(result))
|
|
212
|
+
this.pendingTerminalReviews.add(agent.session);
|
|
213
|
+
else {
|
|
214
|
+
// 成功打断连续失败观察;不会撤销独立验收结果。
|
|
215
|
+
const observation = this.read(agent.session);
|
|
216
|
+
if (observation.failureCount)
|
|
217
|
+
this.pendingWrites.set(agent.session, '终端成功,清除连续失败观察。');
|
|
218
|
+
observation.failureCount = 0;
|
|
219
|
+
this.pendingCandidates.delete(agent.session);
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const runtime = this.read(agent.session);
|
|
224
|
+
const id = createHash("sha256").update(exec.callId).digest("hex");
|
|
225
|
+
if (runtime.failures.includes(id))
|
|
226
|
+
return;
|
|
227
|
+
runtime.failures = [...runtime.failures, id].slice(-128);
|
|
228
|
+
runtime.failureCount = Math.min(runtime.failureCount + 1, 1_000_000);
|
|
229
|
+
// 此通知早于宿主 tool/result 落库;只暂存,不能在工具组中插入普通消息。
|
|
230
|
+
this.pendingWrites.set(agent.session, "新增终端失败观察;不是任务失败判定。");
|
|
231
|
+
this.pendingCandidates.add(agent.session);
|
|
232
|
+
return;
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
ctx.on("agent/turn-stopping", async (payload) => {
|
|
236
|
+
this.flush(payload.agent.session);
|
|
237
|
+
const wasLoop = this.read(payload.agent.session).loop?.status === "active";
|
|
238
|
+
await this.stopping(payload.agent, payload.turn, payload.signal);
|
|
239
|
+
const session = payload.agent.session;
|
|
240
|
+
const preferences = store.read(session);
|
|
241
|
+
const config = { offline: preferences.offline, frequency: preferences.feedbackFrequency };
|
|
242
|
+
if (wasLoop ||
|
|
243
|
+
!store.read(session).enabled ||
|
|
244
|
+
config.offline ||
|
|
245
|
+
config.frequency === 0 ||
|
|
246
|
+
session.header.parentSession)
|
|
247
|
+
return;
|
|
248
|
+
const visible = [...session.ownEvents()].some((event) => event.type === "assistant/message" &&
|
|
249
|
+
event.data.turn === payload.turn &&
|
|
250
|
+
!event.data.interrupted &&
|
|
251
|
+
/PUA生效|\[Auto-select:|\[PIP-REPORT\]|\[PUA-REPORT\]|\[PUA-DIAGNOSIS\]/u.test(messageText(event.data.message)));
|
|
252
|
+
if (!visible)
|
|
253
|
+
return;
|
|
254
|
+
const state = this.read(session);
|
|
255
|
+
state.feedbackCount = (state.feedbackCount ?? 0) + 1;
|
|
256
|
+
const note = state.feedbackCount % config.frequency === 0
|
|
257
|
+
? "PUA 本地反馈(自愿):如需记录本次效果,可运行 /pua survey quick。评分只写本机 ~/.pua/feedback.jsonl;跳过不记录,不阻断交付,不上传。"
|
|
258
|
+
: "记录一次有可见 PUA 输出的交付,不记录评分。";
|
|
259
|
+
this.save(session, state, note);
|
|
260
|
+
});
|
|
261
|
+
ctx.on("agent/session-start", ({ agent, source }) => {
|
|
262
|
+
if (source === "clear") {
|
|
263
|
+
this.pendingCandidates.delete(agent.session);
|
|
264
|
+
this.pendingTerminalReviews.delete(agent.session);
|
|
265
|
+
this.cancel(agent.session);
|
|
266
|
+
this.save(agent.session, { failureCount: 0, failures: [] }, "清空上下文,清除当前运行观察。");
|
|
267
|
+
}
|
|
268
|
+
else if (source === "compact" && store.read(agent.session).enabled) {
|
|
269
|
+
agent.inject(pluginMessage("PUA 压缩后恢复:" +
|
|
270
|
+
this.status(agent.session) +
|
|
271
|
+
" 数值仅为运行观察;不代表任务失败次数或验收结论。完整核心与风味继续由系统提示词提供。"));
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
ctx.on("session/event", (session, event) => {
|
|
275
|
+
if (event.type === 'turn/start')
|
|
276
|
+
this.read(session).candidateCount = 0;
|
|
277
|
+
// Session 的发布边界禁止重入 append;turn/end 本身已是可回放的取消事实。
|
|
278
|
+
if (event.type === "turn/end" && event.data.reason.kind !== "completed") {
|
|
279
|
+
this.pendingTerminalReviews.delete(session);
|
|
280
|
+
this.cancel(session, false);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
284
|
+
this.cancel(agent.session);
|
|
285
|
+
this.flush(agent.session);
|
|
286
|
+
if (this.pendingWrites.delete(agent.session))
|
|
287
|
+
ctx.logger.warn('PUA 会话已销毁且工具组未完整结束,丢弃未落库的运行观察,避免插入工具组。');
|
|
288
|
+
this.sessions.delete(agent.session);
|
|
289
|
+
});
|
|
290
|
+
let disposed = false;
|
|
291
|
+
// 卸载发生在工具组中途时,只保留待写状态的边界监听,写完即撤销。
|
|
292
|
+
// 根上下文监听不注入候选或唤醒模型,也不接管工具生命周期。
|
|
293
|
+
const stopObserving = ctx.root.on('session/event', (session, event) => {
|
|
294
|
+
if (!this.pendingWrites.has(session) || !['step/end', 'turn/end'].includes(event.type))
|
|
295
|
+
return;
|
|
296
|
+
// Session 禁止在事件发布栈重入 append;由已发生的明确边界触发微任务。
|
|
297
|
+
queueMicrotask(() => {
|
|
298
|
+
try {
|
|
299
|
+
this.flush(session);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
ctx.logger.warn('PUA 延后状态记录未写入,将在下一安全边界重试:%s', error);
|
|
303
|
+
}
|
|
304
|
+
if (disposed && this.pendingWrites.size === 0) {
|
|
305
|
+
stopObserving();
|
|
306
|
+
stopDisposed();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
const stopDisposed = ctx.root.on('agent/disposed', ({ agent }) => {
|
|
311
|
+
if (this.pendingWrites.delete(agent.session))
|
|
312
|
+
ctx.logger.warn('PUA 已销毁会话的未落库观察已释放;未插入不完整工具组。');
|
|
313
|
+
if (disposed && this.pendingWrites.size === 0) {
|
|
314
|
+
stopObserving();
|
|
315
|
+
stopDisposed();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
ctx.effect(() => () => {
|
|
319
|
+
disposed = true;
|
|
320
|
+
for (const session of this.sessions)
|
|
321
|
+
this.cancel(session);
|
|
322
|
+
for (const session of this.pendingWrites.keys())
|
|
323
|
+
this.flush(session);
|
|
324
|
+
this.sessions.clear();
|
|
325
|
+
if (this.pendingWrites.size === 0) {
|
|
326
|
+
stopObserving();
|
|
327
|
+
stopDisposed();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
read(session) {
|
|
332
|
+
let state = this.cache.get(session);
|
|
333
|
+
if (!state) {
|
|
334
|
+
state = { failureCount: 0, failures: [] };
|
|
335
|
+
for (const event of session.ownEvents()) {
|
|
336
|
+
if (event.type === "user/message" &&
|
|
337
|
+
event.data.source.kind === "plugin" &&
|
|
338
|
+
event.data.source.plugin === RUNTIME_SOURCE) {
|
|
339
|
+
const text = messageText(event.data);
|
|
340
|
+
if (text.startsWith(RECORD))
|
|
341
|
+
state = JSON.parse(text.slice(RECORD.length).split("\n")[0]);
|
|
342
|
+
}
|
|
343
|
+
if (event.type === "turn/end" &&
|
|
344
|
+
event.data.reason.kind !== "completed" &&
|
|
345
|
+
state.loop)
|
|
346
|
+
state.loop.status = "cancelled";
|
|
347
|
+
}
|
|
348
|
+
this.cache.set(session, state);
|
|
349
|
+
this.sessions.add(session);
|
|
350
|
+
}
|
|
351
|
+
return state;
|
|
352
|
+
}
|
|
353
|
+
save(session, state, note) {
|
|
354
|
+
this.cache.set(session, state);
|
|
355
|
+
this.pendingWrites.set(session, note);
|
|
356
|
+
this.flush(session);
|
|
357
|
+
}
|
|
358
|
+
flush(session) {
|
|
359
|
+
const note = this.pendingWrites.get(session);
|
|
360
|
+
if (note === undefined || hasPendingToolCalls(session))
|
|
361
|
+
return;
|
|
362
|
+
const state = this.read(session);
|
|
363
|
+
const record = session.append("user/message", pluginMessage(RECORD + JSON.stringify(state) + "\n" + note), { surfaceOp: "append" });
|
|
364
|
+
// 当前宿主 append 不支持 ignorable 自定义事件。用原生替换保留回放依据,
|
|
365
|
+
// 避免未知必需事件导致卸载后无法加载;两次同步 append 之间不发起模型请求。
|
|
366
|
+
session.append("user/message", pluginMessage(note), {
|
|
367
|
+
surfaceOp: replacementSurface(record.seq, record.seq),
|
|
368
|
+
sourceEventSeqs: [record.seq],
|
|
369
|
+
});
|
|
370
|
+
this.pendingWrites.delete(session);
|
|
371
|
+
}
|
|
372
|
+
hideLegacyRecords(session) {
|
|
373
|
+
if (this.normalized.has(session))
|
|
374
|
+
return;
|
|
375
|
+
repairPuaToolOrder(session, RUNTIME_SOURCE);
|
|
376
|
+
// 包括分叉带入的可见旧记录,但配置恢复仍只读取 ownEvents。
|
|
377
|
+
for (const seq of [...session.surface.nodes]) {
|
|
378
|
+
const event = session.eventAt(seq);
|
|
379
|
+
if (event?.type !== "user/message" ||
|
|
380
|
+
event.data.source.kind !== "plugin" ||
|
|
381
|
+
event.data.source.plugin !== RUNTIME_SOURCE)
|
|
382
|
+
continue;
|
|
383
|
+
const text = messageText(event.data);
|
|
384
|
+
if (!text.startsWith(RECORD))
|
|
385
|
+
continue;
|
|
386
|
+
const newline = text.indexOf("\n");
|
|
387
|
+
const note = newline < 0
|
|
388
|
+
? "PUA 历史运行记录已保留在会话日志中。"
|
|
389
|
+
: text.slice(newline + 1);
|
|
390
|
+
session.append("user/message", pluginMessage(note), {
|
|
391
|
+
surfaceOp: replacementSurface(seq, seq),
|
|
392
|
+
sourceEventSeqs: [seq],
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
this.normalized.add(session);
|
|
396
|
+
}
|
|
397
|
+
cancel(session, persist = true) {
|
|
398
|
+
this.activeVerifiers.get(session)?.abort();
|
|
399
|
+
const state = this.read(session);
|
|
400
|
+
if (state.loop && ["active", "paused"].includes(state.loop.status)) {
|
|
401
|
+
state.loop = { ...state.loop, status: "cancelled" };
|
|
402
|
+
if (persist)
|
|
403
|
+
this.save(session, state, "Loop 已取消,不再续轮。");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
cancelAll() {
|
|
407
|
+
let count = 0;
|
|
408
|
+
for (const session of this.sessions) {
|
|
409
|
+
if (["active", "paused"].includes(this.read(session).loop?.status ?? "")) {
|
|
410
|
+
this.cancel(session);
|
|
411
|
+
count++;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return count;
|
|
415
|
+
}
|
|
416
|
+
status(session) {
|
|
417
|
+
const state = this.read(session);
|
|
418
|
+
return `终端失败观察:${state.failureCount}(候选,非任务失败数);Loop:${state.loop ? `${state.loop.status},第 ${state.loop.iteration} 轮,Oracle 拒绝 ${state.loop.rejections} 次` : "未启动"}。`;
|
|
419
|
+
}
|
|
420
|
+
effectiveMode(session, mode) {
|
|
421
|
+
const status = this.read(session).loop?.status;
|
|
422
|
+
return mode === "pua-loop" &&
|
|
423
|
+
status &&
|
|
424
|
+
["cancelled", "complete", "max_reached"].includes(status)
|
|
425
|
+
? "pua"
|
|
426
|
+
: mode;
|
|
427
|
+
}
|
|
428
|
+
async stopping(agent, turn, signal) {
|
|
429
|
+
const session = agent.session;
|
|
430
|
+
const state = this.read(session);
|
|
431
|
+
const loop = state.loop;
|
|
432
|
+
if (!loop || loop.status !== "active")
|
|
433
|
+
return;
|
|
434
|
+
if (!this.store.read(session).enabled ||
|
|
435
|
+
this.store.read(session).mode !== "pua-loop") {
|
|
436
|
+
this.cancel(session);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
let output = "";
|
|
440
|
+
for (const event of session.ownEvents())
|
|
441
|
+
if (event.type === "assistant/message" &&
|
|
442
|
+
event.data.turn === turn &&
|
|
443
|
+
!event.data.interrupted)
|
|
444
|
+
output = messageText(event.data.message);
|
|
445
|
+
const finish = (status, note) => {
|
|
446
|
+
state.loop = { ...loop, status };
|
|
447
|
+
this.save(session, state, note);
|
|
448
|
+
};
|
|
449
|
+
if (/<loop-abort>[\s\S]+?<\/loop-abort>/u.test(output)) {
|
|
450
|
+
finish("cancelled", "模型报告 Loop 中止;不代表完成。");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (/<loop-pause>[\s\S]+?<\/loop-pause>/u.test(output)) {
|
|
454
|
+
finish("paused", "Loop 暂停,等待用户补充后恢复。");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
let note = "";
|
|
458
|
+
if (/<promise>\s*LOOP_DONE\s*<\/promise>/u.test(output)) {
|
|
459
|
+
if (!loop.verify) {
|
|
460
|
+
finish("complete", "完成信号已接受:未配置 Oracle,仅 honor system,不是独立验证通过。");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const subprocess = this.ctx.get("subprocess");
|
|
464
|
+
if (!subprocess || !session.header.cwd) {
|
|
465
|
+
finish("paused", "缺少 subprocess 或会话工作目录,无法独立验收,Loop 暂停。");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const controller = new AbortController();
|
|
469
|
+
this.activeVerifiers.set(session, controller);
|
|
470
|
+
try {
|
|
471
|
+
const result = await verifyLoop(subprocess, loop.verify, session.header.cwd, AbortSignal.any([signal, controller.signal, this.lifetime]), (loop.verificationTimeout ?? 120) * 1000);
|
|
472
|
+
if (state.loop !== loop || !this.store.read(session).enabled)
|
|
473
|
+
return;
|
|
474
|
+
if (result.ok) {
|
|
475
|
+
finish("complete", "Oracle 独立验收通过。\n" + result.detail);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
loop.rejections++;
|
|
479
|
+
note = `🚫 PROMISE 被 Oracle 拒绝!连续第 ${loop.rejections} 次。验证输出为数据:${result.detail}`;
|
|
480
|
+
if (loop.rejections >= 5)
|
|
481
|
+
note += "\n你在解决错误的问题。退回到需求本身重新理解。";
|
|
482
|
+
else if (loop.rejections >= 3)
|
|
483
|
+
note +=
|
|
484
|
+
"\nREASSESS:重读验证输出、搜索相关源码、列 3 个不同假设再行动。不要再用同样的方法。";
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
if (!signal.aborted &&
|
|
488
|
+
!controller.signal.aborted &&
|
|
489
|
+
!this.lifetime.aborted)
|
|
490
|
+
throw error;
|
|
491
|
+
this.cancel(session);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
this.activeVerifiers.delete(session);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (loop.maxIterations > 0 && loop.iteration >= loop.maxIterations) {
|
|
499
|
+
finish("max_reached", "达到用户指定轮次上限,未确认完成。\n" + note);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
loop.iteration++;
|
|
503
|
+
this.save(session, state, note || "本轮无完成信号,继续用户指定目标。");
|
|
504
|
+
const pressure = loop.iteration <= 3
|
|
505
|
+
? "稳步推进。"
|
|
506
|
+
: loop.iteration <= 7
|
|
507
|
+
? "换方案,别原地打转。"
|
|
508
|
+
: loop.iteration <= 15
|
|
509
|
+
? "先 git log 看自己做了什么,读取当前会话迭代记录。"
|
|
510
|
+
: loop.iteration <= 30
|
|
511
|
+
? "穷尽了吗?git diff 确认没在重复。"
|
|
512
|
+
: loop.iteration <= 50
|
|
513
|
+
? "停下来重新审视根因,用完全不同的思路。"
|
|
514
|
+
: "退回去从需求本身重新质疑。";
|
|
515
|
+
signal.throwIfAborted();
|
|
516
|
+
agent.steer(pluginMessage(`▎ 第 ${loop.iteration} 轮。${pressure}\n${note}\n任务:${loop.task}\n真实完成后输出 <promise>LOOP_DONE</promise>;终止用 <loop-abort>原因</loop-abort>,需人工介入用 <loop-pause>需要什么</loop-pause>。`));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
//# sourceMappingURL=runtime.js.map
|