mixdog 0.9.24 → 0.9.25
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 +2 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/explore-bench-tmp.mjs +1 -1
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/src/defaults/cycle3-review-prompt.md +11 -4
- package/src/defaults/memory-promote-prompt.md +9 -0
- package/src/rules/lead/02-channels.md +3 -3
- package/src/runtime/channels/backends/discord.mjs +10 -3
- package/src/runtime/channels/lib/inbound-handler.mjs +45 -1
- package/src/runtime/memory/lib/cycle-scheduler.mjs +17 -1
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +59 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +10 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +79 -9
- package/src/tui/dist/index.mjs +37 -3
- package/src/tui/engine/agent-job-feed.mjs +42 -3
- package/src/workflows/default/WORKFLOW.md +29 -38
- package/src/workflows/solo/WORKFLOW.md +15 -20
|
@@ -11,13 +11,17 @@ function __mixdogMemoryLog(...args) {
|
|
|
11
11
|
// {{CORE_REVIEW}} block for defaults/cycle3-review-prompt.md, then asks the
|
|
12
12
|
// maintenance-preset LLM for one verdict per id. By default Cycle3 performs
|
|
13
13
|
// conservative cleanup: safe compression updates and strict duplicate merges
|
|
14
|
-
// are applied
|
|
14
|
+
// are applied. Deletes now also apply in conservative mode when the model
|
|
15
|
+
// tags the entry as clear junk (a whitelisted junk reason) and — for
|
|
16
|
+
// redundant-with-default reasons — the text actually echoes a current rule,
|
|
17
|
+
// bounded by a per-run delete cap so a run keeps a safety margin instead of
|
|
18
|
+
// nuking the whole set. Unreasoned / non-junk deletes still require APPLY.
|
|
15
19
|
//
|
|
16
20
|
// Verdict line grammar (mirrors parseUnifiedFormat in memory-cycle2.mjs):
|
|
17
21
|
// <id>|keep
|
|
18
22
|
// <id>|update|<element>|<summary>
|
|
19
23
|
// <id>|merge|<target_id>|<source_ids_csv>
|
|
20
|
-
// <id>|delete
|
|
24
|
+
// <id>|delete|<reason>
|
|
21
25
|
|
|
22
26
|
import { existsSync, readFileSync } from 'fs'
|
|
23
27
|
import { join } from 'path'
|
|
@@ -136,6 +140,58 @@ function isStrictDuplicate(a, b) {
|
|
|
136
140
|
return sim >= 0.78
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
// Whitelisted delete reasons that conservative mode may auto-apply. These are
|
|
144
|
+
// the "clear junk" classes: a copy of a built-in/default rule, a bare
|
|
145
|
+
// restatement, an obsolete/already-implemented decision, or a past-event log.
|
|
146
|
+
// Anything outside this set (or a bare `delete` with no reason) stays held for
|
|
147
|
+
// APPLY CYCLE3 so genuinely durable rules are never removed unattended.
|
|
148
|
+
const SAFE_DELETE_REASONS = new Set([
|
|
149
|
+
'duplicate', 'dup', 'duplicate_of_default', 'default', 'redundant',
|
|
150
|
+
'restatement', 'restate', 'restates_default',
|
|
151
|
+
'obsolete', 'implemented', 'done', 'completed', 'resolved',
|
|
152
|
+
'superseded_decision', 'stale', 'past_event', 'event_log', 'log',
|
|
153
|
+
])
|
|
154
|
+
// Reasons that claim redundancy with a built-in/default rule — these demand
|
|
155
|
+
// corroboration (the core text must actually echo the current rules digest)
|
|
156
|
+
// before a conservative auto-delete, so a mislabelled durable rule survives.
|
|
157
|
+
const DEFAULT_ECHO_REASONS = new Set([
|
|
158
|
+
'duplicate', 'dup', 'duplicate_of_default', 'default', 'redundant',
|
|
159
|
+
'restatement', 'restate', 'restates_default',
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
function normalizeDeleteReason(reason) {
|
|
163
|
+
return String(reason ?? '')
|
|
164
|
+
.toLowerCase()
|
|
165
|
+
.trim()
|
|
166
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
167
|
+
.replace(/^_+|_+$/g, '')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Max trigram similarity of `text` against any substantive line of the current
|
|
171
|
+
// rules digest — how strongly a core entry echoes a built-in rule.
|
|
172
|
+
function digestRedundancy(text, rulesDigest) {
|
|
173
|
+
if (!text || !rulesDigest) return 0
|
|
174
|
+
const lines = String(rulesDigest).split('\n').map(l => l.trim()).filter(l => l.length >= 12)
|
|
175
|
+
let best = 0
|
|
176
|
+
for (const line of lines) {
|
|
177
|
+
const d = charDice(text, line)
|
|
178
|
+
if (d > best) best = d
|
|
179
|
+
if (best >= 0.9) break
|
|
180
|
+
}
|
|
181
|
+
return best
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isSafeConservativeDelete(core, action, rulesDigest) {
|
|
185
|
+
const reason = normalizeDeleteReason(action?.reason)
|
|
186
|
+
if (!reason) return { ok: false, reason: 'delete needs a junk reason → APPLY CYCLE3' }
|
|
187
|
+
if (!SAFE_DELETE_REASONS.has(reason)) return { ok: false, reason: `delete reason "${reason}" not in safe set → APPLY CYCLE3` }
|
|
188
|
+
if (DEFAULT_ECHO_REASONS.has(reason)) {
|
|
189
|
+
const red = digestRedundancy(coreText(core), rulesDigest)
|
|
190
|
+
if (red < 0.5) return { ok: false, reason: `not redundant with defaults (sim=${red.toFixed(2)})` }
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, reason }
|
|
193
|
+
}
|
|
194
|
+
|
|
139
195
|
function formatRelatedRow(r) {
|
|
140
196
|
const tag = r.project_id ? r.project_id : 'COMMON'
|
|
141
197
|
const stat = r.status ? `[${r.status}]` : '[?]'
|
|
@@ -206,7 +262,8 @@ function parseVerdicts(raw, idSet) {
|
|
|
206
262
|
}
|
|
207
263
|
actions.push({ id, verb: 'merge', targetId, sourceIds })
|
|
208
264
|
} else if (verb === 'delete') {
|
|
209
|
-
|
|
265
|
+
const reason = (parts[2] ?? '').trim()
|
|
266
|
+
actions.push({ id, verb: 'delete', reason: reason || null })
|
|
210
267
|
} else if (verb === 'superseded' || verb === 'supersede') {
|
|
211
268
|
// Require newer-id proof: `id|superseded|<newer_id>`. Without a valid
|
|
212
269
|
// newer active core id the supersession has no evidence → drop to keep.
|
|
@@ -553,6 +610,10 @@ async function _runCycle3Impl(db, config, dataDir, options = {}) {
|
|
|
553
610
|
const held = { updated: 0, merged: 0, deleted: 0, superseded: 0 }
|
|
554
611
|
const details = []
|
|
555
612
|
const touched = new Set() // ids already acted on this cycle — avoid double action
|
|
613
|
+
// Safety margin: even when every junk verdict is legitimate, a single
|
|
614
|
+
// conservative run applies at most this many outright deletes and holds the
|
|
615
|
+
// rest for the next pass. Prevents an over-eager model from clearing the set.
|
|
616
|
+
const conservativeDeleteCap = Math.max(1, Math.ceil(cores.length * 0.35))
|
|
556
617
|
|
|
557
618
|
// Core-store edit/delete calls are the mutation unit; checkpoints sit before
|
|
558
619
|
// each action/source and after each awaited unit, not inside one file-store write.
|
|
@@ -596,19 +657,28 @@ async function _runCycle3Impl(db, config, dataDir, options = {}) {
|
|
|
596
657
|
}
|
|
597
658
|
if (a.verb === 'delete') {
|
|
598
659
|
proposed.deleted++
|
|
599
|
-
|
|
660
|
+
// confirmed → always delete. conservative → delete only clear junk (safe
|
|
661
|
+
// reason, corroborated for redundant-with-default), capped per run.
|
|
662
|
+
// proposal → always hold.
|
|
663
|
+
let applyDelete = confirmed
|
|
664
|
+
let holdReason = 'proposal mode'
|
|
665
|
+
let safeReason = null
|
|
666
|
+
if (!confirmed && conservative) {
|
|
667
|
+
const safeDel = isSafeConservativeDelete(coreById.get(a.id), a, rulesDigest)
|
|
668
|
+
if (!safeDel.ok) holdReason = safeDel.reason
|
|
669
|
+
else if (deleted >= conservativeDeleteCap) holdReason = `conservative delete cap ${conservativeDeleteCap} reached`
|
|
670
|
+
else { applyDelete = true; safeReason = safeDel.reason }
|
|
671
|
+
}
|
|
672
|
+
if (!applyDelete) {
|
|
600
673
|
held.deleted++
|
|
601
|
-
details.push({
|
|
602
|
-
id: a.id, verb: 'delete', applied: false, held: true,
|
|
603
|
-
reason: conservative ? 'delete requires APPLY CYCLE3' : 'proposal mode',
|
|
604
|
-
})
|
|
674
|
+
details.push({ id: a.id, verb: 'delete', applied: false, held: true, reason: holdReason })
|
|
605
675
|
touched.add(a.id)
|
|
606
676
|
continue
|
|
607
677
|
}
|
|
608
678
|
try {
|
|
609
679
|
await deleteCore(dataDir, a.id)
|
|
610
680
|
deleted++
|
|
611
|
-
details.push({ id: a.id, verb: 'delete', applied: true })
|
|
681
|
+
details.push({ id: a.id, verb: 'delete', applied: true, reason: safeReason || 'confirmed' })
|
|
612
682
|
touched.add(a.id)
|
|
613
683
|
} catch (err) {
|
|
614
684
|
if (signal?.aborted) throw signal.reason ?? err
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -22810,6 +22810,15 @@ function createToolCardResults({
|
|
|
22810
22810
|
}
|
|
22811
22811
|
|
|
22812
22812
|
// src/tui/engine/agent-job-feed.mjs
|
|
22813
|
+
function parseInboundImagePaths(raw) {
|
|
22814
|
+
if (typeof raw !== "string" || !raw) return [];
|
|
22815
|
+
try {
|
|
22816
|
+
const arr = JSON.parse(raw);
|
|
22817
|
+
return Array.isArray(arr) ? arr.filter((p) => typeof p === "string" && p.length > 0) : [];
|
|
22818
|
+
} catch {
|
|
22819
|
+
return [];
|
|
22820
|
+
}
|
|
22821
|
+
}
|
|
22813
22822
|
function createAgentJobFeed({
|
|
22814
22823
|
runtime,
|
|
22815
22824
|
getState,
|
|
@@ -22896,13 +22905,38 @@ function createAgentJobFeed({
|
|
|
22896
22905
|
set(agentStatusState({ force: true }));
|
|
22897
22906
|
}
|
|
22898
22907
|
const modelContent = String(delivery.modelContent ?? delivery.displayText ?? text).trim();
|
|
22899
|
-
|
|
22900
|
-
|
|
22908
|
+
const imagePaths = parseInboundImagePaths(event?.meta?.image_paths);
|
|
22909
|
+
if (!modelContent && imagePaths.length === 0) return true;
|
|
22910
|
+
const enqueueOpts = {
|
|
22901
22911
|
mode: "task-notification",
|
|
22902
22912
|
priority: "next",
|
|
22903
22913
|
key: notificationKey || void 0,
|
|
22904
22914
|
displayText: delivery.displayText || text
|
|
22905
|
-
}
|
|
22915
|
+
};
|
|
22916
|
+
if (imagePaths.length > 0) {
|
|
22917
|
+
void (async () => {
|
|
22918
|
+
if (getDisposed()) return;
|
|
22919
|
+
const parts = [];
|
|
22920
|
+
if (modelContent) parts.push({ type: "text", text: modelContent });
|
|
22921
|
+
for (const p of imagePaths) {
|
|
22922
|
+
let att = null;
|
|
22923
|
+
try {
|
|
22924
|
+
att = await readImageAttachmentFromPath(p);
|
|
22925
|
+
} catch {
|
|
22926
|
+
att = null;
|
|
22927
|
+
}
|
|
22928
|
+
if (!att) continue;
|
|
22929
|
+
if (att.metadataText) parts.push({ type: "text", text: att.metadataText });
|
|
22930
|
+
parts.push({ type: "image", data: att.content, mimeType: att.mediaType || "image/png" });
|
|
22931
|
+
}
|
|
22932
|
+
if (getDisposed()) return;
|
|
22933
|
+
const hasImage = parts.some((part) => part.type === "image");
|
|
22934
|
+
if (!hasImage && !modelContent) return;
|
|
22935
|
+
enqueue(hasImage ? parts : modelContent, enqueueOpts);
|
|
22936
|
+
})();
|
|
22937
|
+
return true;
|
|
22938
|
+
}
|
|
22939
|
+
enqueue(modelContent, enqueueOpts);
|
|
22906
22940
|
return true;
|
|
22907
22941
|
});
|
|
22908
22942
|
}
|
|
@@ -23,6 +23,20 @@ import {
|
|
|
23
23
|
notificationQueueKey,
|
|
24
24
|
resolveTuiRuntimeNotificationDelivery,
|
|
25
25
|
} from './notification-plan.mjs';
|
|
26
|
+
import { readImageAttachmentFromPath } from '../paste-attachments.mjs';
|
|
27
|
+
|
|
28
|
+
// Channel inbound images arrive as a JSON-array-of-paths meta value (stringified
|
|
29
|
+
// across the notify IPC boundary). Parse defensively; a malformed value simply
|
|
30
|
+
// yields no images and the notification degrades to its text body.
|
|
31
|
+
function parseInboundImagePaths(raw) {
|
|
32
|
+
if (typeof raw !== 'string' || !raw) return [];
|
|
33
|
+
try {
|
|
34
|
+
const arr = JSON.parse(raw);
|
|
35
|
+
return Array.isArray(arr) ? arr.filter((p) => typeof p === 'string' && p.length > 0) : [];
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
export function createAgentJobFeed({
|
|
28
42
|
runtime,
|
|
@@ -117,13 +131,38 @@ export function createAgentJobFeed({
|
|
|
117
131
|
set(agentStatusState({ force: true }));
|
|
118
132
|
}
|
|
119
133
|
const modelContent = String(delivery.modelContent ?? delivery.displayText ?? text).trim();
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
const imagePaths = parseInboundImagePaths(event?.meta?.image_paths);
|
|
135
|
+
if (!modelContent && imagePaths.length === 0) return true;
|
|
136
|
+
const enqueueOpts = {
|
|
122
137
|
mode: 'task-notification',
|
|
123
138
|
priority: 'next',
|
|
124
139
|
key: notificationKey || undefined,
|
|
125
140
|
displayText: delivery.displayText || text,
|
|
126
|
-
}
|
|
141
|
+
};
|
|
142
|
+
if (imagePaths.length > 0) {
|
|
143
|
+
// Read each downloaded image into a real image content block so the
|
|
144
|
+
// channel turn is vision-visible. Async, but the notification is
|
|
145
|
+
// already "handled" (return true) — the enqueue lands on resolve.
|
|
146
|
+
// Any unreadable path is skipped; if none load, fall back to text.
|
|
147
|
+
void (async () => {
|
|
148
|
+
if (getDisposed()) return;
|
|
149
|
+
const parts = [];
|
|
150
|
+
if (modelContent) parts.push({ type: 'text', text: modelContent });
|
|
151
|
+
for (const p of imagePaths) {
|
|
152
|
+
let att = null;
|
|
153
|
+
try { att = await readImageAttachmentFromPath(p); } catch { att = null; }
|
|
154
|
+
if (!att) continue;
|
|
155
|
+
if (att.metadataText) parts.push({ type: 'text', text: att.metadataText });
|
|
156
|
+
parts.push({ type: 'image', data: att.content, mimeType: att.mediaType || 'image/png' });
|
|
157
|
+
}
|
|
158
|
+
if (getDisposed()) return;
|
|
159
|
+
const hasImage = parts.some((part) => part.type === 'image');
|
|
160
|
+
if (!hasImage && !modelContent) return;
|
|
161
|
+
enqueue(hasImage ? parts : modelContent, enqueueOpts);
|
|
162
|
+
})();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
enqueue(modelContent, enqueueOpts);
|
|
127
166
|
return true;
|
|
128
167
|
});
|
|
129
168
|
}
|
|
@@ -7,44 +7,35 @@ agents: worker, heavy-worker, reviewer, debugger, maintainer
|
|
|
7
7
|
|
|
8
8
|
# Default Workflow
|
|
9
9
|
|
|
10
|
-
HARD APPROVAL GATE —
|
|
11
|
-
"proceed", "ㄱㄱ")
|
|
12
|
-
|
|
13
|
-
pointing out a problem is NOT approval.
|
|
10
|
+
HARD APPROVAL GATE — no execution (changes, state mutations, delegation)
|
|
11
|
+
before an explicit go-ahead ("do it", "proceed", "ㄱㄱ"); read-only
|
|
12
|
+
exploration only. Diagnosis agreement or problem-pointing is NOT approval.
|
|
14
13
|
|
|
15
|
-
Lead supervises: delegates, coordinates, judges, decides. Route by complexity
|
|
16
|
-
(
|
|
17
|
-
- Lead directly: simple 1–2 step work,
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
- Reviewer: verify implementation scopes (diff, regressions, missing checks).
|
|
22
|
-
- Debugger: very high complexity, or when root-causing has already failed at
|
|
23
|
-
least once.
|
|
14
|
+
Lead supervises: delegates, coordinates, judges, decides. Route by complexity
|
|
15
|
+
(after approval):
|
|
16
|
+
- Lead directly: simple 1–2 step work, coordination, config, git deployment.
|
|
17
|
+
- Worker: multi-step implementation. Heavy Worker: high-complexity scopes.
|
|
18
|
+
- Reviewer: verify implementation scopes. Debugger: very high complexity, or
|
|
19
|
+
root-causing already failed once.
|
|
24
20
|
|
|
25
|
-
1. Plan —
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
user asks for debugging, or a bug survives 2+ fix cycles, have the debugger
|
|
45
|
-
investigate first instead of another fix round.
|
|
46
|
-
4. Report — synthesize outcome + key evidence (never forward raw agent
|
|
47
|
-
output), state the final state, and ask about ship/deploy when relevant.
|
|
48
|
-
Prepare deploy/build/commit only after user feedback with no issues.
|
|
21
|
+
1. Plan — present a draft plan before ANY implementation; if not approved,
|
|
22
|
+
revise and re-present (ping-pong) until an explicit go-ahead.
|
|
23
|
+
2. Delegate — split into the maximum independent scopes; spawn all in the
|
|
24
|
+
SAME turn (parallel by default; sequential steps only inside one complex
|
|
25
|
+
scope, gated build/test-green). Shared/cross-cutting code does NOT justify
|
|
26
|
+
merging scopes — split per path, verify shared parts yourself; a genuinely
|
|
27
|
+
inseparable single scope must be stated. Briefs per the Lead brief
|
|
28
|
+
contract. After spawning async agents, END THE TURN.
|
|
29
|
+
3. Review — pair one reviewer 1:1 per implementation scope, same turn.
|
|
30
|
+
Cross-check agent results yourself; send fixes back to the original scope
|
|
31
|
+
and loop fix -> re-verify until clean. Skip only for simple low-risk work.
|
|
32
|
+
Debugger first when the user asks for debugging or a bug survives 2+ fix
|
|
33
|
+
cycles.
|
|
34
|
+
On each agent report: tell the user what was received (scope + verdict)
|
|
35
|
+
and how work proceeds, marked in-progress — never as a conclusion.
|
|
36
|
+
4. Report — final report briefs the whole work vs the approved plan and the
|
|
37
|
+
verified result, distinct from interim updates. Never forward raw agent
|
|
38
|
+
output. Ask about ship/deploy when relevant; deploy/build/commit only
|
|
39
|
+
after user feedback with no issues.
|
|
49
40
|
|
|
50
|
-
On
|
|
41
|
+
On major direction shifts mid-work, pause and re-consult the user.
|
|
@@ -7,27 +7,22 @@ agents:
|
|
|
7
7
|
|
|
8
8
|
# Solo Workflow
|
|
9
9
|
|
|
10
|
-
HARD APPROVAL GATE —
|
|
11
|
-
"proceed", "ㄱㄱ")
|
|
12
|
-
|
|
13
|
-
problem is NOT approval.
|
|
10
|
+
HARD APPROVAL GATE — no execution (changes, state mutations) before an
|
|
11
|
+
explicit go-ahead ("do it", "proceed", "ㄱㄱ"); read-only exploration only.
|
|
12
|
+
Diagnosis agreement or problem-pointing is NOT approval.
|
|
14
13
|
|
|
15
|
-
1. Plan —
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
remaining risk or requested next step. Only after user feedback with no
|
|
25
|
-
issues prepare deploy/build/commit.
|
|
14
|
+
1. Plan — present a draft plan before ANY implementation; if not approved,
|
|
15
|
+
revise and re-present (ping-pong) until an explicit go-ahead. When
|
|
16
|
+
ambiguous, restate the plan and ask.
|
|
17
|
+
2. Execute — Lead does all work directly; delegation forbidden. Interim
|
|
18
|
+
updates are marked in-progress — never phrased as conclusions.
|
|
19
|
+
3. Verify — check and fix directly until clean or a blocker is reported.
|
|
20
|
+
4. Report — final report briefs the whole work vs the approved plan, the
|
|
21
|
+
verification result, and remaining risk/next step, distinct from interim
|
|
22
|
+
updates. Deploy/build/commit only after user feedback with no issues.
|
|
26
23
|
|
|
27
|
-
On
|
|
28
|
-
before continuing.
|
|
24
|
+
On major direction shifts mid-work, pause and re-consult the user.
|
|
29
25
|
|
|
30
26
|
Delegation rule:
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
handled by Lead directly.
|
|
27
|
+
- Never delegate, spawn, send, or ask any agent to perform work; ignore any
|
|
28
|
+
available-agent section while this workflow is active.
|