Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
339d8d9
refactor(execution): a round's store and board channel are scoped by run
wefio Sep 13, 2026
b0b4fb5
test(execution): two runs in one store neither collide nor see each o…
wefio Sep 13, 2026
7b29d3a
test(execution): retarget the moved teeth and pin the claim's run scope
wefio Sep 13, 2026
4adf6b4
docs(execution): record the run-scoped round store, with what it does…
wefio Sep 13, 2026
49a5ab4
refactor(execution): a task row's three kinds of fact become three ta…
wefio Sep 13, 2026
28e22e9
test(execution): the split is proven by deleting the derived cache
wefio Sep 13, 2026
b1bcf54
test(execution): the moved teeth, and a tooth for the rebuild
wefio Sep 13, 2026
a0b6949
docs(execution): record the split, and the fact the recomputation che…
wefio Sep 13, 2026
9148b79
refactor(store): the store owns the transaction boundary
wefio Sep 13, 2026
476fd89
test(core): the transaction port contract, as refusals
wefio Sep 13, 2026
d6d2668
test(store): teeth for the port contract - a nested transaction, and …
wefio Sep 13, 2026
fbb00ee
docs(execution): record the port slice, what it verified, and what it…
wefio Sep 13, 2026
d5a7cce
test(execution): the namespace test reads the projection B2 introduced
wefio Sep 13, 2026
68b7011
refactor(execution): a round's publication joins the transition it be…
wefio Sep 13, 2026
00e9885
test(execution): a board write inside a round transition shares its fate
wefio Sep 13, 2026
2f1797a
test(execution): a tooth for the composed publication, and the record…
wefio Sep 13, 2026
af40909
refactor(store): the four remaining transaction sites use the boundary
wefio Sep 13, 2026
4ce9767
test(core): the one-boundary invariant is checked, not remembered
wefio Sep 13, 2026
3b78dad
test(store): a tooth for a hand-rolled BEGIN, and the record for this…
wefio Sep 13, 2026
5a29e2f
fix(ooo): a cancelled round releases the default store directory on b…
wefio Sep 13, 2026
5056bf7
test(execution): the cancelled round's temp root holds no store direc…
wefio Sep 13, 2026
d6d46e0
docs(postmortem): two records for this session's escaped failures
wefio Sep 13, 2026
72bb5b9
feat(store): a read-only factory that neither creates, migrates nor c…
wefio Sep 14, 2026
d6e4690
test(store): a read-only open refuses a missing file, a write, and an…
wefio Sep 14, 2026
44775fe
test(teeth): structural locators, and a stale anchor is now a failure
wefio Sep 14, 2026
6a2833f
refactor(board): one read implementation, and an owner-issued query port
wefio Sep 14, 2026
ae066e7
feat(ooo): status reads through the port instead of constructing the …
wefio Sep 14, 2026
5736784
fix(ooo): the status read path imports the port it calls
wefio Sep 14, 2026
87c6709
test(ooo): pin the one acceptance predicate, and prove the pin bites
wefio Sep 14, 2026
79c35d8
test(ooo): fence proofs, the read-only tooth, and the contract's list…
wefio Sep 14, 2026
cd584ed
docs(ooo): the contract as a ledger of nodes, not a tally of edits
wefio Sep 14, 2026
48675f6
feat(ooo): the lifecycle nodes, and the two read paths proven to agree
wefio Sep 14, 2026
eb7d388
docs(ooo): E closed by the design's own sentence, and the D7 attempt …
wefio Sep 14, 2026
962989c
docs(ooo): the design names the product entry, so the ledger gets a G…
wefio Sep 14, 2026
f56646d
docs(ooo): the design is tracked after all - it is the newer revision…
wefio Sep 14, 2026
5d078ea
feat(ooo): the round borrows its store, and the crash window is not g…
wefio Sep 14, 2026
33d5369
test(ooo): a notification that fails is not a submission that failed
wefio Sep 14, 2026
853c60d
feat(cli): shutdown drains what it accepted, and says so when it cannot
wefio Sep 14, 2026
b33191f
docs(ooo): G1 is the wiring slice, not a test
wefio Sep 14, 2026
a1b720e
refactor(ooo): the store supplies the facts, the shared compiler supp…
wefio Sep 14, 2026
af775f3
test(ooo): G1 and G2 on the ordinary path, with no round involved
wefio Sep 14, 2026
d659cc9
docs(ooo): G1 and G2 are proven, and the gate they were owed for is p…
wefio Sep 14, 2026
f6addd6
fix(ooo): drop the import the round no longer uses
wefio Sep 14, 2026
7025169
fix(ci): clear the gates the audit reads, and one real bug in the anc…
wefio Sep 14, 2026
afdb791
fix(teeth): move the tooth with the rule it pins, and correct two claims
wefio Sep 14, 2026
0222a90
docs(ooo): the G2 row names the tooth that now pins its release rule
wefio Sep 14, 2026
b872f79
docs(ooo): the G2 row reports where the mutation actually fails
wefio Sep 14, 2026
27121b0
test(ooo): G3 - failure, cancellation and the ordered fallback on the…
wefio Sep 14, 2026
1b9eb2e
docs(ooo): G3 and G4 are proven
wefio Sep 14, 2026
67b350b
fix(ooo): answer the audit - the missing G3 case, an honest G4, and c…
wefio Sep 14, 2026
13aac73
feat(ooo)!: G5 - ooo_round leaves the product tool directory
wefio Sep 14, 2026
4767b96
feat(ooo): G7 - one real handoff at a legal boundary, five structural…
wefio Sep 14, 2026
3e8009a
analysis(ooo): compare the stable baseline against the continuation, …
wefio Sep 14, 2026
4d886cd
chore(ooo): archive the continuation samples inside the repository
wefio Sep 14, 2026
d74d79b
chore(ooo): finish moving the rejected artifact into the archive
wefio Sep 14, 2026
0d3dc09
docs(ooo): the contract ledger moves to the home that fits it (#65)
wefio Sep 16, 2026
9499367
Merge origin/main into feat/ooo-run-namespace
wefio Sep 16, 2026
0d15651
docs(ooo): the ledger reads the merge it just took
wefio Sep 16, 2026
7d39f6f
fix(ooo): the continuation driver calls the throws helper it declared
wefio Sep 16, 2026
bd63a6a
fix(rcp): the TAP rejection names the count that rejected it
wefio Sep 16, 2026
8b2a6be
docs(ooo): the ledger's quotes are re-checked against the design it c…
wefio Sep 16, 2026
c4e9183
feat(store): task run records live in the store, and their writes joi…
wefio Sep 16, 2026
6255bf0
docs(ooo): D11 records the run-record namespace, and the next pass is…
wefio Sep 16, 2026
05e84eb
Merge origin/main into feat/ooo-run-namespace
wefio Sep 16, 2026
72edb93
fix(evals): a kept artifact's failure keeps its cause
wefio Sep 16, 2026
f895224
refactor(ooo): delete a board helper no caller reaches
wefio Sep 16, 2026
7963c95
docs: re-measure the gate debt on the tree this branch produces
wefio Sep 16, 2026
4352234
feat(ooo): a managed entry's write belongs to its run's transition
wefio Sep 16, 2026
f36b43a
docs(ooo): B6 is proven, and the remaining half of step 3 is named
wefio Sep 16, 2026
5848f2a
feat(ooo): the contract's publications hold over every enumerated int…
wefio Sep 16, 2026
5356edd
docs(ooo): A5 is proven, and B9 is what its enumeration found
wefio Sep 16, 2026
07e0154
feat(ooo): binding is what makes an entry managed, and one rule route…
wefio Sep 16, 2026
bf130d5
docs(ooo): D12 lands the binding, and step 3's remaining half is name…
wefio Sep 16, 2026
2b4fedc
chore: ignore the session scratch directory, and route .gitignore
wefio Sep 16, 2026
5aa4c7b
feat(verify): a route may declare the always-run shared checks not ap…
wefio Sep 16, 2026
655cf22
docs(verify): record why a route may decline the shared checks
wefio Sep 16, 2026
b22b9b2
feat(ooo): the run surface a second process reaches
wefio Sep 17, 2026
47edc74
docs(ooo): D13 records the run surface, and design.md records why no …
wefio Sep 17, 2026
e7e8a66
feat(ooo): the evidence drivers reach the board through the daemon
wefio Sep 17, 2026
7a32c5d
docs(ooo): D14 records the driver boundary, and what step 3 still owes
wefio Sep 17, 2026
5ff8061
feat(ooo): a client bounds its call and never calls the endpoint it s…
wefio Sep 17, 2026
569e3ff
docs(ooo): D14 records the pessimistic client boundary
wefio Sep 17, 2026
61c6af4
feat(ooo): a suggestion source may only rank inside the legal set
wefio Sep 17, 2026
c995720
docs(ooo): the E seam and its offline proofs, with nothing wired
wefio Sep 17, 2026
1740573
feat(ooo): an advisory cost sweep before any paid arm
wefio Sep 17, 2026
ff3cd39
docs(ooo): the F1 sweep, its assumptions and what it does not say
wefio Sep 17, 2026
0a422e8
feat(ooo): the round's plan has one home, and its log names it
wefio Sep 17, 2026
ad07346
docs(ooo): why the arms get their own driver, and what happens to the…
wefio Sep 17, 2026
d5fec89
test(ooo): keep the plan test out of the repository root
wefio Sep 17, 2026
ed4ac24
docs(ooo): F1 landed, F2a landed, and F2b is the boundary F2a proved
wefio Sep 17, 2026
c50b443
docs(ooo): the round's role/mechanism question is deferred with its m…
wefio Sep 17, 2026
72609ca
ooo: a driver for the arms' plans, and the slot count it could not reach
wefio Sep 17, 2026
52e2f99
docs(ooo): F2b landed for one arm, and the C arm is blocked by a meas…
wefio Sep 17, 2026
fe6a260
ooo: the driver names no task, and the plan's ids are data
wefio Sep 17, 2026
5b4ed7f
Revert "ooo: the driver names no task, and the plan's ids are data"
wefio Sep 17, 2026
f602cba
ooo: a run declares its slot budget, and a claim spends one
wefio Sep 18, 2026
32f1db4
docs(ooo): the slot-budget decision, and the ledger row it closes
wefio Sep 18, 2026
108ebf7
ooo: the admission layer holds as many claims as the run declared
wefio Sep 18, 2026
b067088
docs(ooo): the slot budget landed, with the board and driver halves
wefio Sep 18, 2026
b4dc0cd
feat(ooo): give the publication enumeration a budget dimension
wefio Sep 18, 2026
2336054
feat(ooo): the F2c task families, and the pilot that runs them
wefio Sep 18, 2026
7bea058
docs(ooo): record the arms pilot, the two families, and what it canno…
wefio Sep 18, 2026
a7b56e9
docs: long checks run detached, and their result is collected before …
wefio Sep 18, 2026
a101633
docs(skills): repo-development grows by routing detail into references/
wefio Sep 18, 2026
e957b65
fix(tools): a failing agent:verify check restates its own output and …
wefio Sep 18, 2026
7821c5e
docs(decisions): propose keep/merge/cut for ooo-cycle.ts, measured
wefio Sep 18, 2026
c47ba60
refactor(ooo): retire the round instrument, distribute what has a home
wefio Sep 18, 2026
5b0089c
docs(decisions): propose the fusion and speculation slices, offline f…
wefio Sep 18, 2026
4d35266
docs(decisions): record the approved 1000k token ceiling for the fusi…
wefio Sep 18, 2026
eec472d
chore(verification): make a live mutant visible to the tree
wefio Sep 19, 2026
7624453
fix(store): widen the current-value window by a named clock grace
wefio Sep 19, 2026
6e31c83
feat(ooo-execution): fusion legality, accounting and the driver's ses…
wefio Sep 19, 2026
8ebbcf9
chore(verification): fix the guardrail's own three failures, and reco…
wefio Sep 19, 2026
622701b
docs(decisions): record the approval for the sweep and evidence rules
wefio Sep 19, 2026
7bb67bd
docs(ledger): correct where task-unit-semantics.md actually stands
wefio Sep 19, 2026
d4f8229
docs(decisions): say what the fusion mechanism is, instead of calling…
wefio Sep 19, 2026
0840cb1
docs(decisions): give the fusion mechanism its required sections and …
wefio Sep 19, 2026
1985469
refactor(pi-extension): let one tool surface serve a chain (fusion me…
wefio Sep 19, 2026
89ef1d3
refactor(pi-extension): one session can run many units (fusion mechan…
wefio Sep 19, 2026
4b2c6aa
feat(ooo-execution): a fused live arm can hold one session (fusion me…
wefio Sep 19, 2026
38fa73d
docs(ooo-execution): the paid D arm - fusion measured against its con…
wefio Sep 19, 2026
621d309
feat(ooo-execution): the speculation lifecycle, as rules instead of a…
wefio Sep 19, 2026
847baf2
feat(ooo-execution): the E arm's instrument, and what its first paid …
wefio Sep 19, 2026
5e6a8cc
docs(experiments): keep the arms' samples, and plan the data before t…
wefio Sep 19, 2026
0edd749
feat(ooo-execution): answer the E arm's diagnosis and its economics, …
wefio Sep 19, 2026
3275c3f
test(ooo-verification): pin that an artifact is read by its kind (P3)
wefio Sep 19, 2026
6f902ab
docs(experiments): the autodiff cost-model attempt, and the data that…
wefio Sep 19, 2026
3cab7da
feat(ooo): fusion planning - repair-first online, ceiling offline
wefio Sep 19, 2026
ab41d22
feat(ooo): the cap experiment, and the driver asks the one owner of t…
wefio Sep 19, 2026
2495e16
measure(ooo): record cache accounting, and the cap experiment says ca…
wefio Sep 19, 2026
8496485
fix(ci): the coverage check acknowledges archived run directories
wefio Sep 19, 2026
4b1a6e2
docs(decisions): close the six proposals whose criteria are met
wefio Sep 19, 2026
2306b26
chore: follow the moved decision record in a source comment
wefio Sep 19, 2026
d2edaed
refactor(integration): name the check identity and its runner by what…
wefio Sep 19, 2026
d52587d
feat(context): route the integration layer so editing it runs checks
wefio Sep 19, 2026
57da8ff
Merge origin/main into feat/ooo-run-namespace
wefio Sep 19, 2026
2179a8d
feat(context): give the integration layer's third part its own route
wefio Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ coverage/
.mcp.json
/.benchmarks/
/.playwright-cli/
# Scratch and experiment output for a session; never a home for a necessary file (AGENTS.md).
/.temp/
graph.html
/output/
/design/
Expand Down
46 changes: 0 additions & 46 deletions .pi/extensions/nmg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
type DaemonConnection,
} from "../../../src/cli/daemon-client.ts";
import { resolveNmgDataDir } from "../../../src/cli/data-path.ts";
import { describeStart, shortRound, startRound, type OooRoundAction } from "./ooo-round.ts";
import {
archiveOrStage,
archiveNodeName,
Expand Down Expand Up @@ -264,171 +263,171 @@
}
});

pi.on("before_agent_start", async (event, ctx) => {
const sessionId = ctx.sessionManager.getSessionId();
const isNewUserTurn = recallFlow.beginTurn(
sessionId,
piUserTurnKey(ctx.sessionManager, event.prompt),
);
await beginDisclosureTurn(sessionId, isNewUserTurn);
const completionNudge = popCompletionNudge(event.prompt);
// Pi re-enters before_agent_start after tool results. A completed graph may
// already exist at that point, but it must be reviewed on the next user
// turn, not consumed by an internal tool loop from the same prompt.
const pendingFeedback = isNewUserTurn ? controllerShadow.pendingFeedback(sessionId) : null;
if (pendingFeedback) await controllerShadow.feedbackNudgeShown(sessionId, pendingFeedback);
const feedbackNudge = pendingFeedback
? renderDisclosure(nmgPrompts.shadow_feedback_nudge, {
active_graph_id: pendingFeedback.activeGraphId,
semantic_task_id: pendingFeedback.semanticTaskId,
})
: "";
const pendingClaimOutcome = isNewUserTurn
? controllerShadow.pendingClaimOutcome(sessionId)
: null;
if (pendingClaimOutcome) {
await controllerShadow.claimOutcomeNudgeShown(sessionId, pendingClaimOutcome);
}
const claimOutcomeNudge = pendingClaimOutcome
? renderDisclosure(nmgPrompts.shadow_claim_outcome_nudge, {
active_graph_id: pendingClaimOutcome.activeGraphId,
semantic_task_id: pendingClaimOutcome.semanticTaskId,
memory_ids: pendingClaimOutcome.memoryIds.join(","),
})
: "";
const nudge = [completionNudge, feedbackNudge, claimOutcomeNudge].filter(Boolean).join("\n");
let reasoningCheckpoint = "";
if (labToolsEnabled) {
try {
const status = (await invokeDaemon(await connection(), "lab", {
action: "status",
capability: "reasoning_workspace",
sessionId,
})) as { activation?: unknown };
if (status.activation) {
const consumed = (await invokeDaemon(await connection(), "lab", {
action: "invoke",
capability: "reasoning_workspace",
sessionId,
operation: "consume_checkpoint",
input: { maxNodes: 24, maxChars: 6_000 },
})) as { output?: { text?: string } | null };
reasoningCheckpoint = consumed.output?.text ?? "";
}
} catch (error) {
reasoningCheckpoint = `Reasoning workspace unavailable: ${message(error)}`;
}
}
let runtimeAgContext = "";
try {
const result = (await invoke("sessionActiveGraph", {
action: "snapshot",
sessionId,
})) as {
snapshot?:
import("../../../src/core/session-active-graph.ts").SessionActiveGraphSnapshot | null;
};
runtimeAgContext = renderSessionActiveGraphSurface(result.snapshot ?? null);
} catch {
// Search and ordinary Pi operation continue without optional working state.
}
const runtimeContext = [runtimeAgContext, reasoningCheckpoint].filter(Boolean).join("\n");
const recallRequest = taskWindow.prepare(sessionId, event.prompt);
const dynamicContext = composeNmgContextMessage("", "", nudge, runtimeContext);
if (!recallRequest) {
return {
systemPrompt: composeNmgSystemPrompt(event.systemPrompt),
...(dynamicContext
? {
message: {
customType: "nmg-context",
content: dynamicContext,
display: true,
details: { count: 0 },
},
}
: {}),
};
}
try {
let context = (await invoke("search", {
query: recallRequest.query,
projectDir: projectDirectory(),
sessionId,
maxTier: Math.min(configuredAutoRecallTier(), recallRequest.maxTier) as MemoryTier,
limit: Math.min(configuredAutoRecallLimit(), recallRequest.limit),
initialEvidenceTarget: configuredInitialTarget(),
strongHitTopGap: configuredStrongHitTopGap(),
strongHitInitialTarget: configuredStrongHitInitialTarget(),
secondPass: qpp2Mode === "active",
graphHops: Math.min(1, recallRequest.graphHops),
tieredDisclosure: true,
// Tell the daemon this is an automatic recall decision: it stages the
// injected graph for online learning (explicit nmg_search stays unstaged).
autoRecall: true,
})) as MemoryContext;
if (controllerRerankMode === "active") {
context = await controllerShadow.rerank(context);
}
const fullContext = context;
if (qpp2Mode === "active") {
context = await applyLearnedFold(context, controllerShadow, qpp2RetainedMass, false);
}
const recalled = await formatDisclosedContext(sessionId, context, "header");
await controllerShadow.retrieval(fullContext, sessionId, "automatic", recalled);
// Automatic recall is still a retrieval trace. Keep it in the per-turn
// attribution window so agent_end can distinguish surfaced evidence from
// candidates that were merely injected.
agentAttributionFlow.note(sessionId, fullContext);
const recordCount = (recalled.match(/memory=/g) ?? []).length;
const searchNudge = formatSearchRecommendation(context, recommendationMode);
const recallContext = composeNmgContextMessage(
recalled,
"",
[
nudge,
searchNudge,
recordCount > 0 ? recallFeedbackAffordance(fullContext.activeGraph?.id) : "",
]
.filter(Boolean)
.join("\n"),
runtimeContext,
);
return {
systemPrompt: composeNmgSystemPrompt(event.systemPrompt),
...(recallContext
? {
message: {
customType: "nmg-context",
content: recallContext,
display: true,
details: { count: recordCount },
},
}
: {}),
};
} catch (error) {
const errorContext = composeNmgContextMessage(
"",
`NMG unavailable: ${message(error)}`,
nudge,
runtimeContext,
);
return {
systemPrompt: composeNmgSystemPrompt(event.systemPrompt),
...(errorContext
? {
message: {
customType: "nmg-context",
content: errorContext,
display: true,
details: { count: 0 },
},
}
: {}),
};
}
});

Check notice on line 430 in .pi/extensions/nmg/index.ts

View check run for this annotation

codefactor.io / CodeFactor

.pi/extensions/nmg/index.ts#L266-L430

Complex Method

// TUI 折叠开关:nmg-context 默认折叠为一个 [nmg-context] chip,避免每轮刷屏。
// /nmg-recall 切换展开(走 pi 默认渲染:label + 全文)。状态只影响后续渲染
Expand All @@ -449,210 +448,210 @@
// NMG 总二级/三级菜单。早期只有独立的 /nmg-recall 命令;现在统一收编到
// /nmg 下,保留 /nmg-recall 作为别名。状态只影响后续渲染与重开会话后的
// 历史恢复;已渲染的历史消息不重绘(pi 无公开 force-rerender)。
const nmgMenuHandler = async (args: string, ctx: ExtensionCommandContext): Promise<void> => {
const parts = args.trim().split(/\s+/).filter(Boolean);
const [sub, ...rest] = parts;
const restText = rest.join(" ");
switch (sub) {
case "recall": {
recallCollapsed = !recallCollapsed;
ctx.ui.notify(
recallCollapsed
? "nmg-context 已折叠(只显示 [nmg-context] chip)"
: "nmg-context 已展开(显示召回全文)",
"info",
);
return;
}
case "wake": {
const current = readWakeConfig();
const [wcmd, wvalRaw] = restText.split(/\s+/);
const wval = Number(wvalRaw);
switch (wcmd) {
case "on":
writeWakeConfig({ ...current, enabled: true });
ctx.ui.notify("黑板唤醒已开启(世界频道+活跃频道有新 open 条目会唤醒)", "info");
return;
case "off":
writeWakeConfig({ ...current, enabled: false });
ctx.ui.notify("黑板唤醒已关闭", "info");
return;
case "status": {
const budgetText = current.budget === 0 ? "不限制" : `${current.budget}/天`;
const cooldownText =
current.cooldownMs === 0 ? "无" : `${Math.round(current.cooldownMs / 60_000)} 分`;
ctx.ui.notify(
`黑板唤醒:${current.enabled ? "开" : "关"} · 预算 ${budgetText} · 冷却 ${cooldownText} · 轮询 ${Math.round(current.intervalMs / 1_000)} 秒 · 世界广播 ${current.worldBroadcast ? "开" : "关"}`,
"info",
);
return;
}
case "budget": {
if (!Number.isFinite(wval)) {
ctx.ui.notify("用法:/nmg wake budget N(0=不限制)", "warning");
return;
}
const budget = Math.max(0, Math.min(100, Math.round(wval)));
writeWakeConfig({ ...current, budget });
ctx.ui.notify(`黑板唤醒预算已设为 ${budget === 0 ? "不限制" : `${budget}/天`}`, "info");
return;
}
case "cooldown": {
if (!Number.isFinite(wval)) {
ctx.ui.notify("用法:/nmg wake cooldown M(0=无冷却)", "warning");
return;
}
const cooldownMs = wval === 0 ? 0 : Math.max(30_000, Math.round(wval * 60_000));
writeWakeConfig({ ...current, cooldownMs });
ctx.ui.notify(
`黑板唤醒冷却已设为 ${cooldownMs === 0 ? "无" : `${Math.round(cooldownMs / 60_000)} 分钟`}`,
"info",
);
return;
}
case "interval": {
if (!Number.isFinite(wval)) {
ctx.ui.notify("用法:/nmg wake interval S(秒,最小 5)", "warning");
return;
}
const intervalMs = Math.max(5_000, Math.round(wval * 1_000));
writeWakeConfig({ ...current, intervalMs });
ctx.ui.notify(`黑板唤醒轮询已设为 ${Math.round(intervalMs / 1_000)} 秒`, "info");
return;
}
case "world": {
// 无参切换;0 关、1 开。
const world =
wvalRaw === "0" ? false : wvalRaw === "1" ? true : !current.worldBroadcast;
writeWakeConfig({ ...current, worldBroadcast: world });
ctx.ui.notify(
world
? "已开启世界频道协作广播:协作类新条目会同时发到世界频道拉其他 agent"
: "已关闭世界频道协作广播",
"info",
);
return;
}
default: {
const enabled = !current.enabled;
writeWakeConfig({ ...current, enabled });
ctx.ui.notify(enabled ? "黑板唤醒已开启" : "黑板唤醒已关闭", "info");
}
}
return;
}
default: {
// 无参数 → 交互式选择菜单(可上下键选,不用手敲);未知子命令 → 总览提示。
if (parts.length === 0) {
await nmgInteractiveMenu(ctx);
return;
}
const wake = readWakeConfig();
const budgetText = wake.budget === 0 ? "不限制" : `${wake.budget}/天`;
const cooldownText =
wake.cooldownMs === 0 ? "无" : `${Math.round(wake.cooldownMs / 60_000)} 分`;
ctx.ui.notify(
`NMG 菜单:/nmg recall(召回折叠 ${recallCollapsed ? "开" : "关"}) · /nmg wake on|off|status|budget N|cooldown M|interval S(唤醒 ${wake.enabled ? "开" : "关"},预算 ${budgetText},冷却 ${cooldownText},轮询 ${Math.round(wake.intervalMs / 1_000)} 秒)`,
"info",
);
}
}
};

Check notice on line 559 in .pi/extensions/nmg/index.ts

View check run for this annotation

codefactor.io / CodeFactor

.pi/extensions/nmg/index.ts#L451-L559

Complex Method
// 交互式选择菜单:/nmg(无参数)弹出可上下键选的列表,避免手敲。带参数的
// 形式(/nmg recall、/nmg wake on 等)仍保留,供脚本/快捷路径直接调用。
const nmgInteractiveMenu = async (ctx: ExtensionCommandContext): Promise<void> => {
if (!ctx.hasUI || typeof ctx.ui.select !== "function") {
ctx.ui.notify(
"当前模式不支持交互菜单,可用:/nmg recall、/nmg wake on|off|status|budget N|cooldown M|interval S",
"warning",
);
return;
}
const choice = await ctx.ui.select(
"NMG 控制台",
nmgMenuOptions(recallCollapsed, readWakeConfig()),
);
if (!choice) return; // 用户取消
if (choice.startsWith("召回")) {
await nmgMenuHandler("recall", ctx);
return;
}
if (choice.startsWith("唤醒:开启/关闭")) {
const onOff = await ctx.ui.select("黑板唤醒", ["开启", "关闭"]);
if (!onOff) return;
const current = readWakeConfig();
writeWakeConfig({ ...current, enabled: onOff === "开启" });
ctx.ui.notify(onOff === "开启" ? "黑板唤醒已开启" : "黑板唤醒已关闭", "info");
return;
}
if (choice.startsWith("唤醒:世界广播")) {
const onOff = await ctx.ui.select("世界频道协作广播", ["开启", "关闭"]);
if (!onOff) return;
const current = readWakeConfig();
writeWakeConfig({ ...current, worldBroadcast: onOff === "开启" });
ctx.ui.notify(
onOff === "开启"
? "已开启世界频道协作广播:协作类新条目会同时发到世界频道拉其他 agent"
: "已关闭世界频道协作广播",
"info",
);
return;
}
if (choice.startsWith("唤醒:参数设置")) {
// 挡位选择(select 预设值,避免随意输入溢出/无效);自由数字只走命令形式
// (/nmg wake budget N 等,已有 clamp)。
const param = await ctx.ui.select("唤醒参数", ["每日上限", "冷却", "轮询"]);
if (!param) return;
const current = readWakeConfig();
if (param === "每日上限") {
const option = await ctx.ui.select("每日唤醒上限", ["不限制", "3/天", "8/天", "15/天"]);
if (!option) return;
const budget = option === "不限制" ? 0 : Number(option.split("/")[0]);
writeWakeConfig({ ...current, budget });
ctx.ui.notify(`黑板唤醒预算已设为 ${budget === 0 ? "不限制" : `${budget}/天`}`, "info");
} else if (param === "冷却") {
const option = await ctx.ui.select("冷却", ["无", "10 分钟", "30 分钟", "1 小时"]);
if (!option) return;
const cooldownMs =
option === "无"
? 0
: option === "10 分钟"
? 600_000
: option === "30 分钟"
? 1_800_000
: 3_600_000;
writeWakeConfig({ ...current, cooldownMs });
ctx.ui.notify(
`黑板唤醒冷却已设为 ${cooldownMs === 0 ? "无" : `${Math.round(cooldownMs / 60_000)} 分钟`}`,
"info",
);
} else if (param === "轮询") {
const option = await ctx.ui.select("轮询", ["5 秒", "30 秒", "60 秒", "5 分钟"]);
if (!option) return;
const intervalMs =
option === "5 秒"
? 5_000
: option === "30 秒"
? 30_000
: option === "60 秒"
? 60_000
: 300_000;
writeWakeConfig({ ...current, intervalMs });
ctx.ui.notify(`黑板唤醒轮询已设为 ${Math.round(intervalMs / 1_000)} 秒`, "info");
}
return;
}
{
const wake = readWakeConfig();
const budgetText = wake.budget === 0 ? "不限制" : `${wake.budget}/天`;
const cooldownText =
wake.cooldownMs === 0 ? "无" : `${Math.round(wake.cooldownMs / 60_000)} 分`;
ctx.ui.notify(
`NMG:召回折叠 ${recallCollapsed ? "开" : "关"} · 唤醒 ${wake.enabled ? "开" : "关"}(预算 ${budgetText},冷却 ${cooldownText},轮询 ${Math.round(wake.intervalMs / 1_000)} 秒)`,
"info",
);
}
};

Check notice on line 654 in .pi/extensions/nmg/index.ts

View check run for this annotation

codefactor.io / CodeFactor

.pi/extensions/nmg/index.ts#L562-L654

Complex Method
// 菜单项带当前状态,进子菜单前就看得见:召回折叠/展开、唤醒开/关、参数当前值。
// 预算/冷却支持 0(不限制/无),轮询保持最小 5 秒(0 无意义)。
const nmgMenuOptions = (
Expand Down Expand Up @@ -1053,51 +1052,6 @@
},
});

// The restricted out-of-order round, registered by default like the other NMG tools: a
// consumer that has to be switched on is not one. The cost boundary is per call: `live` spends
// tokens, while the default replays recorded answers and spends nothing.
pi.registerTool({
name: "ooo_round",
label: "Restricted OoO round",
description:
"Drive one restricted out-of-order round. " +
"action=submit starts the round detached from a spec JSON and returns immediately (a live " +
"round takes minutes and spends tokens); action=status reads the round's own run directory " +
"from another process; action=cancel records an operator decision that survives a restart. " +
"The coordinator, not this tool, decides acceptance.",
parameters: Type.Object({
action: Type.Union([Type.Literal("submit"), Type.Literal("status"), Type.Literal("cancel")]),
specPath: Type.Optional(
Type.String({ description: "submit only: path to the round spec JSON" }),
),
runDir: Type.Optional(
Type.String({ description: "the round's own directory; required for every action" }),
),
reason: Type.Optional(Type.String({ description: "cancel only: why the round was stopped" })),
live: Type.Optional(
Type.Boolean({
description:
"submit only: use real model calls (costs tokens); absent means recorded answers",
}),
),
}),
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
const request = {
action: params.action as OooRoundAction,
specPath: params.specPath,
runDir: params.runDir,
reason: params.reason,
live: params.live,
};
if (request.action === "submit") {
const started = startRound(projectDirectory(), request);
return toolResult(started, describeStart(request, started));
}
const reply = await shortRound(projectDirectory(), request);
return toolResult(reply, `round ${reply.action}: exit ${reply.code}\n${reply.output}`);
},
});

pi.registerTool({
name: "nmg_remember",
label: "Remember with NMG",
Expand Down
Loading
Loading