@aibyzero/byz 0.1.6 → 0.1.9

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cli.js +3 -16
  3. package/dist/conversation/conversation-extension.js +402 -0
  4. package/dist/conversation/interaction-policy.js +53 -0
  5. package/dist/conversation/routing-policy.js +102 -0
  6. package/dist/runtime/bundle/chunks/{chunk-SKWR5IN4.js → chunk-IUR2NDXL.js} +7 -7
  7. package/dist/runtime/bundle/cli.js +1 -1
  8. package/dist/runtime/bundle/index.js +1 -1
  9. package/dist/runtime/bundle/rpc-entry.js +1 -1
  10. package/dist/runtime/config.d.ts.map +1 -1
  11. package/dist/runtime/config.js +3 -3
  12. package/dist/runtime/config.js.map +1 -1
  13. package/dist/runtime/core/extensions/index.d.ts +1 -1
  14. package/dist/runtime/core/extensions/index.d.ts.map +1 -1
  15. package/dist/runtime/core/extensions/index.js.map +1 -1
  16. package/dist/runtime/core/extensions/runner.d.ts.map +1 -1
  17. package/dist/runtime/core/extensions/runner.js +3 -0
  18. package/dist/runtime/core/extensions/runner.js.map +1 -1
  19. package/dist/runtime/core/extensions/types.d.ts +17 -0
  20. package/dist/runtime/core/extensions/types.d.ts.map +1 -1
  21. package/dist/runtime/core/extensions/types.js.map +1 -1
  22. package/dist/runtime/modes/interactive/interactive-mode.d.ts +5 -0
  23. package/dist/runtime/modes/interactive/interactive-mode.d.ts.map +1 -1
  24. package/dist/runtime/modes/interactive/interactive-mode.js +59 -6
  25. package/dist/runtime/modes/interactive/interactive-mode.js.map +1 -1
  26. package/dist/runtime/modes/rpc/rpc-mode.d.ts.map +1 -1
  27. package/dist/runtime/modes/rpc/rpc-mode.js +9 -0
  28. package/dist/runtime/modes/rpc/rpc-mode.js.map +1 -1
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.9 - 2026-08-30
6
+
7
+ ### Changed
8
+
9
+ - Changed the default BYZ terminal footer to show the project name, git branch, remaining context percentage, token usage, cost, extension statuses, and current model.
10
+ - Changed long-running BYZ waits to show a compact non-technical progress card by default, with the fuller work-site card available in details mode.
11
+ - Added persistent BYZ detail-mode preferences through `/details remember` and `/details remember compact`.
12
+
13
+ ## 0.1.7 - 2026-08-29
14
+
15
+ ### Added
16
+
17
+ - Added same-session routing and collaboration preferences that classify common requests, inject minimal per-turn guidance, and show route details only on demand.
18
+ - Added the default BYZ conversation shell with a goal-first welcome, low-noise progress, on-demand details, and natural-language confirmation input.
19
+
20
+ ### Changed
21
+
22
+ - Changed the default interactive shell to hide internal resources, tool rows, model metadata, and advanced controls until requested.
23
+
5
24
  ## 0.1.6 - 2026-08-28
6
25
 
7
26
  ### Added
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { createConversationExtension } from "./conversation/conversation-extension.js";
3
4
  import { createFastSessionController, prepareFastRuntimeArgs, selectFastRuntimeArgs } from "./fast.js";
4
5
  import { createPrewalkExtension } from "./prewalk.js";
5
6
  import { main } from "./runtime/bundle/index.js";
@@ -25,16 +26,6 @@ try {
25
26
  const fastRuntime = prepareFastRuntimeArgs(args);
26
27
  const parsedWorkflow = parseWorkflowOption(fastRuntime.commandArgs);
27
28
  const commandArgs = parsedWorkflow.forwardedArgs;
28
- const isRootHelp = commandArgs.length === 1 && (commandArgs[0] === "--help" || commandArgs[0] === "-h");
29
- if (isRootHelp) {
30
- console.error("BYZ updates: byz update (npm-managed global installations only)");
31
- console.error("BYZ Fast: --fast (thinking=low; optional model: BYZ_FAST_MODEL)");
32
- console.error("BYZ Prewalk: /prewalk (one-time handoff after the first successful workspace edit/write)");
33
- console.error("BYZ workflows: --workflow <cm|cm-plugin|none> (default: BYZ_WORKFLOW or cm)");
34
- console.error(
35
- "Commands: byz workflow list | byz workflow status [cm|cm-plugin|none] | byz workflow check <cm|cm-plugin>",
36
- );
37
- }
38
29
 
39
30
  if (await handleWorkflowCommand(commandArgs, { workflowId: parsedWorkflow.workflowId })) {
40
31
  // BYZ-owned command handled without starting the Pi runtime.
@@ -47,12 +38,6 @@ try {
47
38
  stdoutIsTTY: process.stdout.isTTY,
48
39
  });
49
40
  const runtimeArgs = selectFastRuntimeArgs(fastRuntime, { isInteractive, loadWorkflow });
50
- if (fastRuntime.enabled && loadWorkflow && isInteractive) {
51
- console.error(
52
- `BYZ Fast: model=${fastRuntime.model}, thinking=${fastRuntime.thinking}, workflow=${parsedWorkflow.workflowId}`,
53
- );
54
- }
55
-
56
41
  if (loadWorkflow && isInteractive) {
57
42
  const parsedRuntimeWorkflow = parseWorkflowOption(runtimeArgs);
58
43
  const resolveResources = (workflowId) =>
@@ -68,7 +53,9 @@ try {
68
53
  initialUseLowThinking: fastRuntime.useLowThinking,
69
54
  });
70
55
  const prewalkExtension = createPrewalkExtension({ fastController });
56
+ const conversationExtension = createConversationExtension();
71
57
  const byzExtension = (pi) => {
58
+ conversationExtension(pi);
72
59
  workflowExtension(pi);
73
60
  fastController.extension(pi);
74
61
  prewalkExtension(pi);
@@ -0,0 +1,402 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { createInteractionPolicy, formatDecision, parseConversationControl } from "./interaction-policy.js";
5
+ import { createRoutingPolicy } from "./routing-policy.js";
6
+
7
+ const WELCOME = "BYZ\n\n你想让我帮你做什么?";
8
+ const DETAIL_MODE_COMPACT = "compact";
9
+ const DETAIL_MODE_DETAILS = "details";
10
+
11
+ function getByzAgentDir() {
12
+ return process.env.BYZ_CODING_AGENT_DIR || join(homedir(), ".byz", "agent");
13
+ }
14
+
15
+ function getConversationConfigPath() {
16
+ return join(getByzAgentDir(), "conversation.json");
17
+ }
18
+
19
+ function readConversationConfig() {
20
+ try {
21
+ return JSON.parse(readFileSync(getConversationConfigPath(), "utf8"));
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+
27
+ function getSavedDetailMode() {
28
+ const mode = readConversationConfig().detailMode;
29
+ return mode === DETAIL_MODE_DETAILS ? DETAIL_MODE_DETAILS : DETAIL_MODE_COMPACT;
30
+ }
31
+
32
+ function saveDetailMode(mode) {
33
+ const configPath = getConversationConfigPath();
34
+ mkdirSync(dirname(configPath), { recursive: true });
35
+ writeFileSync(configPath, `${JSON.stringify({ ...readConversationConfig(), detailMode: mode }, null, "\t")}\n`);
36
+ }
37
+
38
+ function formatTokens(count) {
39
+ if (count < 1000) return String(count);
40
+ if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
41
+ if (count < 1000000) return `${Math.round(count / 1000)}k`;
42
+ if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
43
+ return `${Math.round(count / 1000000)}M`;
44
+ }
45
+
46
+ function truncateText(text, width) {
47
+ if (width <= 0) return "";
48
+ if (text.length <= width) return text;
49
+ if (width <= 1) return "…".slice(0, width);
50
+ return `${text.slice(0, width - 1)}…`;
51
+ }
52
+
53
+ function findProjectRoot(cwd) {
54
+ let dir = cwd;
55
+ while (dir) {
56
+ if (existsSync(join(dir, ".git"))) return dir;
57
+ const parent = dirname(dir);
58
+ if (parent === dir) return cwd;
59
+ dir = parent;
60
+ }
61
+ return cwd;
62
+ }
63
+
64
+ function getProjectName(cwd) {
65
+ return basename(findProjectRoot(cwd)) || basename(cwd) || cwd;
66
+ }
67
+
68
+ function addUsage(totals, usage) {
69
+ if (!usage) return;
70
+ totals.input += usage.input ?? 0;
71
+ totals.output += usage.output ?? 0;
72
+ totals.cacheRead += usage.cacheRead ?? 0;
73
+ totals.cacheWrite += usage.cacheWrite ?? 0;
74
+ totals.cost += usage.cost?.total ?? 0;
75
+ }
76
+
77
+ function getUsageTotals(ctx) {
78
+ const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
79
+ for (const entry of ctx.sessionManager?.getEntries?.() ?? []) {
80
+ if (entry.type === "message" && entry.message?.role === "assistant") {
81
+ addUsage(totals, entry.message.usage);
82
+ } else if (entry.type === "message" && entry.message?.role === "toolResult") {
83
+ addUsage(totals, entry.message.usage);
84
+ } else if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
85
+ addUsage(totals, entry.usage);
86
+ }
87
+ }
88
+ return totals;
89
+ }
90
+
91
+ function shortModelName(modelId) {
92
+ if (!modelId) return "no-model";
93
+ return modelId
94
+ .replace(/^claude-/, "")
95
+ .replace(/^gpt-/, "gpt-")
96
+ .replace(/-20\d{6}$/, "")
97
+ .replace(/-latest$/, "");
98
+ }
99
+
100
+ function createByzFooter(ctx, tui, theme, footerData) {
101
+ const unsubscribe = footerData.onBranchChange?.(() => tui.requestRender?.());
102
+ return {
103
+ invalidate() {},
104
+ dispose() {
105
+ unsubscribe?.();
106
+ },
107
+ render(width) {
108
+ const safeWidth = Math.max(1, width ?? 80);
109
+ const cwd = ctx.sessionManager?.getCwd?.() ?? ctx.cwd ?? process.cwd();
110
+ const parts = [getProjectName(cwd)];
111
+ const branch = footerData.getGitBranch?.();
112
+ if (branch) parts.push(branch);
113
+
114
+ const contextUsage = ctx.getContextUsage?.();
115
+ if (contextUsage) {
116
+ const left = contextUsage.percent === null ? "?" : `${Math.max(0, 100 - contextUsage.percent).toFixed(0)}%`;
117
+ parts.push(`left ${left}`);
118
+ }
119
+
120
+ const usage = getUsageTotals(ctx);
121
+ if (usage.input > 0) parts.push(`↑${formatTokens(usage.input)}`);
122
+ if (usage.output > 0) parts.push(`↓${formatTokens(usage.output)}`);
123
+ if (usage.cacheRead > 0) parts.push(`R${formatTokens(usage.cacheRead)}`);
124
+ if (usage.cacheWrite > 0) parts.push(`W${formatTokens(usage.cacheWrite)}`);
125
+ if (usage.cost > 0) parts.push(`$${usage.cost.toFixed(3)}`);
126
+
127
+ const extensionStatuses = footerData.getExtensionStatuses?.();
128
+ for (const text of extensionStatuses?.values?.() ?? []) {
129
+ const clean = String(text)
130
+ .replace(/[\r\n\t]/g, " ")
131
+ .replace(/ +/g, " ")
132
+ .trim();
133
+ if (clean) parts.push(clean);
134
+ }
135
+
136
+ const leftText = parts.join(" ");
137
+ const modelText = shortModelName(ctx.model?.id);
138
+ const minGap = 2;
139
+ let line;
140
+ if (leftText.length + minGap + modelText.length <= safeWidth) {
141
+ line = `${leftText}${" ".repeat(safeWidth - leftText.length - modelText.length)}${modelText}`;
142
+ } else {
143
+ const modelBudget = Math.min(modelText.length, Math.max(0, safeWidth - minGap - 12));
144
+ const model = truncateText(modelText, modelBudget);
145
+ const leftBudget = Math.max(1, safeWidth - minGap - model.length);
146
+ line = `${truncateText(leftText, leftBudget)}${" ".repeat(Math.max(minGap, safeWidth - leftBudget - model.length))}${model}`;
147
+ }
148
+ return [theme.fg?.("dim", line) ?? line];
149
+ },
150
+ };
151
+ }
152
+
153
+ function createProgressState() {
154
+ return {
155
+ goal: "当前任务",
156
+ stage: "确认目标与边界",
157
+ confirmed: [],
158
+ judgements: [],
159
+ nextSteps: ["完成必要检查", "整理结果给你"],
160
+ safeguards: ["不会提交代码", "不会执行高影响动作"],
161
+ tools: { inspected: 0, edited: 0, commands: 0 },
162
+ visible: false,
163
+ };
164
+ }
165
+
166
+ function pushUnique(list, value, limit = 3) {
167
+ if (!value || list.includes(value)) return;
168
+ list.push(value);
169
+ if (list.length > limit) list.shift();
170
+ }
171
+
172
+ function summarizeGoal(prompt) {
173
+ const clean = String(prompt ?? "")
174
+ .replace(/[\r\n\t]/g, " ")
175
+ .replace(/展开细节[,,;;\s]*/g, "")
176
+ .replace(/ +/g, " ")
177
+ .trim();
178
+ if (!clean) return "当前任务";
179
+ return clean.length > 28 ? `${clean.slice(0, 27)}…` : clean;
180
+ }
181
+
182
+ function getActivitySummary(state) {
183
+ const activity = [];
184
+ if (state.tools.inspected > 0) activity.push(`查看 ${state.tools.inspected} 项`);
185
+ if (state.tools.edited > 0) activity.push(`修改 ${state.tools.edited} 项`);
186
+ if (state.tools.commands > 0) activity.push(`命令 ${state.tools.commands} 次`);
187
+ return activity.join(",");
188
+ }
189
+
190
+ function renderProgressCard(state, options = {}) {
191
+ const activity = getActivitySummary(state);
192
+ if (options.compact) {
193
+ const progress = activity || state.confirmed.at(-1) || state.stage;
194
+ const next = state.nextSteps.at(-1) ?? "整理结果给你";
195
+ const boundary = state.safeguards.at(-1) ?? "不会提交代码";
196
+ return [`处理中:${state.goal}`, `进展:${progress}`, `下一步:${next}`, `边界:${boundary}`].join("\n");
197
+ }
198
+
199
+ const lines = [`正在处理:${state.goal}`, `当前阶段:${state.stage}`];
200
+ if (activity) lines.push(`现场进展:${activity}`);
201
+ if (state.confirmed.length > 0) lines.push(`已确认:${state.confirmed.join(";")}`);
202
+ if (state.judgements.length > 0) lines.push(`当前判断:${state.judgements.join(";")}`);
203
+ if (state.nextSteps.length > 0) lines.push(`下一步:${state.nextSteps.join(";")}`);
204
+ if (state.safeguards.length > 0) lines.push(`不会做:${state.safeguards.join(";")}`);
205
+ return lines.join("\n");
206
+ }
207
+
208
+ function stageForTool(toolName) {
209
+ if (["read", "grep", "find", "ls"].includes(toolName)) return "定位和核对相关材料";
210
+ if (["edit", "write"].includes(toolName)) return "执行最小必要修改";
211
+ if (["bash", "powershell"].includes(toolName)) return "运行命令并核对结果";
212
+ return "处理必要步骤";
213
+ }
214
+
215
+ function updateProgressFromToolStart(state, toolName) {
216
+ state.stage = stageForTool(toolName);
217
+ if (["read", "grep", "find", "ls"].includes(toolName)) {
218
+ pushUnique(state.nextSteps, "基于证据判断方案");
219
+ } else if (["edit", "write"].includes(toolName)) {
220
+ pushUnique(state.judgements, "优先做小改动,避免扩大范围");
221
+ pushUnique(state.nextSteps, "补充验证");
222
+ } else if (["bash", "powershell"].includes(toolName)) {
223
+ pushUnique(state.nextSteps, "根据命令结果决定是否继续");
224
+ }
225
+ }
226
+
227
+ function updateProgressFromToolEnd(state, toolName, isError) {
228
+ if (["read", "grep", "find", "ls"].includes(toolName)) {
229
+ state.tools.inspected += 1;
230
+ pushUnique(state.confirmed, "已查看相关项目资料");
231
+ } else if (["edit", "write"].includes(toolName)) {
232
+ state.tools.edited += 1;
233
+ pushUnique(state.confirmed, isError ? "修改步骤需要复核" : "已完成代码层面的变更");
234
+ } else if (["bash", "powershell"].includes(toolName)) {
235
+ state.tools.commands += 1;
236
+ pushUnique(state.confirmed, isError ? "命令结果需要处理" : "已执行验证命令");
237
+ }
238
+ if (isError) {
239
+ state.stage = "处理异常结果";
240
+ pushUnique(state.judgements, "先解释失败原因,再决定是否调整");
241
+ return;
242
+ }
243
+ state.stage = "继续核对并收敛结果";
244
+ }
245
+
246
+ export function createConversationExtension(options = {}) {
247
+ const policy = createInteractionPolicy();
248
+ const routingPolicy = createRoutingPolicy();
249
+ const progressCardDelayMs = options.progressCardDelayMs ?? 8_000;
250
+ let savedDetailMode = getSavedDetailMode();
251
+
252
+ return function conversationExtension(pi) {
253
+ let progressTimer;
254
+ let progressState = createProgressState();
255
+ let activeCtx;
256
+
257
+ function clearProgressTimer() {
258
+ if (progressTimer) clearTimeout(progressTimer);
259
+ progressTimer = undefined;
260
+ }
261
+
262
+ function publishProgress() {
263
+ if (!activeCtx) return;
264
+ progressState.visible = true;
265
+ activeCtx.ui.setWorkingMessage?.(renderProgressCard(progressState, { compact: !policy.isDetailEnabled() }));
266
+ }
267
+
268
+ function updateVisibleProgress() {
269
+ if (progressState.visible) publishProgress();
270
+ }
271
+
272
+ pi.on("session_start", (_event, ctx) => {
273
+ routingPolicy.reset();
274
+ policy.setDetailEnabled(savedDetailMode === DETAIL_MODE_DETAILS);
275
+ ctx.ui.setTitle?.("BYZ");
276
+ ctx.ui.setMessagePresenter?.((message) => policy.presentAssistantMessage(message));
277
+ ctx.ui.setToolExecutionVisible?.(policy.isDetailEnabled());
278
+ ctx.ui.setFooter?.((tui, theme, footerData) => createByzFooter(ctx, tui, theme, footerData));
279
+ ctx.ui.setConfirmationPresenter?.(async ({ title, message, confirm }) => {
280
+ const prompt = formatDecision({
281
+ impact: message,
282
+ recommendation: "确认",
283
+ alternative: "取消",
284
+ onReject: "不会执行此操作",
285
+ });
286
+ const answer = await ctx.ui.input(prompt, `${title}:输入“确认”或“取消”`);
287
+ const choice = answer ? parseConversationControl(answer) : undefined;
288
+ if (choice === "accept" || choice === "proceed") return true;
289
+ if (choice === "reject") return false;
290
+ return confirm();
291
+ });
292
+ ctx.ui.notify(WELCOME, "info");
293
+ });
294
+ pi.on("agent_start", (_event, ctx) => {
295
+ activeCtx = ctx;
296
+ policy.resetProgress();
297
+ progressState.visible = false;
298
+ clearProgressTimer();
299
+ ctx.ui.setWorkingMessage?.("正在确认目标与边界…");
300
+ progressTimer = setTimeout(() => {
301
+ publishProgress();
302
+ }, progressCardDelayMs);
303
+ });
304
+ pi.on("tool_execution_start", (event) => {
305
+ updateProgressFromToolStart(progressState, event.toolName);
306
+ updateVisibleProgress();
307
+ });
308
+ pi.on("tool_execution_end", (event) => {
309
+ updateProgressFromToolEnd(progressState, event.toolName, event.isError);
310
+ updateVisibleProgress();
311
+ });
312
+ pi.on("message_update", (event) => {
313
+ if (event.message?.role !== "assistant") return;
314
+ progressState.stage = "组织回复";
315
+ pushUnique(progressState.nextSteps, "给出结论和已做验证");
316
+ updateVisibleProgress();
317
+ });
318
+ pi.on("agent_end", () => {
319
+ clearProgressTimer();
320
+ activeCtx?.ui.setWorkingMessage?.();
321
+ activeCtx = undefined;
322
+ });
323
+ pi.on("session_shutdown", () => {
324
+ routingPolicy.reset();
325
+ clearProgressTimer();
326
+ activeCtx?.ui.setWorkingMessage?.();
327
+ activeCtx = undefined;
328
+ });
329
+ function applyDetailMode(ctx, mode, options = {}) {
330
+ policy.setDetailEnabled(mode === DETAIL_MODE_DETAILS);
331
+ ctx.ui.setToolExecutionVisible?.(policy.isDetailEnabled());
332
+ if (options.remember) {
333
+ saveDetailMode(mode);
334
+ savedDetailMode = mode;
335
+ }
336
+ if (mode === DETAIL_MODE_DETAILS) {
337
+ const scope = options.remember ? "已设为所有会话默认" : "仅当前会话";
338
+ ctx.ui.notify(`已展开细节(${scope})。高级控制:/fast、/prewalk、/workflow。`, "info");
339
+ return;
340
+ }
341
+ const scope = options.remember ? "已设为所有会话默认" : "仅当前会话";
342
+ ctx.ui.notify(`已切回紧凑模式(${scope})。`, "info");
343
+ }
344
+
345
+ function handleDetailsCommand(args, ctx) {
346
+ const action = String(args ?? "")
347
+ .trim()
348
+ .toLowerCase();
349
+ if (!action || action === "on") {
350
+ applyDetailMode(ctx, DETAIL_MODE_DETAILS);
351
+ return;
352
+ }
353
+ if (["off", "compact"].includes(action)) {
354
+ applyDetailMode(ctx, DETAIL_MODE_COMPACT);
355
+ return;
356
+ }
357
+ if (["remember", "save", "details"].includes(action)) {
358
+ applyDetailMode(ctx, DETAIL_MODE_DETAILS, { remember: true });
359
+ return;
360
+ }
361
+ if (["remember compact", "save compact", "compact remember"].includes(action)) {
362
+ applyDetailMode(ctx, DETAIL_MODE_COMPACT, { remember: true });
363
+ return;
364
+ }
365
+ if (action === "status") {
366
+ const current = policy.isDetailEnabled() ? DETAIL_MODE_DETAILS : DETAIL_MODE_COMPACT;
367
+ ctx.ui.notify(`当前:${current}。默认:${savedDetailMode}。`, "info");
368
+ return;
369
+ }
370
+ ctx.ui.notify("用法:/details [on|off|remember|remember compact|status]", "warning");
371
+ }
372
+
373
+ pi.registerCommand("details", {
374
+ description: "Configure BYZ detail mode",
375
+ handler: async (args, ctx) => handleDetailsCommand(args, ctx),
376
+ });
377
+ pi.on("before_agent_start", async (event, ctx) => {
378
+ const route = routingPolicy.route(event.prompt);
379
+ progressState = createProgressState();
380
+ progressState.goal = summarizeGoal(event.prompt);
381
+ pushUnique(progressState.confirmed, "已收到目标");
382
+ if (route.kind !== "general") pushUnique(progressState.judgements, `任务类型:${route.kind}`);
383
+ if (route.preferences.autonomy === "confirm-key-actions") {
384
+ pushUnique(progressState.safeguards, "关键动作会先确认");
385
+ }
386
+ if (route.details || parseConversationControl(event.prompt) === "detail") {
387
+ applyDetailMode(ctx, DETAIL_MODE_DETAILS);
388
+ }
389
+ if (policy.isDetailEnabled()) {
390
+ ctx.ui.notify(
391
+ `当前类别:${route.kind}。当前偏好:主动程度 ${route.preferences.autonomy},交付 ${route.preferences.delivery}。`,
392
+ "info",
393
+ );
394
+ }
395
+ return {
396
+ systemPrompt: `${event.systemPrompt ?? ""}\n\nBYZ collaboration guidance for this turn:\n${route.instructions}`,
397
+ };
398
+ });
399
+ };
400
+ }
401
+
402
+ export { WELCOME };
@@ -0,0 +1,53 @@
1
+ const INTERNAL_TERMS = /\b(?:model|skill|workflow|fast|prewalk|token|tool|step\s*\d+)\b/gi;
2
+
3
+ export function createInteractionPolicy() {
4
+ let progressShown = false;
5
+ let detailEnabled = false;
6
+
7
+ function present(kind, message, { detail = false } = {}) {
8
+ if (kind === "progress") {
9
+ if (progressShown) return undefined;
10
+ progressShown = true;
11
+ return "正在处理,稍后给你结果。";
12
+ }
13
+ if (kind === "advanced-control" && !detailEnabled && !detail) return undefined;
14
+ if (detail || detailEnabled) return message;
15
+ return message.replace(INTERNAL_TERMS, "内部设置");
16
+ }
17
+
18
+ function presentAssistantMessage(message) {
19
+ if (detailEnabled) return message;
20
+ const content = message.content
21
+ .filter((part) => part.type === "text")
22
+ .map((part) => ({ ...part, text: part.text.replace(INTERNAL_TERMS, "内部设置") }));
23
+ return { ...message, content };
24
+ }
25
+
26
+ return {
27
+ present,
28
+ presentAssistantMessage,
29
+ setDetailEnabled(enabled) {
30
+ detailEnabled = enabled;
31
+ },
32
+ isDetailEnabled() {
33
+ return detailEnabled;
34
+ },
35
+ resetProgress() {
36
+ progressShown = false;
37
+ },
38
+ };
39
+ }
40
+
41
+ export function parseConversationControl(input) {
42
+ const value = input.trim();
43
+ if (/^(展开细节|查看细节|显示细节)$/.test(value)) return "detail";
44
+ if (/^(少问一点|直接做)$/.test(value)) return "proceed";
45
+ if (/^(关键动作先问我|先问我)$/.test(value)) return "confirm";
46
+ if (/^(取消|不要|拒绝)$/.test(value)) return "reject";
47
+ if (/^(确认|继续|同意|可以)$/.test(value)) return "accept";
48
+ return undefined;
49
+ }
50
+
51
+ export function formatDecision({ impact, recommendation, alternative, onReject }) {
52
+ return `需要你决定\n影响:${impact}\n建议:${recommendation}\n其他选择:${alternative}\n如果拒绝:${onReject}`;
53
+ }
@@ -0,0 +1,102 @@
1
+ const DEFAULT_PREFERENCES = Object.freeze({
2
+ autonomy: "balanced",
3
+ delivery: "normal",
4
+ });
5
+
6
+ const CONTROL_PATTERNS = [
7
+ [/关键动作先问我|先问我/, { autonomy: "confirm-key-actions" }],
8
+ [/少问一点/, { autonomy: "fewer-questions" }],
9
+ [/直接做/, { autonomy: "direct" }],
10
+ [/先给三个方向/, { delivery: "three-directions" }],
11
+ [/展开细节|查看细节|显示细节/, {}],
12
+ ];
13
+
14
+ const FALLBACKS = Object.freeze({
15
+ research: {
16
+ missingInput: "需要可访问链接、正文、关键词或目标来源。",
17
+ fallback: "可先基于你提供的摘要做初步判断,或请你补充正文、截图、链接。",
18
+ },
19
+ "bug-fix": {
20
+ missingInput: "需要复现步骤、报错信息、相关输入或运行环境。",
21
+ fallback: "可先根据现象列排查清单,等你补充日志后再定位。",
22
+ },
23
+ "project-recovery": {
24
+ missingInput: "需要当前项目路径、上次进度、任务状态或错误现场。",
25
+ fallback: "可先读取本地项目状态并汇总可恢复线索。",
26
+ },
27
+ general: {
28
+ missingInput: "需要更明确的目标、输入材料或期望输出。",
29
+ fallback: "可先给出可选方向或最小可执行下一步。",
30
+ },
31
+ });
32
+
33
+ function classify(goal) {
34
+ if (/https?:\/\/|链接|帖子|查一下|调研|研究/.test(goal)) return "research";
35
+ if (/三个方向|创意|设计|文案|写作|起个名字/.test(goal)) return "creative";
36
+ if (/报错|bug|缺陷|无法复现|修复/.test(goal)) return "bug-fix";
37
+ if (/新功能|实现|开发|增加.*功能|添加.*功能/.test(goal)) return "feature";
38
+ if (/继续.*项目|恢复.*项目|上次.*停/.test(goal)) return "project-recovery";
39
+ return "general";
40
+ }
41
+
42
+ function instructionsFor(kind, preferences) {
43
+ const collaboration = [
44
+ preferences.autonomy === "direct" ? "在安全且可逆的范围内直接推进,不要先要求用户选择内部模式。" : undefined,
45
+ preferences.autonomy === "fewer-questions" ? "仅在缺少会明显改变结果的关键信息时提一个问题。" : undefined,
46
+ preferences.autonomy === "confirm-key-actions" ? "关键动作前说明影响并请求确认。" : undefined,
47
+ preferences.delivery === "three-directions" ? "先给出三个可区分的方向,再建议一个推荐方向。" : undefined,
48
+ ].filter(Boolean);
49
+ const task = {
50
+ research: "优先说明来源是否可访问;无法取得内容时说明缺失并建议用户提供正文或替代来源。",
51
+ creative: "先给出可见的创作骨架或方向,避免暴露内部能力名称。",
52
+ "bug-fix": "先以可复现证据定位;无法复现时说明缺少的环境、输入或日志。",
53
+ feature: "先确认目标与范围;涉及高影响动作时保持既有确认边界。",
54
+ "project-recovery": "只在当前会话可见信息足够时恢复上下文;否则说明需要的项目状态。",
55
+ general: "直接用自然语言处理目标;资料不足时说明未完成部分和可行替代路径。",
56
+ }[kind];
57
+ return [...collaboration, task].join("\n");
58
+ }
59
+
60
+ export function classifyRequest(prompt, preferences = DEFAULT_PREFERENCES) {
61
+ const kind = classify(prompt.trim());
62
+ return {
63
+ fallback: FALLBACKS[kind]?.fallback,
64
+ instructions: instructionsFor(kind, preferences),
65
+ kind,
66
+ missingInput: FALLBACKS[kind]?.missingInput,
67
+ };
68
+ }
69
+
70
+ export function parseSessionPreference(input) {
71
+ const preferences = {};
72
+ const details = /展开细节|查看细节|显示细节/.test(input);
73
+ let goal = input;
74
+ for (const [pattern, changes] of CONTROL_PATTERNS) {
75
+ if (pattern.test(goal)) Object.assign(preferences, changes);
76
+ goal = goal.replace(pattern, "");
77
+ }
78
+ return { details, goal: goal.replace(/[,,。;;]+/g, " ").trim(), preferences };
79
+ }
80
+
81
+ export const parseSessionPreferences = parseSessionPreference;
82
+
83
+ export function createRoutingPolicy() {
84
+ let preferences = { ...DEFAULT_PREFERENCES };
85
+
86
+ return {
87
+ route(input) {
88
+ const parsed = parseSessionPreference(input);
89
+ preferences = { ...preferences, ...parsed.preferences };
90
+ const route = classifyRequest(parsed.goal, preferences);
91
+ return {
92
+ ...route,
93
+ details: parsed.details,
94
+ goal: parsed.goal,
95
+ preferences: { ...preferences },
96
+ };
97
+ },
98
+ reset() {
99
+ preferences = { ...DEFAULT_PREFERENCES };
100
+ },
101
+ };
102
+ }