mixdog 0.9.71 → 0.9.74
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 +1 -1
- package/src/lib/rules-builder.cjs +21 -48
- package/src/output-styles/default.md +17 -25
- package/src/output-styles/minimal.md +9 -13
- package/src/output-styles/simple.md +12 -21
- package/src/rules/lead/01-general.md +2 -2
- package/src/rules/shared/01-tool.md +11 -13
- package/src/runtime/agent/orchestrator/config.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +8 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +13 -2
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -2
- package/src/runtime/memory/lib/session-ingest.mjs +6 -0
- package/src/runtime/memory/tool-defs.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +1 -1
- package/src/runtime/shared/user-data-guard.mjs +1 -2
- package/src/session-runtime/lifecycle-api.mjs +5 -1
- package/src/tui/dist/index.mjs +2 -3
- package/src/workflows/solo/WORKFLOW.md +2 -2
- package/src/rules/lead/02-channels.md +0 -3
package/package.json
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* Source files (rules/):
|
|
22
22
|
* - shared/01-tool.md — universal tool policy (Lead + agent BP1, identical full set)
|
|
23
23
|
* - lead/lead-tool.md — Lead-specific control-tower / delegation / ToolSearch guidance
|
|
24
|
-
* - lead/lead-brief.md — Lead brief contract (
|
|
25
|
-
* - lead/01-
|
|
24
|
+
* - lead/lead-brief.md — Lead brief contract (skipped in solo workflow)
|
|
25
|
+
* - lead/01-general.md — Lead general
|
|
26
26
|
* - output-styles/<name>.md — Lead output style, selected by config outputStyle
|
|
27
27
|
* - agent/00-core.md — universal agent constraints (BP2, all profiles)
|
|
28
28
|
* - agent/00-common.md — public-agent-only extras (BP2 full profile)
|
|
@@ -133,7 +133,7 @@ function buildProfilePreferencesContent(dataDir) {
|
|
|
133
133
|
lines.push(`- Use "${profile.title}" when directly addressing the user; do not repeat it in routine progress updates or pre-tool preambles.`);
|
|
134
134
|
}
|
|
135
135
|
const shell = process.platform === 'win32' ? 'powershell' : 'bash';
|
|
136
|
-
lines.push(`- Shell environment: ${shell}. Write shell commands and scripts in ${shell} syntax unless the user specifies otherwise
|
|
136
|
+
lines.push(`- Shell environment: ${shell}. Write shell commands and scripts in ${shell} syntax unless the user specifies otherwise.`);
|
|
137
137
|
return lines.length ? `# Profile Preferences\n\n${lines.join('\n')}` : '';
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -240,27 +240,6 @@ function collectMarkdownFilesRecursive(dir) {
|
|
|
240
240
|
return collected;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
// Address-form rule. Reads only `user.title` — `user.name` is intentionally
|
|
244
|
-
// not consumed; the user is addressed solely by the configured form.
|
|
245
|
-
// Returns '' when title is empty so nothing is injected.
|
|
246
|
-
function composeUserAddressBullet(memoryConfig) {
|
|
247
|
-
const userTitle = (memoryConfig.user && memoryConfig.user.title || '').trim();
|
|
248
|
-
if (!userTitle) return '';
|
|
249
|
-
return `- User address form: ${userTitle} (use this address form exactly as written; do not combine it with any other name or honorific)`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function splitLeadGeneral(general, addressBullet = '') {
|
|
253
|
-
// Language is owned solely by the `# Language` section (buildLanguageSection);
|
|
254
|
-
// general.md never carries a language line, so there is no language content to
|
|
255
|
-
// promote into the meta layer here. The only meta-tier content this split
|
|
256
|
-
// emits is the optional user address-form bullet.
|
|
257
|
-
const role = String(general || '').trim();
|
|
258
|
-
const meta = addressBullet
|
|
259
|
-
? ['# General', '', addressBullet].join('\n').trim()
|
|
260
|
-
: '';
|
|
261
|
-
return { role, meta };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
243
|
function buildSharedToolContent({ PLUGIN_ROOT }) {
|
|
265
244
|
const SHARED_DIR = path.join(PLUGIN_ROOT, 'rules', 'shared');
|
|
266
245
|
return readOptional(path.join(SHARED_DIR, '01-tool.md'));
|
|
@@ -269,22 +248,26 @@ function buildSharedToolContent({ PLUGIN_ROOT }) {
|
|
|
269
248
|
function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR }) {
|
|
270
249
|
const RULES_DIR = path.join(PLUGIN_ROOT, 'rules');
|
|
271
250
|
const LEAD_DIR = path.join(RULES_DIR, 'lead');
|
|
272
|
-
const memoryConfig = readConfigSection(DATA_DIR, 'memory');
|
|
273
|
-
const addressBullet = composeUserAddressBullet(memoryConfig);
|
|
274
251
|
const general = readOptional(path.join(LEAD_DIR, '01-general.md'));
|
|
275
|
-
const generalSplit = splitLeadGeneral(general, addressBullet);
|
|
276
252
|
const parts = [];
|
|
277
253
|
|
|
278
254
|
const toolLead = readOptional(path.join(LEAD_DIR, 'lead-tool.md'));
|
|
279
255
|
if (toolLead) parts.push(toolLead);
|
|
280
256
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
257
|
+
// Solo workflow forbids delegation, so the agent-brief contract is dead
|
|
258
|
+
// weight there. Cache safety: lead rules cache keys on mixdog-config.json
|
|
259
|
+
// mtime, so switching workflow rebuilds this block.
|
|
260
|
+
const workflowActive = String(
|
|
261
|
+
(readConfigSection(DATA_DIR, 'agent').workflow || {}).active
|
|
262
|
+
|| readConfigSection(DATA_DIR, 'workflow').active
|
|
263
|
+
|| 'default',
|
|
264
|
+
).trim().toLowerCase();
|
|
265
|
+
if (workflowActive !== 'solo') {
|
|
266
|
+
const briefLead = readOptional(path.join(LEAD_DIR, 'lead-brief.md'));
|
|
267
|
+
if (briefLead) parts.push(briefLead);
|
|
268
|
+
}
|
|
285
269
|
|
|
286
|
-
|
|
287
|
-
if (channels) parts.push(channels);
|
|
270
|
+
if (general) parts.push(general);
|
|
288
271
|
|
|
289
272
|
return parts.join('\n\n');
|
|
290
273
|
}
|
|
@@ -292,11 +275,6 @@ function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR }) {
|
|
|
292
275
|
function buildLeadMetaContent({ PLUGIN_ROOT, DATA_DIR }) {
|
|
293
276
|
const RULES_DIR = path.join(PLUGIN_ROOT, 'rules');
|
|
294
277
|
const LEAD_DIR = path.join(RULES_DIR, 'lead');
|
|
295
|
-
const HISTORY_DIR = path.join(DATA_DIR, 'history');
|
|
296
|
-
const memoryConfig = readConfigSection(DATA_DIR, 'memory');
|
|
297
|
-
const addressBullet = composeUserAddressBullet(memoryConfig);
|
|
298
|
-
const general = readOptional(path.join(LEAD_DIR, '01-general.md'));
|
|
299
|
-
const generalSplit = splitLeadGeneral(general, addressBullet);
|
|
300
278
|
const parts = [];
|
|
301
279
|
|
|
302
280
|
const profilePreferences = buildProfilePreferencesContent(DATA_DIR);
|
|
@@ -305,16 +283,11 @@ function buildLeadMetaContent({ PLUGIN_ROOT, DATA_DIR }) {
|
|
|
305
283
|
const languageSection = buildLanguageSection(DATA_DIR);
|
|
306
284
|
if (languageSection) parts.push(languageSection);
|
|
307
285
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const botPersona = readOptional(path.join(HISTORY_DIR, 'bot.md'));
|
|
314
|
-
if (botPersona) parts.push(`# Bot Persona\n\n${botPersona}`);
|
|
315
|
-
|
|
316
|
-
const userWorkflowMd = readOptional(path.join(DATA_DIR, 'user-workflow.md'));
|
|
317
|
-
if (userWorkflowMd) parts.push(`# User Workflow\n\n${userWorkflowMd}`);
|
|
286
|
+
// Common instructions (renamed from user-workflow.md; legacy file still
|
|
287
|
+
// honored so existing installs keep their guidance without migration).
|
|
288
|
+
const commonInstructionsMd = readOptional(path.join(DATA_DIR, 'instructions.md'))
|
|
289
|
+
|| readOptional(path.join(DATA_DIR, 'user-workflow.md'));
|
|
290
|
+
if (commonInstructionsMd) parts.push(`# Common Instructions\n\n${commonInstructionsMd}`);
|
|
318
291
|
|
|
319
292
|
const outputStyle = loadOutputStyle({ PLUGIN_ROOT, DATA_DIR });
|
|
320
293
|
if (outputStyle) parts.push(outputStyle);
|
|
@@ -13,36 +13,28 @@ essay-form. Depth comes from picking the right facts, not explaining more.
|
|
|
13
13
|
Content
|
|
14
14
|
- Lead with the outcome in one short sentence, then only the detail that
|
|
15
15
|
matters: what changed, key evidence (paths, commands, errors, verification).
|
|
16
|
-
- Summarize at the concept level: name the problem/behavior and direction,
|
|
17
|
-
the code path
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- Size budget: roughly TWICE the Simple style — per point about 2 rendered
|
|
27
|
-
lines, whole report ~10–15 lines. Spend the extra room on evidence and
|
|
28
|
-
context Simple would drop, not on longer sentences.
|
|
16
|
+
- Summarize at the concept level: name the problem/behavior and direction,
|
|
17
|
+
not the code path; cite a symbol/path only as an anchor.
|
|
18
|
+
State conclusions, not reasoning: no mechanism walkthroughs or chained
|
|
19
|
+
qualifiers unless asked.
|
|
20
|
+
- Compress by cutting content (filler, hedging, restated facts), not grammar:
|
|
21
|
+
natural, complete sentences in the user's language; technical terms, paths,
|
|
22
|
+
commands, symbols, code, and exact errors stay verbatim.
|
|
23
|
+
- Say each point once: problem and fix in ONE compact statement. Size budget:
|
|
24
|
+
roughly TWICE the Simple style — about 2 rendered lines per point, whole
|
|
25
|
+
report ~10–15 lines, spent on evidence Simple would drop.
|
|
29
26
|
- Use labels such as `Changes`, `Verification`, and `Risks / next steps` in
|
|
30
|
-
final reports
|
|
31
|
-
|
|
27
|
+
final reports; skip labels on interim progress, and collapse trivial tasks
|
|
28
|
+
to a couple of sentences instead of forcing sections.
|
|
32
29
|
- Synthesize agent or retrieval results; never forward raw reports, long file
|
|
33
30
|
lists, tool traces, or session metadata.
|
|
34
31
|
- Do not hide blockers, failed verification, or required follow-up; surface them
|
|
35
32
|
in one short clause.
|
|
36
|
-
- Keep paths, commands, symbols, API names, code, and exact errors verbatim.
|
|
37
33
|
|
|
38
34
|
Layout (hard rules)
|
|
39
|
-
- One bullet or numbered item = one idea, at most 2 rendered lines
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
never
|
|
43
|
-
|
|
44
|
-
past one line (loose list). Never emit a wall of consecutive multi-line items.
|
|
45
|
-
- Keep paragraphs to ~3 lines max, with a blank line between paragraphs, lists,
|
|
46
|
-
and code blocks.
|
|
47
|
-
- Nest at most one sub-level; deeper detail means you are over-explaining.
|
|
35
|
+
- One bullet or numbered item = one idea, at most 2 rendered lines, opened
|
|
36
|
+
with a short **bold key point**; if it needs more, cut the detail.
|
|
37
|
+
- Blank line between numbered items and between any multi-line list items —
|
|
38
|
+
never a wall of consecutive multi-line items. Paragraphs ~3 lines max with
|
|
39
|
+
blank lines between blocks; nest at most one sub-level.
|
|
48
40
|
- Never name this style unless asked.
|
|
@@ -10,18 +10,14 @@ keep-coding-instructions: true
|
|
|
10
10
|
Minimal — a very short summary: one or two sentences, nothing more.
|
|
11
11
|
|
|
12
12
|
- Summarize only the net result in one short sentence; add a second short
|
|
13
|
-
sentence only
|
|
14
|
-
|
|
15
|
-
- Size budget: roughly HALF the Simple style — 1–2 plain sentences
|
|
16
|
-
rendered lines
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
only, even when the request says "report" or "summary".
|
|
23
|
-
- Preferred pattern: `<target> changed. <verification> passed.`
|
|
24
|
-
- If verification was not run, say the change is done and verification was not
|
|
25
|
-
run.
|
|
13
|
+
sentence only for a fact (verification, blocker) that genuinely needs it —
|
|
14
|
+
never a run-on that crams extra facts in.
|
|
15
|
+
- Size budget: roughly HALF the Simple style — 1–2 plain, complete sentences
|
|
16
|
+
(~2–3 rendered lines) however large the task was, concept-level only.
|
|
17
|
+
- Summarize, never itemize: no headings, bullets, labels, or sections and no
|
|
18
|
+
file-by-file detail — state only what the change accomplishes, even when
|
|
19
|
+
the request says "report" or "summary".
|
|
20
|
+
- Preferred pattern: `<target> changed. <verification> passed.` If
|
|
21
|
+
verification was not run, say so.
|
|
26
22
|
- Preserve only the single decisive path, command, symbol, API name, code, or
|
|
27
23
|
error verbatim.
|
|
@@ -12,31 +12,22 @@ Practical concise — outcome-first handoffs for coding work: summarize the
|
|
|
12
12
|
result, do not narrate or explain the change.
|
|
13
13
|
|
|
14
14
|
- Open with the outcome in one sentence: done, blocked, or awaiting a decision.
|
|
15
|
-
- Summarize at the concept level
|
|
16
|
-
code path; cite a
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
line per point, whole reply ~5–7 lines. Above that you are writing Default;
|
|
28
|
-
below ~3 lines consider whether prose (Minimal) reads better.
|
|
29
|
-
- Layout: one idea per bullet, ONE line each (two only when a verbatim
|
|
30
|
-
path/error forces it), led with a short bold key phrase; blank line between
|
|
31
|
-
multi-line list items — never a dense wall of text. If a point runs past one
|
|
32
|
-
line, cut the elaboration — detail beyond key phrase + one clause belongs to
|
|
33
|
-
Default.
|
|
15
|
+
- Summarize at the concept level — what the change accomplishes, not a
|
|
16
|
+
per-file changelog or code path; cite a path (`file_path:line_number`) only
|
|
17
|
+
as a navigation anchor, never as the explanation.
|
|
18
|
+
- Compress by cutting content (filler, acknowledgments, hedging, restated
|
|
19
|
+
facts), not grammar: natural, complete sentences in the user's language;
|
|
20
|
+
paths, commands, symbols, code, and exact errors stay verbatim.
|
|
21
|
+
- Controlled detail: 1–3 short bullets or 2–3 sentences; state each point
|
|
22
|
+
once. Size budget: roughly HALF the Default style and TWICE Minimal —
|
|
23
|
+
whole reply ~5–7 lines.
|
|
24
|
+
- Layout: one idea per bullet, ONE line each, led with a short bold key
|
|
25
|
+
phrase; blank line between multi-line list items — never a dense wall of
|
|
26
|
+
text.
|
|
34
27
|
- Final handoffs may use labels like `Changes`, `Verification`, and
|
|
35
28
|
`Risks / next steps`; do not label interim progress.
|
|
36
29
|
- Synthesize agent or retrieval results; never forward raw reports, long file
|
|
37
30
|
lists, tool traces, or session metadata.
|
|
38
31
|
- Do not hide blockers, failed verification, or required follow-up — state
|
|
39
32
|
them in one short clause; if verification was not run, say so once.
|
|
40
|
-
- Keep paths, commands, symbols, API names, code, and exact errors verbatim.
|
|
41
|
-
- Skip filler, acknowledgments, hedging, and repeated conclusions.
|
|
42
33
|
- Never name this style unless asked.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- You are Mixdog, the current coding-agent CLI/TUI assistant with
|
|
4
4
|
multi-provider agent workflows. Never identify as generic OpenAI/ChatGPT.
|
|
5
|
-
- A preamble is at most one useful sentence
|
|
6
|
-
|
|
5
|
+
- A preamble is at most one useful sentence, with no direct names, honorifics,
|
|
6
|
+
headings, labels, or routine lookup narration.
|
|
7
7
|
- Destructive/hard-to-reverse action needs explicit confirmation.
|
|
8
8
|
- Act proactively; ask only for decisions.
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
concurrently regardless of tool. Later turns are only for targets dependent
|
|
16
16
|
on prior results or unresolved facets. Shell/write calls are serial.
|
|
17
17
|
- After locator results, collect all known candidate files/regions before
|
|
18
|
-
inspection. Batch compatible reads, including same-file regions
|
|
18
|
+
inspection. Batch compatible reads, including same-file regions — real
|
|
19
|
+
`{path,offset,limit}` arrays covering the whole logical unit — in one
|
|
19
20
|
`path[]` call and graph targets in arrays. Parallelize independent
|
|
20
|
-
incompatible read-only inspections.
|
|
21
|
-
|
|
21
|
+
incompatible read-only inspections. Never page or reread returned spans,
|
|
22
|
+
and do not start a singleton while a known compatible candidate remains.
|
|
23
|
+
Put all new edits in one patch.
|
|
22
24
|
- Project root, session cwd, user-provided and tool-returned paths are
|
|
23
25
|
verified. Use `find` first for every genuinely guessed path/name fragment, in
|
|
24
26
|
the same turn as independent probes. Never find verified roots or use
|
|
@@ -27,22 +29,18 @@
|
|
|
27
29
|
- At task start, batch all `explore` facets in one `query[]` call, maximum 8,
|
|
28
30
|
without rephrased duplicates. Retry `EXPLORATION_FAILED` once with changed
|
|
29
31
|
tokens.
|
|
30
|
-
- Stop when evidence covers the deliverable; never re-locate, re-verify,
|
|
31
|
-
|
|
32
|
-
freezes the LOCATION only
|
|
33
|
-
code_graph detail inspection is valid
|
|
32
|
+
- Stop when evidence covers the deliverable; never re-locate, re-verify, or
|
|
33
|
+
upgrade a sufficient anchor. A returned requested `path:line`
|
|
34
|
+
freezes the LOCATION only; when content was not returned,
|
|
35
|
+
read or code_graph detail inspection is valid — never re-locate it.
|
|
34
36
|
- Give grep content mode enough `-C` to avoid rereads; a sufficient contextual
|
|
35
37
|
grep means no overlapping `read`. After `files_with_matches`, `count`,
|
|
36
38
|
capped, or insufficient context, inspect only missing content. A nonzero
|
|
37
39
|
`content_with_context` result resolves the concept; act directly without
|
|
38
|
-
|
|
39
|
-
or scope changes.
|
|
40
|
-
- Batch independent read files/regions as real arrays, using
|
|
41
|
-
`{path,offset,limit}` regions, and read the whole logical unit. Never page or
|
|
42
|
-
reread returned spans.
|
|
40
|
+
re-search; only zero/error results permit token or scope changes.
|
|
43
41
|
- Apply edits before verification, then verify in a separate shell turn and
|
|
44
42
|
consume results in order. Otherwise remain parallel.
|
|
45
43
|
- A long-running command promoted to background is a decision point, not a
|
|
46
|
-
cue to wait
|
|
44
|
+
cue to wait: estimate whether observed progress can finish within budget;
|
|
47
45
|
otherwise diagnose the bottleneck and switch routes. Choose waiting
|
|
48
46
|
explicitly.
|
|
@@ -396,7 +396,6 @@ export function loadConfig(options = {}) {
|
|
|
396
396
|
// bypass the per-provider auto-clear table.
|
|
397
397
|
autoClear: { enabled: true, ...raw.autoClear },
|
|
398
398
|
compaction: raw.compaction && typeof raw.compaction === 'object' ? { ...raw.compaction } : {},
|
|
399
|
-
trajectory: { enabled: true, ...raw.trajectory },
|
|
400
399
|
runtime: raw.runtime && typeof raw.runtime === 'object' ? raw.runtime : {},
|
|
401
400
|
shell: raw.shell && typeof raw.shell === 'object' ? raw.shell : {},
|
|
402
401
|
update: raw.update && typeof raw.update === 'object' ? { ...raw.update } : {},
|
|
@@ -426,7 +425,6 @@ export function loadConfig(options = {}) {
|
|
|
426
425
|
skills: normalizeSkillsConfig(null),
|
|
427
426
|
autoClear: { enabled: true },
|
|
428
427
|
compaction: {},
|
|
429
|
-
trajectory: { enabled: true },
|
|
430
428
|
runtime: {},
|
|
431
429
|
shell: {},
|
|
432
430
|
update: {},
|
|
@@ -566,7 +564,6 @@ function buildAgentSaveBuilder(config, appliedDirty) {
|
|
|
566
564
|
skills: normalizeSkillsConfig(config.skills),
|
|
567
565
|
autoClear: config.autoClear || {},
|
|
568
566
|
compaction: config.compaction || {},
|
|
569
|
-
trajectory: config.trajectory || {},
|
|
570
567
|
runtime: config.runtime || {},
|
|
571
568
|
shell: config.shell || {},
|
|
572
569
|
update: config.update || {},
|
|
@@ -124,6 +124,15 @@ export function createSteeringLadder(ctx) {
|
|
|
124
124
|
pushSystemReminder(editPushEligible
|
|
125
125
|
? 'Last 2 turns each ran a single read-only tool. Batch independent lookups (read/grep/glob/code_graph) into ONE turn, or start editing if you have enough context.'
|
|
126
126
|
: 'Last 2 turns each ran a single read-only tool. Batch independent lookups (read/grep/glob/code_graph) into ONE turn.');
|
|
127
|
+
try {
|
|
128
|
+
appendAgentTrace({
|
|
129
|
+
sessionId,
|
|
130
|
+
iteration: iterations,
|
|
131
|
+
kind: 'steer',
|
|
132
|
+
payload: { tag: 'level1_batching', level1_fires: _level1FireCount, edit_count: editCount },
|
|
133
|
+
agent: sessionAgent || null,
|
|
134
|
+
});
|
|
135
|
+
} catch { /* best-effort */ }
|
|
127
136
|
}
|
|
128
137
|
_hintFiredThisTurn = true;
|
|
129
138
|
}
|
|
@@ -11,8 +11,12 @@ const STORED_TOOL_ARG_BODY_KEY_RE = /^(?:content|old_string|new_string|patch|rew
|
|
|
11
11
|
const STORED_TOOL_ARG_LONG_KEY_RE = /^(?:command|script)$/i;
|
|
12
12
|
// Marker-alone value produced by compactStoredToolArgString for body keys.
|
|
13
13
|
const STORED_TOOL_ARG_MARKER_RE = /^\[mixdog compacted\b[^\]\n]*\]$/;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// One shared budget for stored bodies AND long commands, matching the codex
|
|
15
|
+
// reference truncation budget (TruncationPolicyConfig::bytes(10_000)). Below
|
|
16
|
+
// this, the model always sees its own recent patch text verbatim — the 2 KB
|
|
17
|
+
// body cut made every mid-size successful patch render as a marker and Opus
|
|
18
|
+
// mimicked the marker as literal patch input on the next call.
|
|
19
|
+
const STORED_TOOL_ARG_LIMIT = 10_000;
|
|
16
20
|
const STORED_TOOL_ARG_PREVIEW_HEAD = 360;
|
|
17
21
|
const STORED_TOOL_ARG_PREVIEW_TAIL = 160;
|
|
18
22
|
|
|
@@ -20,10 +24,10 @@ function compactStoredToolArgString(value, key = '') {
|
|
|
20
24
|
if (typeof value !== 'string') return value;
|
|
21
25
|
const isBody = STORED_TOOL_ARG_BODY_KEY_RE.test(key);
|
|
22
26
|
const isLong = isBody || STORED_TOOL_ARG_LONG_KEY_RE.test(key);
|
|
23
|
-
const limit =
|
|
27
|
+
const limit = isLong ? STORED_TOOL_ARG_LIMIT : Infinity;
|
|
24
28
|
if (value.length <= limit) return value;
|
|
25
29
|
const hash = createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
26
|
-
const marker = `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}]`;
|
|
30
|
+
const marker = `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}; do not copy]`;
|
|
27
31
|
// Body args (patch / old_string / new_string / content / rewrite) are
|
|
28
32
|
// apply_patch / edit inputs. Keeping a head/tail preview leaves real patch
|
|
29
33
|
// fragments (a "*** Begin Patch" opening, diff lines) inside a SUCCESSFUL
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
prefixSessionStartContent,
|
|
22
22
|
buildCurrentTimeBlock,
|
|
23
23
|
buildSessionStartBlock,
|
|
24
|
+
buildProjectInstructionsBlock,
|
|
24
25
|
hasUserConversationMessage,
|
|
25
26
|
} from './prompt-utils.mjs';
|
|
26
27
|
import {
|
|
@@ -379,11 +380,16 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
379
380
|
const _sessionStartBlock = shouldInjectSessionStart
|
|
380
381
|
? buildSessionStartBlock(session, effectiveCwd)
|
|
381
382
|
: '';
|
|
383
|
+
// Project instructions ride the same once-per-session gate as the
|
|
384
|
+
// `# Session` block (Lead only — agent-owned sessions return '').
|
|
385
|
+
const _projectInstructionsBlock = _sessionStartBlock
|
|
386
|
+
? buildProjectInstructionsBlock(effectiveCwd)
|
|
387
|
+
: '';
|
|
382
388
|
const _currentTimeBlock = buildCurrentTimeBlock(prompt);
|
|
383
389
|
const _turnReminderBlock = _currentTimeBlock
|
|
384
390
|
? `<system-reminder>\n# Current Time\n${_currentTimeBlock}\n</system-reminder>`
|
|
385
391
|
: '';
|
|
386
|
-
const _turnPrefixBlock = [_sessionStartBlock, _turnReminderBlock].filter(Boolean).join('\n\n');
|
|
392
|
+
const _turnPrefixBlock = [_sessionStartBlock, _projectInstructionsBlock, _turnReminderBlock].filter(Boolean).join('\n\n');
|
|
387
393
|
const _baseUserTurnContent = prefixUserTurnContent(prompt, _contextBlock);
|
|
388
394
|
const _userTurnContent = prefixSessionStartContent(_baseUserTurnContent, _turnPrefixBlock);
|
|
389
395
|
if (shouldInjectSessionStart && _sessionStartBlock) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Prompt content + temporal helpers, extracted verbatim from manager.mjs
|
|
2
2
|
// (behavior-preserving). Pure string/date utilities with no session state.
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
3
5
|
import { isInternalRuntimeNotificationText as contractIsInternalRuntimeNotificationText } from '../../../../shared/tool-execution-contract.mjs';
|
|
4
6
|
import { SUMMARY_PREFIX } from '../compact.mjs';
|
|
5
7
|
|
|
@@ -113,6 +115,30 @@ export function buildSessionStartBlock(session, cwd) {
|
|
|
113
115
|
return lines.length > 1 ? lines.join('\n') : '';
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
// Project-scoped user instructions (<project>/.mixdog/instructions.md),
|
|
119
|
+
// injected once per session right after the `# Session` block — BP4 head, so
|
|
120
|
+
// the BP1–3 system prefix stays byte-identical across projects and the block
|
|
121
|
+
// sits in the stable message prefix for same-project cache reuse. Missing or
|
|
122
|
+
// empty file → '' (nothing injected). Best-effort: unreadable files never
|
|
123
|
+
// break a turn.
|
|
124
|
+
const PROJECT_INSTRUCTIONS_MAX_CHARS = 16_000;
|
|
125
|
+
export function buildProjectInstructionsBlock(cwd) {
|
|
126
|
+
const dir = String(cwd || '').trim();
|
|
127
|
+
if (!dir) return '';
|
|
128
|
+
try {
|
|
129
|
+
const file = join(dir, '.mixdog', 'instructions.md');
|
|
130
|
+
if (!existsSync(file)) return '';
|
|
131
|
+
const text = readFileSync(file, 'utf8').trim();
|
|
132
|
+
if (!text) return '';
|
|
133
|
+
const body = text.length > PROJECT_INSTRUCTIONS_MAX_CHARS
|
|
134
|
+
? `${text.slice(0, PROJECT_INSTRUCTIONS_MAX_CHARS)}\n[... project instructions truncated]`
|
|
135
|
+
: text;
|
|
136
|
+
return `# Project Instructions\n${body}`;
|
|
137
|
+
} catch {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
116
142
|
export function isReferenceFilesMessage(message) {
|
|
117
143
|
return message?.role === 'user'
|
|
118
144
|
&& typeof message.content === 'string'
|
|
@@ -104,8 +104,8 @@ export function _buildLeadMetaContext() {
|
|
|
104
104
|
const RULES_DIR = join(PLUGIN_ROOT, 'rules');
|
|
105
105
|
const mtime = maxMtimeRecursive([
|
|
106
106
|
join(RULES_DIR, 'lead'),
|
|
107
|
-
join(DATA_DIR, 'history'),
|
|
108
107
|
join(DATA_DIR, 'mixdog-config.json'),
|
|
108
|
+
join(DATA_DIR, 'instructions.md'),
|
|
109
109
|
join(DATA_DIR, 'user-workflow.md'),
|
|
110
110
|
join(PLUGIN_ROOT, 'output-styles'),
|
|
111
111
|
join(DATA_DIR, 'output-styles'),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// The two halves share the in-flight save map so an unpersisted session still
|
|
4
4
|
// shows up in listings; the cycle is import-only (calls happen at runtime).
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
6
7
|
import { loadConfig } from '../../config.mjs';
|
|
7
8
|
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
8
9
|
import { scanTopLevelLifecycle } from '../lifecycle-scan.mjs';
|
|
@@ -80,7 +80,7 @@ export const BUILTIN_TOOLS = [
|
|
|
80
80
|
properties: {
|
|
81
81
|
command: { type: 'string', description: 'Command.' },
|
|
82
82
|
cwd: { type: 'string', description: 'Working directory; persists across calls. Omit to reuse; absolute path changes it.' },
|
|
83
|
-
timeout: { type: 'number', description: `Timeout ms; default ${_shellDefaultTimeoutMs()}. On sync timeout the command
|
|
83
|
+
timeout: { type: 'number', description: `Timeout ms; default ${_shellDefaultTimeoutMs()}. On sync timeout the command is promoted to a background task_id and keeps running, with any explicit timeout enforced as a background deadline. Sleep-like commands skip promotion: they block the full timeout, then are killed with a [timeout] marker. async runs until done/cancelled unless a timeout is set.` },
|
|
84
84
|
merge_stderr: { type: 'boolean', description: 'Merge stderr.' },
|
|
85
85
|
mode: { type: 'string', enum: ['sync', 'async'], description: executionModeSchemaDescription('sync') },
|
|
86
86
|
shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell. Windows defaults to PowerShell; bash = Git Bash/POSIX.' },
|
|
@@ -453,7 +453,11 @@ export function _spawnCodeGraphWorker(
|
|
|
453
453
|
const genStillCurrent = getGeneration(graphCwd) === genAtStart;
|
|
454
454
|
if (genStillCurrent && cacheResult) {
|
|
455
455
|
setMemoryCache(graphCwd, { ts: Date.now(), signature: msg.signature, graph: msg.graph });
|
|
456
|
-
|
|
456
|
+
// The Worker strictly persisted this graph before posting
|
|
457
|
+
// success. Adopt it in the main process without scheduling a
|
|
458
|
+
// redundant flush that can race the next Worker on the same
|
|
459
|
+
// manifest lock.
|
|
460
|
+
setDiskCache(graphCwd, msg.graph, { persist: false });
|
|
457
461
|
}
|
|
458
462
|
settle(genStillCurrent ? msg.graph : new Error('code-graph build invalidated during prewarm'));
|
|
459
463
|
} else {
|
|
@@ -424,7 +424,7 @@ export function listCachedCodeGraphRoots() {
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
export function _setDiskCodeGraphEntry(cwd, graph) {
|
|
427
|
+
export function _setDiskCodeGraphEntry(cwd, graph, { persist = true } = {}) {
|
|
428
428
|
_loadDiskCodeGraphCache();
|
|
429
429
|
// Stamp the cache entry with the persistence timestamp (not the build
|
|
430
430
|
// start) so manifest/signature metadata stays fresh. Disk retention is
|
|
@@ -434,5 +434,8 @@ export function _setDiskCodeGraphEntry(cwd, graph) {
|
|
|
434
434
|
serialized.builtAt = Date.now();
|
|
435
435
|
_diskCodeGraphCache.set(_canonicalGraphCwd(cwd), serialized);
|
|
436
436
|
_pruneDiskCodeGraphEntries();
|
|
437
|
-
|
|
437
|
+
// Worker success is already fenced by drainCodeGraphCacheStrict(); the
|
|
438
|
+
// parent only adopts that result. Rewriting it here races a following
|
|
439
|
+
// Worker on manifest.json.lock without adding durability.
|
|
440
|
+
if (persist) _scheduleDiskCodeGraphCacheFlush();
|
|
438
441
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// executePatchTool entry point + replay capture. Moved verbatim from
|
|
4
4
|
// patch.mjs; control flow and output are unchanged.
|
|
5
5
|
|
|
6
|
-
import { chmodSync, existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
6
|
+
import { chmodSync, existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
7
7
|
import { dirname as pathDirname, resolve as pathResolve, isAbsolute, join as pathJoin } from 'node:path';
|
|
8
8
|
import { parsePatch } from 'diff';
|
|
9
9
|
import { getAbortSignalForSession } from '../../session/abort-lookup.mjs';
|
|
@@ -664,7 +664,11 @@ function patchTargetPaths(patchStr, basePath) {
|
|
|
664
664
|
}
|
|
665
665
|
|
|
666
666
|
function maybeCapturePatchReplay(args, cwd, errorText) {
|
|
667
|
-
|
|
667
|
+
// Default ON: every apply_patch failure is frozen for `npm run patch:replay`
|
|
668
|
+
// (args + target-file snapshots). Set MIXDOG_PATCH_REPLAY_CAPTURE=0 to
|
|
669
|
+
// disable. Retention is bounded below to the newest records.
|
|
670
|
+
const _flag = String(process.env.MIXDOG_PATCH_REPLAY_CAPTURE ?? '1').trim().toLowerCase();
|
|
671
|
+
if (_flag === '0' || _flag === 'false' || _flag === 'off') return;
|
|
668
672
|
try {
|
|
669
673
|
const patchStr = typeof args?.patch === 'string' ? args.patch : '';
|
|
670
674
|
const basePath = pathResolve(String(args?.base_path || cwd || process.cwd()));
|
|
@@ -693,6 +697,13 @@ function maybeCapturePatchReplay(args, cwd, errorText) {
|
|
|
693
697
|
file_snapshots: files,
|
|
694
698
|
};
|
|
695
699
|
writeFileSync(pathJoin(dir, `${id}.json`), JSON.stringify(record, null, 2), { mode: 0o600 });
|
|
700
|
+
// Retention: keep the newest 40 captures. The id prefix is Date.now() in
|
|
701
|
+
// base36 (fixed width until ~2059), so a lexicographic sort is
|
|
702
|
+
// chronological and the oldest records sort first.
|
|
703
|
+
const _kept = readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
704
|
+
for (const stale of _kept.slice(0, Math.max(0, _kept.length - 40))) {
|
|
705
|
+
try { rmSync(pathJoin(dir, stale), { force: true }); } catch { /* best-effort */ }
|
|
706
|
+
}
|
|
696
707
|
} catch { /* capture is best-effort; never affect the tool result */ }
|
|
697
708
|
}
|
|
698
709
|
|
|
@@ -26,7 +26,7 @@ export const PATCH_TOOL_DEFS = [
|
|
|
26
26
|
name: 'apply_patch',
|
|
27
27
|
title: 'Mixdog Apply Patch',
|
|
28
28
|
annotations: { title: 'Mixdog Apply Patch', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false, compressible: false, compressibleLossless: true },
|
|
29
|
-
description: 'Apply
|
|
29
|
+
description: 'Apply file edits in one patch; sections run in listed order and all touched paths roll back if any section fails. Do not split a dependent edit across turns.',
|
|
30
30
|
freeformDescription: APPLY_PATCH_FREEFORM_DESCRIPTION,
|
|
31
31
|
freeform: {
|
|
32
32
|
type: 'grammar',
|
|
@@ -36,7 +36,7 @@ export const PATCH_TOOL_DEFS = [
|
|
|
36
36
|
inputSchema: {
|
|
37
37
|
type: 'object',
|
|
38
38
|
properties: {
|
|
39
|
-
patch: { type: 'string', description: 'Patch text. V4A preferred;
|
|
39
|
+
patch: { type: 'string', description: 'Patch text. V4A preferred; one file block per target file, 3 lines of exact context per hunk, @@ anchors when ambiguous; include all new edits in listed order. On failure, the tool rolls all earlier writes back.' },
|
|
40
40
|
format: { type: 'string', enum: ['unified', 'v4a'], description: 'Auto-detected.' },
|
|
41
41
|
base_path: { type: 'string', description: 'Repo root.' },
|
|
42
42
|
dry_run: { type: 'boolean', description: 'Default false. true = validate only, no write.' },
|
|
@@ -238,6 +238,12 @@ function stripUserTurnPrefixEnvelopes(text) {
|
|
|
238
238
|
// — its next line is not a `Cwd:/Model:/Workflow:` field — so it is
|
|
239
239
|
// preserved verbatim (zero-loss). Anchored ^.
|
|
240
240
|
out = out.replace(/^# Session\n(?:(?:Cwd|Model|Workflow): [^\n]*\n)+(?:\n|$)/, '')
|
|
241
|
+
// 1b) Leading `# Project Instructions\n<body>` (buildProjectInstructionsBlock,
|
|
242
|
+
// emitted right after the `# Session` block). Anchored to start; body runs
|
|
243
|
+
// to the next `# ` section boundary or end — the human prompt follows a
|
|
244
|
+
// `# Task` marker, so real user text is never inside this span.
|
|
245
|
+
out = out.replace(/^# Project Instructions\n[\s\S]*?(?=\n# |$)/, '')
|
|
246
|
+
out = out.replace(/^\n+/, '')
|
|
241
247
|
// 2) Leading `# Additional context\n<body>\n\n` (manager.mjs:3168). Anchored
|
|
242
248
|
// to start; body runs up to the next `# ` section boundary or end. The
|
|
243
249
|
// `\n\n` separator manager.mjs emits is included.
|
|
@@ -36,7 +36,7 @@ export const TOOL_DEFS = [
|
|
|
36
36
|
properties: {
|
|
37
37
|
query: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Search text, or array for independent fan-out queries.' },
|
|
38
38
|
id: { anyOf: [{ type: 'number' }, { type: 'array', items: { type: 'number' }, minItems: 1 }], description: 'Exact #id(s) from recall. Do not invent ids.' },
|
|
39
|
-
period: { type: 'string', description: "last (recent sessions; +query topic-filter; limit=session count
|
|
39
|
+
period: { type: 'string', description: "last (recent sessions; +query topic-filter; limit=session count), Nm/Nh/Nd, today/yesterday/this_week/last_week, all, YYYY-MM-DD, date~date, HH:MM~HH:MM, or 'YYYY-MM-DD HH:MM~HH:MM'." },
|
|
40
40
|
limit: { type: 'number', description: 'Max entries.' },
|
|
41
41
|
offset: { type: 'number', description: 'Skip entries.' },
|
|
42
42
|
sort: { type: 'string', enum: ['importance', 'date'], description: 'importance or date.' },
|
|
@@ -51,7 +51,7 @@ export function executionModeSchemaDescription(defaultMode = 'sync') {
|
|
|
51
51
|
if (defaultMode === 'async') {
|
|
52
52
|
return 'sync = inline result; async = task_id + completion notification. Default async.';
|
|
53
53
|
}
|
|
54
|
-
return '
|
|
54
|
+
return 'Default sync.';
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function taskIdFromArgs(args = {}) {
|
|
@@ -425,7 +425,11 @@ export function createLifecycleApi(deps) {
|
|
|
425
425
|
setSession(resumed);
|
|
426
426
|
applyResolvedCwd(resolveResumeCwd(resumed, getCurrentCwd()), { markRefresh: false });
|
|
427
427
|
const route = getRoute();
|
|
428
|
-
|
|
428
|
+
// The resumed session's OWN effort wins. resolveRoute always returns an
|
|
429
|
+
// effort key, so this used to reinstate the effort of whichever session
|
|
430
|
+
// happened to be open before — a schedule/webhook session opened with a
|
|
431
|
+
// different effort than the one it actually ran with.
|
|
432
|
+
const resumeEffort = resumed.effort || (hasOwn(route, 'effort') ? route.effort : undefined);
|
|
429
433
|
setRoute(resolveRoute(getConfig(), { provider: resumed.provider, model: resumed.model, effort: resumeEffort }));
|
|
430
434
|
await refreshRouteEffort();
|
|
431
435
|
const session = getSession();
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -1179,9 +1179,8 @@ var init_user_data_guard = __esm({
|
|
|
1179
1179
|
"src/runtime/shared/user-data-guard.mjs"() {
|
|
1180
1180
|
USER_DATA_FILES = [
|
|
1181
1181
|
"mixdog-config.json",
|
|
1182
|
-
"
|
|
1183
|
-
"
|
|
1184
|
-
"history/bot.md"
|
|
1182
|
+
"instructions.md",
|
|
1183
|
+
"user-workflow.md"
|
|
1185
1184
|
];
|
|
1186
1185
|
USER_DATA_DIRS = [
|
|
1187
1186
|
"schedules",
|
|
@@ -7,8 +7,8 @@ agents:
|
|
|
7
7
|
|
|
8
8
|
# Solo
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Consult the user and build the plan together. Before the user explicitly
|
|
11
|
+
approves the latest plan, work is
|
|
12
12
|
read-only investigation and planning — no edits, no state mutation. A new or
|
|
13
13
|
changed request resets planning; a scope change requires fresh approval.
|
|
14
14
|
|