mixdog 0.9.0 → 0.9.2
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/package.json +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
import sliceAnsi from 'slice-ansi';
|
|
2
|
-
import stringWidth from 'string-width';
|
|
3
2
|
import { styledCharsFromTokens, styledCharsToString, tokenize, } from '@alcalzone/ansi-tokenize';
|
|
3
|
+
// [mixdog fork] use the shared display-width policy so ink's per-character
|
|
4
|
+
// advance + width cache treat circled digits / arrows as 2 cells under Windows
|
|
5
|
+
// Terminal, matching OUR wrap/row math. See display-width.js (kept in sync with
|
|
6
|
+
// src/tui/display-width.mjs).
|
|
7
|
+
import { displayStringWidth as stringWidth } from './display-width.js';
|
|
8
|
+
const RGB_COLOR_RE = /^rgb\(\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\s*\)$/;
|
|
9
|
+
const rgbStyle = (value, type, fallback) => {
|
|
10
|
+
const match = RGB_COLOR_RE.exec(String(value || ''));
|
|
11
|
+
const parts = match
|
|
12
|
+
? match.slice(1, 4).map(part => Math.max(0, Math.min(255, Number(part) || 0)))
|
|
13
|
+
: fallback;
|
|
14
|
+
const sgr = type === 'foreground' ? 38 : 48;
|
|
15
|
+
const reset = type === 'foreground' ? '\x1b[39m' : '\x1b[49m';
|
|
16
|
+
return { code: `\x1b[${sgr};2;${parts[0]};${parts[1]};${parts[2]}m`, endCode: reset };
|
|
17
|
+
};
|
|
18
|
+
const intersectClips = (clips) => {
|
|
19
|
+
if (clips.length === 0) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const clip of clips) {
|
|
24
|
+
if (typeof clip?.x1 === 'number') {
|
|
25
|
+
out.x1 = typeof out.x1 === 'number' ? Math.max(out.x1, clip.x1) : clip.x1;
|
|
26
|
+
}
|
|
27
|
+
if (typeof clip?.x2 === 'number') {
|
|
28
|
+
out.x2 = typeof out.x2 === 'number' ? Math.min(out.x2, clip.x2) : clip.x2;
|
|
29
|
+
}
|
|
30
|
+
if (typeof clip?.y1 === 'number') {
|
|
31
|
+
out.y1 = typeof out.y1 === 'number' ? Math.max(out.y1, clip.y1) : clip.y1;
|
|
32
|
+
}
|
|
33
|
+
if (typeof clip?.y2 === 'number') {
|
|
34
|
+
out.y2 = typeof out.y2 === 'number' ? Math.min(out.y2, clip.y2) : clip.y2;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
};
|
|
4
39
|
class OutputCaches {
|
|
5
40
|
widths = new Map();
|
|
6
41
|
blockWidths = new Map();
|
|
@@ -112,10 +147,19 @@ export default class Output {
|
|
|
112
147
|
const { text, transformers } = operation;
|
|
113
148
|
let { x, y } = operation;
|
|
114
149
|
let lines = text.split('\n');
|
|
115
|
-
|
|
150
|
+
// [mixdog fork] Nested overflow:hidden boxes must be clipped to
|
|
151
|
+
// the INTERSECTION of every active ancestor clip. Using only the
|
|
152
|
+
// innermost clip lets child components such as ToolExecution /
|
|
153
|
+
// TurnDone (which have their own overflow hidden rows) escape the
|
|
154
|
+
// transcript viewport and paint over command/prompt/status rows.
|
|
155
|
+
const clip = intersectClips(clips);
|
|
116
156
|
if (clip) {
|
|
117
157
|
const clipHorizontally = typeof clip?.x1 === 'number' && typeof clip?.x2 === 'number';
|
|
118
158
|
const clipVertically = typeof clip?.y1 === 'number' && typeof clip?.y2 === 'number';
|
|
159
|
+
if ((clipHorizontally && clip.x2 <= clip.x1) ||
|
|
160
|
+
(clipVertically && clip.y2 <= clip.y1)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
119
163
|
// If text is positioned outside of clipping area altogether,
|
|
120
164
|
// skip to the next operation to avoid unnecessary calculations
|
|
121
165
|
if (clipHorizontally) {
|
|
@@ -218,15 +262,34 @@ export default class Output {
|
|
|
218
262
|
// dim text, and status colors do not bleed through.
|
|
219
263
|
const sel = this.selection;
|
|
220
264
|
let selectedText = null;
|
|
265
|
+
// [mixdog fork] noSelect exclusion (claude-code skips gutter / line-number
|
|
266
|
+
// / diff-sigil cells from both highlight and copy via screen.noSelect):
|
|
267
|
+
// mixdog's cell model has NO noSelect marker — the Output grid stores only
|
|
268
|
+
// {value, styles} per cell, with no flag distinguishing gutter cells from
|
|
269
|
+
// content. Inferring gutters from position/content would be a fragile
|
|
270
|
+
// heuristic (line numbers, diff +/- sigils, and real content are
|
|
271
|
+
// indistinguishable at the cell level), so this is deliberately NOT
|
|
272
|
+
// implemented. It needs a noSelect bit threaded through the render
|
|
273
|
+
// pipeline before it can be done cleanly.
|
|
221
274
|
if (sel) {
|
|
222
275
|
const captureSelectedText = sel.captureText !== false;
|
|
223
276
|
if (!captureSelectedText) {
|
|
224
277
|
selectedText = undefined;
|
|
225
278
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
// [mixdog fork] Port applySelectionOverlay's fg-preserving principle
|
|
280
|
+
// from claude-code's selection.ts: REPLACE only the background and
|
|
281
|
+
// PRESERVE each cell's own foreground/attribute styles, so syntax
|
|
282
|
+
// highlighting, OSC-8 links, and dim text stay readable under the
|
|
283
|
+
// selection. The old code forced a black fg + light bg, flattening
|
|
284
|
+
// every colored glyph to one color. selectionForeground is now
|
|
285
|
+
// intentionally unused (kept in the setter API for compatibility).
|
|
286
|
+
const selectionBg = rgbStyle(sel.selectionBackground, 'background', [245, 245, 245]);
|
|
287
|
+
// A style entry sets the background when its opening SGR code begins
|
|
288
|
+
// with a background parameter: 40-47 (basic), 48 (256/truecolor),
|
|
289
|
+
// 49 (default), or 100-107 (bright). ansi-tokenize emits one entry
|
|
290
|
+
// per SGR attribute, so a prefix test on `.code` cleanly isolates the
|
|
291
|
+
// bg-only entries we want to drop before appending the selection bg.
|
|
292
|
+
const isBackgroundStyle = (s) => typeof s?.code === 'string' && /^\x1b\[(?:4[0-9]|10[0-7])[;m]/.test(s.code);
|
|
230
293
|
const linear = sel.mode === 'linear';
|
|
231
294
|
const start = linear && (sel.y1 > sel.y2 || (sel.y1 === sel.y2 && sel.x1 > sel.x2))
|
|
232
295
|
? { x: sel.x2, y: sel.y2 }
|
|
@@ -281,9 +344,16 @@ export default class Output {
|
|
|
281
344
|
if (contentStart === -1 || x < contentStart || x > contentEnd) {
|
|
282
345
|
continue;
|
|
283
346
|
}
|
|
347
|
+
// [mixdog fork] fg-preserving highlight: keep the cell's own
|
|
348
|
+
// style entries (foreground color, bold/dim/underline, link
|
|
349
|
+
// attrs) and swap ONLY the background. Drop any existing
|
|
350
|
+
// background entry so the two bgs don't fight, then append the
|
|
351
|
+
// selection bg last so it wins for this cell.
|
|
352
|
+
const baseStyles = Array.isArray(cell.styles) ? cell.styles : [];
|
|
353
|
+
const preserved = baseStyles.filter(style => !isBackgroundStyle(style));
|
|
284
354
|
row[x] = {
|
|
285
355
|
...cell,
|
|
286
|
-
styles:
|
|
356
|
+
styles: [...preserved, selectionBg],
|
|
287
357
|
};
|
|
288
358
|
}
|
|
289
359
|
// Trailing spaces in a selected row are padding, not content.
|
|
@@ -302,6 +372,20 @@ export default class Output {
|
|
|
302
372
|
while (selRows.length > 0 && selRows[selRows.length - 1].trim() === '') {
|
|
303
373
|
selRows.pop();
|
|
304
374
|
}
|
|
375
|
+
// [mixdog fork] SOFT-WRAP JOIN (claude-code getSelectedText):
|
|
376
|
+
// claude-code rejoins word-wrapped continuation rows into one
|
|
377
|
+
// logical line using screen.softWrap, a per-row bitmap set at
|
|
378
|
+
// wrap time marking a row as a continuation of the one above.
|
|
379
|
+
// mixdog's rect(linear) render model carries NO softWrap metadata
|
|
380
|
+
// — the Output grid is a flat array of visual rows with no record
|
|
381
|
+
// of which line breaks were inserted by wrapping vs. present in
|
|
382
|
+
// the source. Detecting "this row filled to content width AND the
|
|
383
|
+
// next continues the same logical block" from cell values alone is
|
|
384
|
+
// a guess (a source line that legitimately fills the width would be
|
|
385
|
+
// wrongly glued to the next). Per the correctness-over-cleverness
|
|
386
|
+
// rule we keep the honest per-visual-row '\n' join rather than
|
|
387
|
+
// fabricate wrap boundaries. Revisit if the render pipeline starts
|
|
388
|
+
// emitting a softWrap bit per row.
|
|
305
389
|
selectedText = selRows.join('\n');
|
|
306
390
|
}
|
|
307
391
|
}
|
|
@@ -317,8 +401,30 @@ export default class Output {
|
|
|
317
401
|
const generatedOutput = output
|
|
318
402
|
.map(line => {
|
|
319
403
|
// See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
|
|
320
|
-
|
|
321
|
-
|
|
404
|
+
// [mixdog fork] Keep the serialized row inside Output.width as a
|
|
405
|
+
// final invariant. Component-level width budgeting can miss
|
|
406
|
+
// background fills, resize races, or wide-char edge cases; if the
|
|
407
|
+
// terminal receives a row wider than its modeled width it may
|
|
408
|
+
// soft-wrap/scroll outside Ink's cursor-origin model.
|
|
409
|
+
const lineWithinWidth = [];
|
|
410
|
+
let usedWidth = 0;
|
|
411
|
+
for (const item of line) {
|
|
412
|
+
if (item === undefined) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const value = item.value ?? '';
|
|
416
|
+
if (value === '') {
|
|
417
|
+
lineWithinWidth.push(item);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const itemWidth = Math.max(1, this.caches.getStringWidth(value));
|
|
421
|
+
if (usedWidth + itemWidth > this.width) {
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
lineWithinWidth.push(item);
|
|
425
|
+
usedWidth += itemWidth;
|
|
426
|
+
}
|
|
427
|
+
return styledCharsToString(lineWithinWidth).trimEnd();
|
|
322
428
|
})
|
|
323
429
|
.join('\n');
|
|
324
430
|
return {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// [mixdog fork] use the shared display-width policy so the wrap decision here
|
|
2
|
+
// agrees with measure-text.js and output.js. Kept in sync with
|
|
3
|
+
// src/tui/display-width.mjs.
|
|
4
|
+
import { displayWidestLine as widestLine } from './display-width.js';
|
|
2
5
|
import indentString from 'indent-string';
|
|
3
6
|
import Yoga from 'yoga-layout';
|
|
4
7
|
import wrapText from './wrap-text.js';
|
|
@@ -41,6 +41,10 @@ const render = (node, options) => {
|
|
|
41
41
|
getSelectionText: () => instance.selectedText,
|
|
42
42
|
// [mixdog fork] resolve the word rect at a cell for double-click select.
|
|
43
43
|
getWordRectAt: (x, y) => instance.getWordRectAt(x, y),
|
|
44
|
+
// [mixdog fork] resolve the whole-line rect at a row for triple-click /
|
|
45
|
+
// line select. Symmetric to getWordRectAt; index.jsx wires it into the
|
|
46
|
+
// store as store.getLineRectAt.
|
|
47
|
+
getLineRectAt: (y) => instance.getLineRectAt(y),
|
|
44
48
|
};
|
|
45
49
|
};
|
|
46
50
|
export default render;
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* permission-rules.cjs
|
|
4
|
-
* Pattern matcher and priority evaluator. Priority order and Tool(content)
|
|
5
|
-
* parsing mirror Claude Code native logic; specifier matching uses a local
|
|
6
|
-
* path/command glob (native delegates per-tool content matching to each tool).
|
|
7
|
-
*
|
|
8
|
-
* Priority (highest → lowest): deny > ask > allow > mode default
|
|
9
|
-
*
|
|
10
|
-
* Pattern forms:
|
|
11
|
-
* mcp__* → toolName starts with "mcp__"
|
|
12
|
-
* ToolName → exact toolName match (no parens)
|
|
13
|
-
* ToolName(*) → toolName matches AND specifier glob matches any
|
|
14
|
-
* ToolName(specifier) → toolName matches AND specifier glob matches
|
|
15
|
-
* toolInput.path / .command / .file
|
|
16
|
-
* Glob chars: * (any chars within segment), ** (any segments)
|
|
17
|
-
*
|
|
18
|
-
* No external deps; no eval/Function.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
// Read-only tool set (mixdog tool names).
|
|
22
|
-
const READ_ONLY_TOOLS = new Set([
|
|
23
|
-
'read', 'list', 'glob', 'grep', 'find_symbol', 'find_references', 'find_callers', 'explore',
|
|
24
|
-
'recall', 'search', 'fetch', 'web_fetch',
|
|
25
|
-
'schedule_status', 'list_models', 'job_wait', 'download_attachment',
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Find the index of the first/last UNescaped occurrence of a char.
|
|
30
|
-
* A char is escaped when preceded by an odd number of backslashes.
|
|
31
|
-
* Mirrors native permissionRuleParser.findFirst/LastUnescapedChar.
|
|
32
|
-
*/
|
|
33
|
-
function findFirstUnescapedChar(str, char) {
|
|
34
|
-
for (let i = 0; i < str.length; i++) {
|
|
35
|
-
if (str[i] !== char) continue;
|
|
36
|
-
let bs = 0, j = i - 1;
|
|
37
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
38
|
-
if (bs % 2 === 0) return i;
|
|
39
|
-
}
|
|
40
|
-
return -1;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function findLastUnescapedChar(str, char) {
|
|
44
|
-
for (let i = str.length - 1; i >= 0; i--) {
|
|
45
|
-
if (str[i] !== char) continue;
|
|
46
|
-
let bs = 0, j = i - 1;
|
|
47
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
48
|
-
if (bs % 2 === 0) return i;
|
|
49
|
-
}
|
|
50
|
-
return -1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Reverse escapeRuleContent: \( -> (, \) -> ), \\ -> \ (order matters). */
|
|
54
|
-
function unescapeRuleContent(content) {
|
|
55
|
-
return content
|
|
56
|
-
.replace(/\\\(/g, '(')
|
|
57
|
-
.replace(/\\\)/g, ')')
|
|
58
|
-
.replace(/\\\\/g, '\\');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Convert a glob pattern string to a RegExp.
|
|
63
|
-
* Supports: * (non-slash wildcard), ** (any), ? (single char).
|
|
64
|
-
* Safe: no eval.
|
|
65
|
-
*/
|
|
66
|
-
function globToRegex(glob) {
|
|
67
|
-
let src = '';
|
|
68
|
-
let i = 0;
|
|
69
|
-
while (i < glob.length) {
|
|
70
|
-
const ch = glob[i];
|
|
71
|
-
if (ch === '*' && glob[i + 1] === '*') {
|
|
72
|
-
src += '.*';
|
|
73
|
-
i += 2;
|
|
74
|
-
if (glob[i] === '/') i++; // consume trailing slash
|
|
75
|
-
} else if (ch === '*') {
|
|
76
|
-
src += '[^/\\\\]*';
|
|
77
|
-
i++;
|
|
78
|
-
} else if (ch === '?') {
|
|
79
|
-
src += '[^/\\\\]';
|
|
80
|
-
i++;
|
|
81
|
-
} else {
|
|
82
|
-
// Escape regex metacharacters
|
|
83
|
-
src += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
84
|
-
i++;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return new RegExp('^' + src + '$', 'i');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Match a single pattern entry against (toolName, toolInput).
|
|
92
|
-
* Returns true if the pattern applies.
|
|
93
|
-
*/
|
|
94
|
-
function matchesPattern(pattern, toolName, toolInput) {
|
|
95
|
-
if (typeof pattern !== 'string') return false;
|
|
96
|
-
|
|
97
|
-
// Detect Tool(specifier) form. Escape-aware, mirroring native
|
|
98
|
-
// permissionRuleValueFromString: only UNescaped parens delimit the
|
|
99
|
-
// specifier, and the close paren must be the final char.
|
|
100
|
-
const parenIdx = findFirstUnescapedChar(pattern, '(');
|
|
101
|
-
const closeIdx = findLastUnescapedChar(pattern, ')');
|
|
102
|
-
if (parenIdx > 0 && closeIdx === pattern.length - 1 && closeIdx > parenIdx) {
|
|
103
|
-
const namePart = pattern.slice(0, parenIdx);
|
|
104
|
-
const rawSpecifier = pattern.slice(parenIdx + 1, closeIdx);
|
|
105
|
-
|
|
106
|
-
if (toolName !== namePart) return false;
|
|
107
|
-
|
|
108
|
-
// Empty "()" or wildcard "(*)" specifier → tool-wide rule, match any
|
|
109
|
-
if (rawSpecifier === '' || rawSpecifier === '*') return true;
|
|
110
|
-
|
|
111
|
-
// Unescape \( \) before glob matching
|
|
112
|
-
const specifier = unescapeRuleContent(rawSpecifier);
|
|
113
|
-
// Match specifier against known path-bearing fields
|
|
114
|
-
const specRe = globToRegex(specifier);
|
|
115
|
-
const inp = toolInput || {};
|
|
116
|
-
const candidates = [...new Set([
|
|
117
|
-
inp.path, inp.command, inp.file, inp.file_path, inp.base_path, inp.cwd,
|
|
118
|
-
...(Array.isArray(inp.path) ? inp.path.map(p => (p && typeof p === 'object' ? p.path : p)) : []),
|
|
119
|
-
...(Array.isArray(inp.reads) ? inp.reads.map(r => r?.path) : []),
|
|
120
|
-
...(Array.isArray(inp.edits) ? inp.edits.flatMap(e => [e?.path, e?.file_path]) : []),
|
|
121
|
-
...(Array.isArray(inp.writes) ? inp.writes.flatMap(w => [w?.path, w?.file_path]) : []),
|
|
122
|
-
].filter(v => typeof v === 'string'))];
|
|
123
|
-
|
|
124
|
-
return candidates.some(c => specRe.test(c));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Prefix wildcard: e.g. "mcp__*"
|
|
128
|
-
if (pattern.endsWith('*')) {
|
|
129
|
-
const prefix = pattern.slice(0, -1);
|
|
130
|
-
return toolName.startsWith(prefix);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Glob on toolName itself (may contain * or **)
|
|
134
|
-
if (pattern.includes('*') || pattern.includes('?')) {
|
|
135
|
-
return globToRegex(pattern).test(toolName);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Exact match
|
|
139
|
-
return toolName === pattern;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Evaluate priority rules against a tool call.
|
|
144
|
-
*
|
|
145
|
-
* @param {string} toolName
|
|
146
|
-
* @param {object} toolInput
|
|
147
|
-
* @param {string[]} allowList
|
|
148
|
-
* @param {string[]} denyList
|
|
149
|
-
* @param {string[]} askList
|
|
150
|
-
* @returns {'deny'|'ask'|'allow'|null} null = no list matched (use mode default)
|
|
151
|
-
*/
|
|
152
|
-
function evaluateRules(toolName, toolInput, allowList, denyList, askList) {
|
|
153
|
-
if (denyList.some(p => matchesPattern(p, toolName, toolInput))) return 'deny';
|
|
154
|
-
if (askList.some(p => matchesPattern(p, toolName, toolInput))) return 'ask';
|
|
155
|
-
if (allowList.some(p => matchesPattern(p, toolName, toolInput))) return 'allow';
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Determine whether a tool is considered read-only.
|
|
161
|
-
* For mcp__ tools: strip the mcp__ prefix chain and check the short tool name.
|
|
162
|
-
*/
|
|
163
|
-
function isReadOnlyTool(toolName) {
|
|
164
|
-
// Strip mcp__plugin_...__ prefix (one or more double-underscore segments)
|
|
165
|
-
const parts = toolName.split('__');
|
|
166
|
-
const shortName = parts[parts.length - 1];
|
|
167
|
-
return READ_ONLY_TOOLS.has(shortName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = { matchesPattern, evaluateRules, isReadOnlyTool, READ_ONLY_TOOLS };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* settings-loader.cjs
|
|
4
|
-
* Loads and merges Mixdog settings from three tiers (lowest → highest):
|
|
5
|
-
* 1. User global: $MIXDOG_CONFIG_DIR/settings.json (or ~/.mixdog/settings.json)
|
|
6
|
-
* 2. Project: $MIXDOG_PROJECT_DIR/.mixdog/settings.json (or cwd/.mixdog/settings.json)
|
|
7
|
-
* 3. Project local: $MIXDOG_PROJECT_DIR/.mixdog/settings.local.json
|
|
8
|
-
*
|
|
9
|
-
* Only `permissions` sub-tree is returned; the rest is ignored.
|
|
10
|
-
* Pure fs/path — no external deps.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const os = require('os');
|
|
16
|
-
|
|
17
|
-
// ── In-process mtime-driven cache ────────────────────────────────────────────
|
|
18
|
-
// Keyed by resolved file path. Each entry: { mtime: number|null, data: any }.
|
|
19
|
-
// Invalidated per-tier independently when mtime changes.
|
|
20
|
-
// TTL: never-expire by default (mtime-driven only).
|
|
21
|
-
const _fileCache = new Map();
|
|
22
|
-
|
|
23
|
-
function readJsonCached(filePath) {
|
|
24
|
-
let entry = _fileCache.get(filePath);
|
|
25
|
-
let mtime = null;
|
|
26
|
-
try { mtime = fs.statSync(filePath).mtimeMs; } catch { /* file absent */ }
|
|
27
|
-
if (entry && entry.mtime === mtime) return entry.data;
|
|
28
|
-
let data = null;
|
|
29
|
-
if (mtime !== null) {
|
|
30
|
-
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { data = null; }
|
|
31
|
-
}
|
|
32
|
-
_fileCache.set(filePath, { mtime, data });
|
|
33
|
-
return data;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Clear the entire settings cache (for test isolation). */
|
|
37
|
-
function clearSettingsCache() {
|
|
38
|
-
_fileCache.clear();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Merge two permissions objects (base then overlay).
|
|
43
|
-
* Arrays are concatenated and de-duped; scalar fields are overwritten.
|
|
44
|
-
*/
|
|
45
|
-
function mergePermissions(base, overlay) {
|
|
46
|
-
if (!overlay) return base || {};
|
|
47
|
-
if (!base) return overlay || {};
|
|
48
|
-
|
|
49
|
-
const merged = Object.assign({}, base);
|
|
50
|
-
|
|
51
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
52
|
-
const b = Array.isArray(base[key]) ? base[key] : [];
|
|
53
|
-
const o = Array.isArray(overlay[key]) ? overlay[key] : [];
|
|
54
|
-
const combined = [...b, ...o];
|
|
55
|
-
if (combined.length) merged[key] = [...new Set(combined)];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// scalar: overlay wins
|
|
59
|
-
if (overlay.defaultMode !== undefined) merged.defaultMode = overlay.defaultMode;
|
|
60
|
-
|
|
61
|
-
return merged;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Load and merge settings from all three tiers.
|
|
66
|
-
* Returns `{ allow: string[], deny: string[], ask: string[], defaultMode: string }`.
|
|
67
|
-
*/
|
|
68
|
-
function loadPermissions(projectDir) {
|
|
69
|
-
const userConfigDir = process.env.MIXDOG_CONFIG_DIR ||
|
|
70
|
-
path.join(os.homedir(), '.mixdog');
|
|
71
|
-
const projDir = projectDir ||
|
|
72
|
-
process.env.MIXDOG_PROJECT_DIR ||
|
|
73
|
-
process.cwd();
|
|
74
|
-
|
|
75
|
-
const userSettings = readJsonCached(path.join(userConfigDir, 'settings.json'));
|
|
76
|
-
const projectSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.json'));
|
|
77
|
-
const localSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.local.json'));
|
|
78
|
-
|
|
79
|
-
// Accept permissions from `settings.permissions` (canonical) OR top-level
|
|
80
|
-
// `allow`/`deny`/`ask`/`defaultMode` fields (common user shorthand).
|
|
81
|
-
function extractPerms(s) {
|
|
82
|
-
if (!s) return {};
|
|
83
|
-
const nested = (s.permissions && typeof s.permissions === 'object') ? s.permissions : {};
|
|
84
|
-
const topLevel = {};
|
|
85
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
86
|
-
if (Array.isArray(s[key])) topLevel[key] = s[key];
|
|
87
|
-
}
|
|
88
|
-
if (typeof s.defaultMode === 'string') topLevel.defaultMode = s.defaultMode;
|
|
89
|
-
return mergePermissions(nested, topLevel);
|
|
90
|
-
}
|
|
91
|
-
const userPerms = extractPerms(userSettings);
|
|
92
|
-
const projectPerms = extractPerms(projectSettings);
|
|
93
|
-
const localPerms = extractPerms(localSettings);
|
|
94
|
-
|
|
95
|
-
let merged = mergePermissions({}, userPerms);
|
|
96
|
-
merged = mergePermissions(merged, projectPerms);
|
|
97
|
-
merged = mergePermissions(merged, localPerms);
|
|
98
|
-
|
|
99
|
-
const VALID_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions', 'auto']);
|
|
100
|
-
const rawMode = merged.defaultMode;
|
|
101
|
-
const resolvedMode = (typeof rawMode === 'string' && VALID_MODES.has(rawMode))
|
|
102
|
-
? rawMode
|
|
103
|
-
: 'default';
|
|
104
|
-
return {
|
|
105
|
-
allow: Array.isArray(merged.allow) ? merged.allow : [],
|
|
106
|
-
deny: Array.isArray(merged.deny) ? merged.deny : [],
|
|
107
|
-
ask: Array.isArray(merged.ask) ? merged.ask : [],
|
|
108
|
-
defaultMode: resolvedMode,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { loadPermissions, clearSettingsCache };
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Single source for the hook IPC path. Centralizes an existing platform
|
|
4
|
-
// contract (not new heuristic). Rust mirror native/mixdog-shim/src/main.rs:34-43
|
|
5
|
-
// must stay in sync.
|
|
6
|
-
module.exports = function hookPipePath() {
|
|
7
|
-
return process.platform === 'win32'
|
|
8
|
-
? '\\\\.\\pipe\\mixdog-hooks'
|
|
9
|
-
: require('path').join(process.env.XDG_RUNTIME_DIR || '/tmp', 'mixdog-hooks.sock');
|
|
10
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: extreme-simple
|
|
3
|
-
title: Extreme Simple
|
|
4
|
-
description: Ultra-short completion sentence
|
|
5
|
-
aliases: extreme, extreme simple
|
|
6
|
-
keep-coding-instructions: true
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Output Style
|
|
10
|
-
|
|
11
|
-
Extreme — one ultra-short completion sentence. The most minimal of the styles.
|
|
12
|
-
|
|
13
|
-
- Reply with a single completion sentence whenever possible.
|
|
14
|
-
- Preferred pattern: `<target> 변경되었습니다. <verification> 통과 완료입니다.`
|
|
15
|
-
- If verification was not run, use one short sentence saying the change is done
|
|
16
|
-
and verification was not run.
|
|
17
|
-
- No headings, bullets, labels, explanations, tool traces, or metadata unless
|
|
18
|
-
the user explicitly asks.
|
|
19
|
-
- Preserve only the single decisive path, command, symbol, API name, code, or
|
|
20
|
-
error verbatim.
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Workflow
|
|
2
|
-
|
|
3
|
-
Priority:
|
|
4
|
-
- When an `# Active Workflow` block is present, it is the binding session
|
|
5
|
-
workflow and takes precedence over all other workflow guidance.
|
|
6
|
-
|
|
7
|
-
Delegation:
|
|
8
|
-
- Lead is the supervisor of agents, responsible for task delegation,
|
|
9
|
-
coordination, judgment, and final decisions.
|
|
10
|
-
- As supervisor, prefer delegating implementation work to an agent; keep direct
|
|
11
|
-
work limited to coordination and User Workflow exceptions such as
|
|
12
|
-
one-or-two-step edits, pre-planning, config changes, and final git deployment.
|
|
13
|
-
- Implementation edits to product/runtime/TUI code MUST be delegated to a
|
|
14
|
-
Worker/Heavy Worker, even when small. Lead may directly edit only rules, docs,
|
|
15
|
-
config, and final deployment/git steps, unless the user explicitly asks Lead
|
|
16
|
-
to patch directly.
|
|
17
|
-
- Delegation split: different code paths / entry points / file groups are
|
|
18
|
-
separate scopes — implementation AND analysis, review, debugging split the
|
|
19
|
-
same way. With 2+ independent scopes, spawn each scoped agent in the
|
|
20
|
-
SAME turn, not one after another. Shared functions or cross-cutting concerns
|
|
21
|
-
do NOT justify collapsing them into one delegation: split per path, and Lead
|
|
22
|
-
verifies the cross-cutting parts (shared state, telemetry consistency, etc.)
|
|
23
|
-
directly. "One agent must see the whole context" is not a valid reason to
|
|
24
|
-
merge; the only legitimate single-scope case is a genuinely inseparable
|
|
25
|
-
dependency — and then state the reason.
|
|
26
|
-
- Follow-up work on the same path or scope MUST reuse the existing agent (same
|
|
27
|
-
tag). Spawn a new agent only for a genuinely separate scope.
|
|
28
|
-
- Pick the agent role defined in the User Workflow section that fits the task
|
|
29
|
-
(worker, heavy-worker, reviewer, debugger, …) when spawning.
|
|
30
|
-
- Write agent-facing briefs and follow-up task messages in English.
|
|
31
|
-
- Agent briefs must be bounded: state the goal/success criteria, known starting
|
|
32
|
-
files or anchors if available, allowed/forbidden changes, expected
|
|
33
|
-
deliverable, and verification boundary. For heavy-worker, include known first
|
|
34
|
-
components to inspect and the stopping condition; complex work must not
|
|
35
|
-
become open-ended.
|
|
36
|
-
|
|
37
|
-
Agent lifecycle:
|
|
38
|
-
- After spawning async agent(s), END THE TURN immediately. Do not poll
|
|
39
|
-
status/read, guess the outcome, or start dependent work — treat the result as
|
|
40
|
-
unknown until the completion notification resumes you. status/read are manual
|
|
41
|
-
recovery only, never progress checks.
|
|
42
|
-
- Unless instructed otherwise, wait for the spawned agent(s); once their results
|
|
43
|
-
are collected, automatically continue with the next turn.
|
|
44
|
-
|
|
45
|
-
Agent result handling:
|
|
46
|
-
- Cross-check material agent responses before using them for decisions or
|
|
47
|
-
summarizing them to the user.
|
|
48
|
-
- When an agent returns, always synthesize its result into a concise report for
|
|
49
|
-
the user (outcome + key changes/evidence). Never forward the raw agent output.
|
|
50
|
-
- If anything here conflicts with the User Workflow section, this Workflow takes
|
|
51
|
-
precedence, except that the `# Active Workflow` block always wins.
|