@yemi33/minions 0.1.2424 → 0.1.2425
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/bin/install-internal-minions.js +1209 -0
- package/bin/minions.js +75 -9
- package/dashboard/docs/typography.md +27 -12
- package/dashboard/js/command-center.js +13 -11
- package/dashboard/js/live-stream.js +1 -1
- package/dashboard/js/memory-search.js +388 -43
- package/dashboard/js/modal-qa.js +5 -5
- package/dashboard/js/qa.js +39 -26
- package/dashboard/js/refresh.js +37 -18
- package/dashboard/js/render-agents.js +14 -18
- package/dashboard/js/render-dispatch.js +14 -74
- package/dashboard/js/render-inbox.js +3 -3
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +45 -20
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +14 -14
- package/dashboard/js/render-prd.js +46 -46
- package/dashboard/js/render-prs.js +9 -65
- package/dashboard/js/render-schedules.js +5 -5
- package/dashboard/js/render-skills.js +6 -6
- package/dashboard/js/render-utils.js +4 -4
- package/dashboard/js/render-watches.js +4 -16
- package/dashboard/js/render-work-items.js +95 -107
- package/dashboard/js/settings.js +175 -74
- package/dashboard/layout.html +3 -2
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/work.html +1 -1
- package/dashboard/shared/model-display.js +9 -0
- package/dashboard/shared/watches-source.js +43 -0
- package/dashboard/shared/welcome-popup.js +259 -0
- package/dashboard/slim/body.html +6 -6
- package/dashboard/slim/js/chat.js +4 -2
- package/dashboard/slim/js/history.js +10 -3
- package/dashboard/slim/js/modals-tiles.js +85 -16
- package/dashboard/slim/js/status.js +12 -16
- package/dashboard/slim/styles.css +24 -4
- package/dashboard/styles.css +62 -27
- package/dashboard-build.js +8 -2
- package/dashboard.js +3854 -2514
- package/docs/README.md +6 -4
- package/docs/api-errors.md +144 -0
- package/docs/auto-discovery.md +83 -61
- package/docs/capture-demos.js +173 -30
- package/docs/command-center.md +5 -1
- package/docs/completion-reports.md +127 -11
- package/docs/constants.md +15 -3
- package/docs/constellation-style-telemetry.md +2 -2
- package/docs/copilot-cli-schema.md +32 -22
- package/docs/cross-repo-plans.md +30 -30
- package/docs/demo/memory-system.html +1 -1
- package/docs/deprecated.json +132 -18
- package/docs/diagnostics-memory.md +8 -4
- package/docs/human-vs-automated.md +2 -2
- package/docs/internal-install.md +190 -0
- package/docs/kb-pr3223-cascade-archiving.md +16 -0
- package/docs/kb-pr696-merge-conflict-docs.md +23 -0
- package/docs/kb-sweep.md +2 -2
- package/docs/keep-processes.md +8 -1
- package/docs/live-checkout-mode.md +1 -1
- package/docs/managed-spawn.md +21 -3
- package/docs/named-agents.md +3 -2
- package/docs/plan-lifecycle.md +32 -32
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/qa-runbook-lifecycle.md +67 -51
- package/docs/qa-runbooks.md +2 -2
- package/docs/runtime-adapters.md +101 -50
- package/docs/security.md +28 -0
- package/docs/self-improvement.md +47 -13
- package/docs/skills.md +29 -0
- package/docs/slim-ux/concepts.md +2 -2
- package/docs/specs/agent-configurability.md +43 -41
- package/docs/specs/agent-rename.md +13 -13
- package/docs/team-memory.md +283 -14
- package/docs/tutorials/03-plan-driven-feature.md +3 -3
- package/docs/tutorials/04-runtimes-and-harness.md +18 -21
- package/docs/tutorials/08-operations-and-recovery.md +3 -2
- package/docs/tutorials/README.md +4 -3
- package/docs/visual-evidence-ci.md +103 -0
- package/docs/watches.md +35 -30
- package/docs/worktree-lifecycle.md +151 -13
- package/engine/acp-transport.js +1 -0
- package/engine/ado.js +80 -33
- package/engine/agent-api-validation.js +571 -0
- package/engine/agent-worker-pool.js +7 -6
- package/engine/api-contracts/agent-content.js +761 -0
- package/engine/api-contracts/cc-ops.js +219 -0
- package/engine/api-contracts/config-runtime.js +419 -0
- package/engine/api-contracts/core.js +138 -0
- package/engine/api-contracts/index.js +506 -0
- package/engine/api-contracts/orchestration.js +602 -0
- package/engine/api-contracts/pull-requests.js +323 -0
- package/engine/api-contracts/qa-process.js +764 -0
- package/engine/api-contracts/work-plan-prd.js +494 -0
- package/engine/api-validation.js +543 -0
- package/engine/cc-api-validation.js +586 -0
- package/engine/cc-worker-pool.js +9 -1
- package/engine/cleanup.js +393 -271
- package/engine/cli.js +65 -7
- package/engine/comment-classifier.js +70 -5
- package/engine/consolidation.js +123 -5
- package/engine/content-api-validation.js +606 -0
- package/engine/create-pr-worktree.js +22 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
- package/engine/dispatch.js +33 -0
- package/engine/distribution.js +189 -0
- package/engine/execution-model.js +59 -0
- package/engine/features.js +12 -0
- package/engine/github.js +67 -25
- package/engine/harness-context.js +313 -0
- package/engine/inbox-store.js +1 -1
- package/engine/kb-sweep.js +161 -38
- package/engine/keep-process-sweep.js +43 -3
- package/engine/lifecycle.js +1066 -165
- package/engine/llm.js +4 -0
- package/engine/managed-spawn.js +463 -48
- package/engine/meeting.js +197 -34
- package/engine/memory-retrieval.js +280 -0
- package/engine/memory-store.js +821 -38
- package/engine/model-discovery.js +17 -3
- package/engine/pipeline.js +802 -14
- package/engine/plan-prd-validation.js +745 -0
- package/engine/playbook.js +206 -17
- package/engine/pooled-agent-process.js +6 -1
- package/engine/pr-action.js +1 -1
- package/engine/pr-issue-validation.js +653 -0
- package/engine/pr-resolve.js +75 -13
- package/engine/prd-store.js +39 -6
- package/engine/preflight.js +7 -2
- package/engine/process-utils.js +79 -31
- package/engine/projects.js +198 -12
- package/engine/promotion.js +371 -0
- package/engine/qa-from-prd.js +3 -0
- package/engine/qa-process-validation.js +572 -0
- package/engine/qa-runbooks.js +41 -37
- package/engine/qa-runners/maestro.js +11 -7
- package/engine/qa-runners/playwright.js +37 -14
- package/engine/qa-runners.js +9 -10
- package/engine/qa-runs.js +124 -11
- package/engine/qa-sessions.js +139 -40
- package/engine/quarantine-refs.js +81 -11
- package/engine/queries.js +163 -9
- package/engine/review-learning-backfill.js +482 -0
- package/engine/review-learning.js +1236 -0
- package/engine/runtimes/claude.js +60 -2
- package/engine/runtimes/codex.js +57 -16
- package/engine/runtimes/contract.js +21 -0
- package/engine/runtimes/copilot.js +51 -3
- package/engine/runtimes/index.js +1 -0
- package/engine/scheduler.js +201 -32
- package/engine/settings-validation.js +909 -0
- package/engine/shared.js +705 -208
- package/engine/spawn-agent.js +48 -21
- package/engine/steering-constraints.js +31 -0
- package/engine/steering-store.js +10 -2
- package/engine/steering.js +14 -2
- package/engine/timeout.js +404 -111
- package/engine/untrusted-fence.js +24 -10
- package/engine/watch-actions.js +7 -2
- package/engine/watches.js +353 -51
- package/engine/work-item-validation.js +561 -0
- package/engine/work-items-store.js +61 -1
- package/engine/worktree-gc.js +656 -37
- package/engine/worktree-preflight.js +154 -0
- package/engine.js +2068 -342
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +88 -41
- package/playbooks/build-fix-complex.md +3 -3
- package/playbooks/docs.md +2 -2
- package/playbooks/fix.md +18 -18
- package/playbooks/implement.md +18 -18
- package/playbooks/qa-session-draft.md +10 -12
- package/playbooks/qa-session-execute.md +16 -12
- package/playbooks/qa-validate.md +8 -7
- package/playbooks/shared-rules.md +45 -0
- package/playbooks/test.md +4 -0
- package/prompts/cc-system.md +1 -1
- package/skills/check-self-authored-review-comment/SKILL.md +2 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Review-learning lifecycle fields extend migration 017 in place. Existing
|
|
2
|
+
// memory rows and FTS content stay intact; only exact generated feedback
|
|
3
|
+
// provenance is moved out of the recallable `active` state.
|
|
4
|
+
|
|
5
|
+
function _hasColumn(db, table, column) {
|
|
6
|
+
return db.prepare(`PRAGMA table_info(${table})`).all().some(row => row.name === column);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function _addColumn(db, table, definition) {
|
|
10
|
+
const column = definition.trim().split(/\s+/, 1)[0];
|
|
11
|
+
if (!_hasColumn(db, table, column)) db.exec(`ALTER TABLE ${table} ADD COLUMN ${definition}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function _parseObject(value) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(value);
|
|
17
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Mirror engine/review-learning.js#normalizeCanonicalPrId's space-compatible
|
|
24
|
+
// contract so the quarantine accepts valid canonical IDs whose ADO
|
|
25
|
+
// org/project/repo segments contain spaces (or %20) — createReviewFeedbackForAuthor
|
|
26
|
+
// writes the raw canonical `pr.id`, and valid ADO scopes may contain spaces.
|
|
27
|
+
// Kept self-contained: migrations must not import runtime engine modules.
|
|
28
|
+
function _isCanonicalPrId(value) {
|
|
29
|
+
const match = String(value == null ? '' : value).trim().match(/^(github|ado):(.+?)#([1-9]\d*)$/i);
|
|
30
|
+
if (!match) return false;
|
|
31
|
+
const host = match[1].toLowerCase();
|
|
32
|
+
const expectedSegments = host === 'github' ? 2 : 3;
|
|
33
|
+
const segments = match[2]
|
|
34
|
+
.split('/')
|
|
35
|
+
.map(segment => segment.replace(/%20/gi, ' ').trim().toLowerCase());
|
|
36
|
+
if (segments.length !== expectedSegments) return false;
|
|
37
|
+
return segments.every(segment => (
|
|
38
|
+
segment
|
|
39
|
+
&& !/[|#\u0000-\u001f\u007f]/.test(segment)
|
|
40
|
+
&& (host !== 'github' || /^[a-z0-9][a-z0-9._-]*$/.test(segment))
|
|
41
|
+
));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _frontmatterBlocks(body) {
|
|
45
|
+
const blocks = [];
|
|
46
|
+
const re = /^---\r?\n([\s\S]*?)\r?\n---/gm;
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = re.exec(String(body || ''))) !== null) {
|
|
49
|
+
const fields = {};
|
|
50
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
51
|
+
const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
52
|
+
if (field) fields[field[1].toLowerCase()] = field[2].trim();
|
|
53
|
+
}
|
|
54
|
+
blocks.push(fields);
|
|
55
|
+
}
|
|
56
|
+
return blocks;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _generatedFeedbackProvenance(row) {
|
|
60
|
+
if (row.source_type !== 'knowledge-base' || row.status !== 'active') return null;
|
|
61
|
+
if (!/^knowledge\/.+\.md$/i.test(String(row.source_path || ''))) return null;
|
|
62
|
+
|
|
63
|
+
const metadata = _parseObject(row.metadata);
|
|
64
|
+
const blocks = _frontmatterBlocks(row.body);
|
|
65
|
+
const sourceBlock = blocks.find(block => block.source && block.agent);
|
|
66
|
+
const detailBlock = blocks.find(block => block.sourcepr && block.reviewer && block.author);
|
|
67
|
+
if (!sourceBlock) return null;
|
|
68
|
+
if (String(sourceBlock.agent).toLowerCase() !== 'feedback') return null;
|
|
69
|
+
if (metadata.agent != null && String(metadata.agent).toLowerCase() !== 'feedback') return null;
|
|
70
|
+
|
|
71
|
+
const sourceRef = String(sourceBlock.source || '').trim();
|
|
72
|
+
if (!/^feedback-[a-z][a-z0-9-]{1,63}-from-[a-z][a-z0-9-]{1,63}-.+-\d{4}-\d{2}-\d{2}\.md$/.test(sourceRef)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (detailBlock) {
|
|
76
|
+
const author = String(detailBlock.author || '').trim().toLowerCase();
|
|
77
|
+
const reviewer = String(detailBlock.reviewer || '').trim().toLowerCase();
|
|
78
|
+
if (String(detailBlock.agent || 'feedback').toLowerCase() !== 'feedback') return null;
|
|
79
|
+
if (!/^[a-z][a-z0-9-]{1,63}$/.test(author) || !/^[a-z][a-z0-9-]{1,63}$/.test(reviewer)) return null;
|
|
80
|
+
if (!sourceRef.startsWith(`feedback-${author}-from-${reviewer}-`)) return null;
|
|
81
|
+
if (!_isCanonicalPrId(detailBlock.sourcepr)) return null;
|
|
82
|
+
}
|
|
83
|
+
if (metadata.inbox != null && String(metadata.inbox) !== sourceRef) return null;
|
|
84
|
+
if (row.source_ref !== sourceRef && row.source_ref !== row.source_path) return null;
|
|
85
|
+
if (!/^# Review Feedback for .+/m.test(String(row.body || ''))) return null;
|
|
86
|
+
return { metadata, sourceRef };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function _quarantineGeneratedFeedback(db) {
|
|
90
|
+
const rows = db.prepare(`
|
|
91
|
+
SELECT id, source_type, source_path, source_ref, status, body, metadata
|
|
92
|
+
FROM memory_records
|
|
93
|
+
WHERE status='active' AND source_type='knowledge-base'
|
|
94
|
+
`).all();
|
|
95
|
+
const update = db.prepare(`
|
|
96
|
+
UPDATE memory_records
|
|
97
|
+
SET status='candidate', metadata=?, updated_at=?
|
|
98
|
+
WHERE id=? AND status='active'
|
|
99
|
+
`);
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
for (const row of rows) {
|
|
102
|
+
const provenance = _generatedFeedbackProvenance(row);
|
|
103
|
+
if (!provenance) continue;
|
|
104
|
+
update.run(JSON.stringify({
|
|
105
|
+
...provenance.metadata,
|
|
106
|
+
legacyUnverified: true,
|
|
107
|
+
candidateState: 'legacy-unverified',
|
|
108
|
+
legacySourceType: row.source_type,
|
|
109
|
+
legacySourceRef: provenance.sourceRef,
|
|
110
|
+
quarantinedBy: 'migration-027',
|
|
111
|
+
}), now, row.id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
version: 27,
|
|
117
|
+
description: 'review-learning candidates, retrieval traces, and lifecycle events',
|
|
118
|
+
up(db) {
|
|
119
|
+
_addColumn(db, 'memory_retrieval_runs', 'dispatch_id TEXT');
|
|
120
|
+
_addColumn(db, 'memory_retrieval_runs', "retrieval_kind TEXT NOT NULL DEFAULT 'general'");
|
|
121
|
+
_addColumn(db, 'memory_retrieval_runs', "selected_ids TEXT NOT NULL DEFAULT '[]'");
|
|
122
|
+
_addColumn(db, 'memory_retrieval_runs', "decision_trace TEXT NOT NULL DEFAULT '[]'");
|
|
123
|
+
|
|
124
|
+
db.exec(`
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_memory_retrieval_dispatch_kind
|
|
126
|
+
ON memory_retrieval_runs(dispatch_id, retrieval_kind, created_at DESC);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE IF NOT EXISTS memory_lifecycle_events (
|
|
129
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
130
|
+
event_key TEXT NOT NULL UNIQUE CHECK(length(event_key) <= 64),
|
|
131
|
+
event_type TEXT NOT NULL CHECK(length(event_type) <= 64),
|
|
132
|
+
memory_id TEXT,
|
|
133
|
+
finding_key TEXT,
|
|
134
|
+
project TEXT NOT NULL CHECK(length(project) <= 120),
|
|
135
|
+
agent TEXT,
|
|
136
|
+
area TEXT NOT NULL DEFAULT 'general' CHECK(length(area) <= 360),
|
|
137
|
+
disposition TEXT,
|
|
138
|
+
work_item_id TEXT,
|
|
139
|
+
dispatch_id TEXT,
|
|
140
|
+
metadata TEXT NOT NULL DEFAULT '{}' CHECK(length(metadata) <= 8192),
|
|
141
|
+
created_at INTEGER NOT NULL
|
|
142
|
+
);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_memory_lifecycle_project_area_created
|
|
144
|
+
ON memory_lifecycle_events(project, area, created_at DESC);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_memory_lifecycle_finding
|
|
146
|
+
ON memory_lifecycle_events(finding_key, event_type, created_at DESC);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_memory_lifecycle_memory
|
|
148
|
+
ON memory_lifecycle_events(memory_id, created_at DESC);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_memory_lifecycle_dispatch
|
|
150
|
+
ON memory_lifecycle_events(dispatch_id, created_at DESC);
|
|
151
|
+
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_memory_review_learning_active
|
|
153
|
+
ON memory_records(scope_key, updated_at DESC)
|
|
154
|
+
WHERE source_type='review-learning'
|
|
155
|
+
AND memory_type='procedural'
|
|
156
|
+
AND scope_type='project'
|
|
157
|
+
AND status='active';
|
|
158
|
+
`);
|
|
159
|
+
|
|
160
|
+
_quarantineGeneratedFeedback(db);
|
|
161
|
+
},
|
|
162
|
+
};
|
package/engine/dispatch.js
CHANGED
|
@@ -977,6 +977,35 @@ function markCompletedSourceWorkItem(item) {
|
|
|
977
977
|
|
|
978
978
|
// ─── Complete Dispatch ───────────────────────────────────────────────────────
|
|
979
979
|
|
|
980
|
+
const WORKTREE_PROTECTION_RETRY_MS = 60 * 1000;
|
|
981
|
+
|
|
982
|
+
function deferDispatchForWorktreeProtection(id, protection = {}, terminal = {}) {
|
|
983
|
+
let deferred = false;
|
|
984
|
+
const now = Date.now();
|
|
985
|
+
mutateDispatch((dispatch) => {
|
|
986
|
+
const item = (dispatch.active || []).find(entry => entry.id === id);
|
|
987
|
+
if (!item) return dispatch;
|
|
988
|
+
const previous = item._worktreeProtectionDeferred || {};
|
|
989
|
+
const previousTerminal = previous.terminal || {};
|
|
990
|
+
item._worktreeProtectionDeferred = {
|
|
991
|
+
deferredAt: previous.deferredAt || new Date(now).toISOString(),
|
|
992
|
+
retryAfter: new Date(now + WORKTREE_PROTECTION_RETRY_MS).toISOString(),
|
|
993
|
+
protectionReason: protection.reason || previous.protectionReason || 'preservation-failed',
|
|
994
|
+
...(protection.error ? { error: String(protection.error) } : {}),
|
|
995
|
+
terminal: {
|
|
996
|
+
reason: terminal.reason || previousTerminal.reason || '',
|
|
997
|
+
resultSummary: terminal.resultSummary || previousTerminal.resultSummary || '',
|
|
998
|
+
completeOpts: terminal.completeOpts && typeof terminal.completeOpts === 'object'
|
|
999
|
+
? terminal.completeOpts
|
|
1000
|
+
: (previousTerminal.completeOpts || {}),
|
|
1001
|
+
},
|
|
1002
|
+
};
|
|
1003
|
+
deferred = true;
|
|
1004
|
+
return dispatch;
|
|
1005
|
+
});
|
|
1006
|
+
return deferred;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
980
1009
|
function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', resultSummary = '', opts = {}) {
|
|
981
1010
|
const {
|
|
982
1011
|
processWorkItemFailure = true,
|
|
@@ -1033,6 +1062,7 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
1033
1062
|
delete item.prompt;
|
|
1034
1063
|
delete item._promptFile;
|
|
1035
1064
|
delete item._promptBytes;
|
|
1065
|
+
delete item._worktreeProtectionDeferred;
|
|
1036
1066
|
if (dispatch.completed.length >= 100) {
|
|
1037
1067
|
dispatch.completed = dispatch.completed.slice(-100);
|
|
1038
1068
|
}
|
|
@@ -1208,6 +1238,8 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
1208
1238
|
// Human-readable labels for each failure class — used as fallback when reason is empty
|
|
1209
1239
|
const CLASS_LABELS = {
|
|
1210
1240
|
[FAILURE_CLASS.EMPTY_OUTPUT]: 'agent produced no output \u2014 likely crashed on startup',
|
|
1241
|
+
[FAILURE_CLASS.RUNTIME_TRUNCATED_EXIT]: 'runtime process ended mid-turn (exit 0 with an unfinished tool call) \u2014 no terminal result and no completion report; retry re-runs the truncated turn',
|
|
1242
|
+
[FAILURE_CLASS.RUNTIME_SESSION_STALE]: 'resumed runtime session was unusable (conversation purged, or its working directory no longer exists) \u2014 cached session cleared so the retry starts fresh',
|
|
1211
1243
|
[FAILURE_CLASS.BUILD_FAILURE]: 'build/test/lint failure in output',
|
|
1212
1244
|
[FAILURE_CLASS.MERGE_CONFLICT]: 'merge conflict',
|
|
1213
1245
|
[FAILURE_CLASS.DEPENDENCY_MERGE_SETUP]: 'dependency merge setup failed',
|
|
@@ -1525,6 +1557,7 @@ module.exports = {
|
|
|
1525
1557
|
addToDispatchWithValidation,
|
|
1526
1558
|
getPrDispatchDedupeKey,
|
|
1527
1559
|
isRetryableFailureReason,
|
|
1560
|
+
deferDispatchForWorktreeProtection,
|
|
1528
1561
|
completeDispatch,
|
|
1529
1562
|
writeInboxAlert,
|
|
1530
1563
|
updateAgentStatus,
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// engine/distribution.js — identity of the *installed* Minions distribution.
|
|
2
|
+
//
|
|
3
|
+
// Minions ships from one source tree as two npm packages (tools/prepare-package.js):
|
|
4
|
+
// • public → `@yemi33/minions` (npmjs, yemi33/minions)
|
|
5
|
+
// • internal → `@yemishin_microsoft/minions` (GitHub Packages; overridable
|
|
6
|
+
// at publish time via INTERNAL_PACKAGE_NAME)
|
|
7
|
+
//
|
|
8
|
+
// Distribution identity is derived ONLY from the installed package manifest —
|
|
9
|
+
// the `name` field npm resolved this runtime from. Repository remotes, branch
|
|
10
|
+
// names, checkout paths and config values are all operator-mutable and are
|
|
11
|
+
// deliberately NOT consulted: a public install must never be mistaken for an
|
|
12
|
+
// internal one because someone added an opg remote.
|
|
13
|
+
//
|
|
14
|
+
// Also owns the *installation identity* (`installationId`) that internal-only
|
|
15
|
+
// first-run surfaces key their acknowledgement on. It lives in the SQL
|
|
16
|
+
// small-state `engine_state` blob, so it survives dashboard/engine restarts and
|
|
17
|
+
// package upgrades, while a genuinely new runtime installation (fresh
|
|
18
|
+
// engine/state.db) gets a fresh id. The internal install/state-migration path
|
|
19
|
+
// consumes the same helpers so both agree on what "this installation" means.
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
const PUBLIC_PACKAGE_NAME = '@yemi33/minions';
|
|
25
|
+
|
|
26
|
+
const DISTRIBUTION_CHANNEL = {
|
|
27
|
+
PUBLIC: 'public',
|
|
28
|
+
INTERNAL: 'internal',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Exact copy required by W-ms1jgr95000ke749 — the popup's primary message.
|
|
32
|
+
const INTERNAL_WELCOME_MESSAGE = 'Welcome to the internal Minions!';
|
|
33
|
+
|
|
34
|
+
const INSTALLATION_ID_KEY = 'installationId';
|
|
35
|
+
const INTERNAL_WELCOME_ACK_KEY = 'internalWelcome';
|
|
36
|
+
|
|
37
|
+
// The installed package root: engine/ lives one level under it in both the npm
|
|
38
|
+
// tarball and the git checkout.
|
|
39
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
40
|
+
|
|
41
|
+
function _packageJsonPath(opts) {
|
|
42
|
+
if (opts && opts.packageJsonPath) return opts.packageJsonPath;
|
|
43
|
+
return path.join((opts && opts.packageRoot) || PACKAGE_ROOT, 'package.json');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Read `{ name, version }` off the installed manifest. Returns null when the
|
|
47
|
+
// manifest is missing or unparseable — callers treat that as "not internal".
|
|
48
|
+
function readRuntimePackageMeta(opts) {
|
|
49
|
+
try {
|
|
50
|
+
const raw = fs.readFileSync(_packageJsonPath(opts), 'utf8');
|
|
51
|
+
const pkg = JSON.parse(raw);
|
|
52
|
+
if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) return null;
|
|
53
|
+
const name = typeof pkg.name === 'string' ? pkg.name.trim() : '';
|
|
54
|
+
const version = typeof pkg.version === 'string' ? pkg.version.trim() : '';
|
|
55
|
+
if (!name) return null;
|
|
56
|
+
return { name, version: version || null };
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A Minions package is `minions` or `@scope/minions`. Anything else (a vendored
|
|
63
|
+
// fork under a different product name, a corrupt manifest) is not classified as
|
|
64
|
+
// an internal distribution — the internal-only surfaces stay hidden.
|
|
65
|
+
function isMinionsPackageName(name) {
|
|
66
|
+
return typeof name === 'string' && /^(?:@[^/\s]+\/)?minions$/.test(name.trim());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Internal == a recognised Minions package that is NOT the public one.
|
|
70
|
+
// Fail closed: unknown / unreadable / non-Minions names are never internal.
|
|
71
|
+
function isInternalPackageName(name) {
|
|
72
|
+
if (!isMinionsPackageName(name)) return false;
|
|
73
|
+
return name.trim() !== PUBLIC_PACKAGE_NAME;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// { packageName, version, channel, isInternal }. `packageName` is null when the
|
|
77
|
+
// manifest could not be read.
|
|
78
|
+
function resolveDistribution(opts) {
|
|
79
|
+
const meta = readRuntimePackageMeta(opts);
|
|
80
|
+
const packageName = meta ? meta.name : null;
|
|
81
|
+
const isInternal = isInternalPackageName(packageName);
|
|
82
|
+
return {
|
|
83
|
+
packageName,
|
|
84
|
+
version: meta ? meta.version : null,
|
|
85
|
+
channel: isInternal ? DISTRIBUTION_CHANNEL.INTERNAL : DISTRIBUTION_CHANNEL.PUBLIC,
|
|
86
|
+
isInternal,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function _smallState() {
|
|
91
|
+
return require('./small-state-store');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function _newInstallationId() {
|
|
95
|
+
return require('crypto').randomUUID();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Stable per-installation id, created on first read and persisted in SQL.
|
|
99
|
+
// Deliberately NOT derived from a path or hostname: copying ~/.minions to a new
|
|
100
|
+
// machine is the same installation, and two installations on one machine must
|
|
101
|
+
// not collide.
|
|
102
|
+
function resolveInstallationId() {
|
|
103
|
+
const store = _smallState();
|
|
104
|
+
const existing = store.readEngineState()[INSTALLATION_ID_KEY];
|
|
105
|
+
if (typeof existing === 'string' && existing) return existing;
|
|
106
|
+
const generated = _newInstallationId();
|
|
107
|
+
const { result } = store.applyEngineStateMutation((state) => {
|
|
108
|
+
if (typeof state[INSTALLATION_ID_KEY] === 'string' && state[INSTALLATION_ID_KEY]) return state;
|
|
109
|
+
state[INSTALLATION_ID_KEY] = generated;
|
|
110
|
+
return state;
|
|
111
|
+
});
|
|
112
|
+
return result[INSTALLATION_ID_KEY] || generated;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// { ackedAt, installationId, packageName } | null
|
|
116
|
+
function readInternalWelcomeAck() {
|
|
117
|
+
const ack = _smallState().readEngineState()[INTERNAL_WELCOME_ACK_KEY];
|
|
118
|
+
if (!ack || typeof ack !== 'object' || Array.isArray(ack)) return null;
|
|
119
|
+
if (typeof ack.ackedAt !== 'string' || !ack.ackedAt) return null;
|
|
120
|
+
return ack;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Record the acknowledgement against the CURRENT installation id, so a package
|
|
124
|
+
// upgrade (same state.db) stays acknowledged while a fresh installation does not
|
|
125
|
+
// inherit it. Idempotent: re-acking keeps the original timestamp.
|
|
126
|
+
function acknowledgeInternalWelcome(opts) {
|
|
127
|
+
const installationId = resolveInstallationId();
|
|
128
|
+
const distribution = resolveDistribution(opts);
|
|
129
|
+
const store = _smallState();
|
|
130
|
+
const { result } = store.applyEngineStateMutation((state) => {
|
|
131
|
+
const prev = state[INTERNAL_WELCOME_ACK_KEY];
|
|
132
|
+
const alreadyAcked = prev && typeof prev === 'object' && !Array.isArray(prev)
|
|
133
|
+
&& typeof prev.ackedAt === 'string' && prev.ackedAt
|
|
134
|
+
&& prev.installationId === installationId;
|
|
135
|
+
if (alreadyAcked) return state;
|
|
136
|
+
state[INTERNAL_WELCOME_ACK_KEY] = {
|
|
137
|
+
ackedAt: new Date().toISOString(),
|
|
138
|
+
installationId,
|
|
139
|
+
packageName: distribution.packageName,
|
|
140
|
+
};
|
|
141
|
+
return state;
|
|
142
|
+
});
|
|
143
|
+
return result[INTERNAL_WELCOME_ACK_KEY];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The full payload the dashboard serves from GET /api/welcome.
|
|
147
|
+
//
|
|
148
|
+
// `show` is true only for an internal distribution whose acknowledgement is
|
|
149
|
+
// missing or belongs to a different installation. SQL being unavailable resolves
|
|
150
|
+
// to `show: false` rather than throwing — a broken state DB must not turn into a
|
|
151
|
+
// popup on every refresh.
|
|
152
|
+
function resolveInternalWelcomeState(opts) {
|
|
153
|
+
const distribution = resolveDistribution(opts);
|
|
154
|
+
const payload = {
|
|
155
|
+
show: false,
|
|
156
|
+
message: INTERNAL_WELCOME_MESSAGE,
|
|
157
|
+
distribution,
|
|
158
|
+
installationId: null,
|
|
159
|
+
ackedAt: null,
|
|
160
|
+
};
|
|
161
|
+
if (!distribution.isInternal) return payload;
|
|
162
|
+
try {
|
|
163
|
+
const installationId = resolveInstallationId();
|
|
164
|
+
const ack = readInternalWelcomeAck();
|
|
165
|
+
payload.installationId = installationId;
|
|
166
|
+
payload.ackedAt = ack && ack.installationId === installationId ? ack.ackedAt : null;
|
|
167
|
+
payload.show = !payload.ackedAt;
|
|
168
|
+
} catch {
|
|
169
|
+
payload.show = false;
|
|
170
|
+
}
|
|
171
|
+
return payload;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = {
|
|
175
|
+
PUBLIC_PACKAGE_NAME,
|
|
176
|
+
DISTRIBUTION_CHANNEL,
|
|
177
|
+
INTERNAL_WELCOME_MESSAGE,
|
|
178
|
+
INSTALLATION_ID_KEY,
|
|
179
|
+
INTERNAL_WELCOME_ACK_KEY,
|
|
180
|
+
PACKAGE_ROOT,
|
|
181
|
+
readRuntimePackageMeta,
|
|
182
|
+
isMinionsPackageName,
|
|
183
|
+
isInternalPackageName,
|
|
184
|
+
resolveDistribution,
|
|
185
|
+
resolveInstallationId,
|
|
186
|
+
readInternalWelcomeAck,
|
|
187
|
+
acknowledgeInternalWelcome,
|
|
188
|
+
resolveInternalWelcomeState,
|
|
189
|
+
};
|
|
@@ -59,10 +59,69 @@ function resolveCommentExecutionModel({
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Honest, stable label for "no concrete model id is known — the runtime picked
|
|
64
|
+
* its own default". Never invent a model name and never render blank.
|
|
65
|
+
*/
|
|
66
|
+
function runtimeDefaultModelLabel(runtime) {
|
|
67
|
+
const name = typeof runtime === 'string' ? runtime.trim() : '';
|
|
68
|
+
return name ? `${name} default` : UNKNOWN_MODEL;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A model id we can show verbatim. `auto` is a request for the runtime to pick,
|
|
73
|
+
* not a concrete model — mirrors buildRuntimeModelEvidence's treatment in
|
|
74
|
+
* engine/queries.js.
|
|
75
|
+
*/
|
|
76
|
+
function concreteExecutionModel(value) {
|
|
77
|
+
const model = normalizeExecutionModel(value);
|
|
78
|
+
if (!model) return null;
|
|
79
|
+
return model.toLowerCase() === 'auto' ? null : model;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* W-ms0ytfdd000d5a26 — Resolve the effective model for a work item.
|
|
84
|
+
*
|
|
85
|
+
* Persisted dispatch evidence always wins so later config edits cannot rewrite
|
|
86
|
+
* history:
|
|
87
|
+
* 1. `dispatchEntry.executionModel` — what the runtime reported it ran.
|
|
88
|
+
* 2. `dispatchEntry.requestedModel` — what Minions sent via `--model`.
|
|
89
|
+
* 3. `<runtime> default` — it ran, but nothing concrete was pinned
|
|
90
|
+
* or reported.
|
|
91
|
+
* Only a work item with no dispatch record at all falls through to the caller's
|
|
92
|
+
* current-config resolution (`configuredRuntime` / `configuredModel`), which is
|
|
93
|
+
* flagged with `persisted: false` so the UI can say so.
|
|
94
|
+
*
|
|
95
|
+
* Runtime-agnostic by construction: the runtime is data carried on the record
|
|
96
|
+
* (or supplied by the caller), never branched on by name.
|
|
97
|
+
*/
|
|
98
|
+
function resolveWorkItemModel({ dispatchEntry, configuredRuntime, configuredModel } = {}) {
|
|
99
|
+
const fallbackRuntime = typeof configuredRuntime === 'string' ? configuredRuntime.trim() : '';
|
|
100
|
+
const build = (model, source, runtime, persisted) => ({
|
|
101
|
+
model: model || null,
|
|
102
|
+
label: model || runtimeDefaultModelLabel(runtime),
|
|
103
|
+
source: model ? source : (runtime ? 'runtime-default' : 'unknown'),
|
|
104
|
+
runtime: runtime || null,
|
|
105
|
+
persisted,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (dispatchEntry) {
|
|
109
|
+
const entryRuntime = typeof dispatchEntry.runtimeName === 'string' ? dispatchEntry.runtimeName.trim() : '';
|
|
110
|
+
const runtime = entryRuntime || fallbackRuntime;
|
|
111
|
+
const reported = concreteExecutionModel(dispatchEntry.executionModel);
|
|
112
|
+
if (reported) return build(reported, 'runtime-reported', runtime, true);
|
|
113
|
+
return build(concreteExecutionModel(dispatchEntry.requestedModel), 'requested', runtime, true);
|
|
114
|
+
}
|
|
115
|
+
return build(concreteExecutionModel(configuredModel), 'configured', fallbackRuntime, false);
|
|
116
|
+
}
|
|
117
|
+
|
|
62
118
|
module.exports = {
|
|
63
119
|
UNKNOWN_MODEL,
|
|
64
120
|
MODEL_ID_RE,
|
|
65
121
|
normalizeExecutionModel,
|
|
122
|
+
concreteExecutionModel,
|
|
66
123
|
resolveExecutionModel,
|
|
67
124
|
resolveCommentExecutionModel,
|
|
125
|
+
runtimeDefaultModelLabel,
|
|
126
|
+
resolveWorkItemModel,
|
|
68
127
|
};
|
package/engine/features.js
CHANGED
|
@@ -102,6 +102,18 @@ const FEATURES = {
|
|
|
102
102
|
addedIn: '0.1.2233',
|
|
103
103
|
expires: '2027-01-31',
|
|
104
104
|
},
|
|
105
|
+
'review-learning-capture': {
|
|
106
|
+
description: 'Capture verified code-review findings as bounded, project-scoped review-learning candidates and procedural lessons.',
|
|
107
|
+
default: false,
|
|
108
|
+
addedIn: '0.1.2203',
|
|
109
|
+
expires: '2027-01-31',
|
|
110
|
+
},
|
|
111
|
+
'review-learning-recall': {
|
|
112
|
+
description: 'Recall bounded applicable review lessons through the dedicated project-scoped review-learning path.',
|
|
113
|
+
default: false,
|
|
114
|
+
addedIn: '0.1.2203',
|
|
115
|
+
expires: '2027-01-31',
|
|
116
|
+
},
|
|
105
117
|
};
|
|
106
118
|
|
|
107
119
|
const ENV_TRUTHY = new Set(['1', 'true', 'on', 'yes']);
|