@yemi33/minions 0.1.1043 → 0.1.1045
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/CHANGELOG.md +3 -1
- package/dashboard/js/refresh.js +1 -0
- package/dashboard.js +156 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1045 (2026-04-16)
|
|
4
4
|
|
|
5
5
|
### Other
|
|
6
|
+
- Harden loop watch conversion
|
|
7
|
+
- Add watches sidebar activity badge
|
|
6
8
|
- test(cli): add unit tests for handleCommand, start, stop, kill, spawn (#1191)
|
|
7
9
|
- chore: untrack pipeline files — local config only
|
|
8
10
|
- restore: recover daily-arch-improvement and weekly-dead-code-cleanup pipelines
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -8,6 +8,7 @@ const _pageCounters = {
|
|
|
8
8
|
plans: function(d) { return (d.prdProgress?.complete || 0) + '|' + (d.plans || []).length + '|' + (d.plans || []).map(function(p) { return p.status || ''; }).join(','); },
|
|
9
9
|
prs: function(d) { return (d.pullRequests || []).length + '|' + (d.pullRequests || []).filter(function(p) { return p.status === 'merged'; }).length; },
|
|
10
10
|
inbox: function(d) { return (d.inbox || []).length + '|' + (d.notes?.content || '').length; },
|
|
11
|
+
watches: function(d) { return (d.watches || []).length + '|' + (d.watches || []).map(function(w) { return [w.id || '', w.status || '', w.triggerCount || 0, w.last_triggered || ''].join(':'); }).join(','); },
|
|
11
12
|
meetings: function(d) { return (d.meetings || []).length + '|' + (d.meetings || []).reduce(function(s, m) { return s + (m.round || 0); }, 0); },
|
|
12
13
|
pipelines: function(d) { return (d.pipelines || []).length + '|' + (d.pipelines || []).reduce(function(s, p) { return s + (p.runs || []).length; }, 0); },
|
|
13
14
|
schedule: function(d) { return (d.schedules || []).length; },
|
package/dashboard.js
CHANGED
|
@@ -672,11 +672,110 @@ function parseCCActions(text) {
|
|
|
672
672
|
// This pure function detects /loop invocation in CC response text and synthesizes
|
|
673
673
|
// a create-watch action as a fallback. Returns null if no conversion needed.
|
|
674
674
|
|
|
675
|
-
function _detectLoopInvocation(text, actions) {
|
|
676
|
-
|
|
675
|
+
function _detectLoopInvocation(text, actions, toolUses) {
|
|
676
|
+
const observedToolUses = Array.isArray(toolUses) ? toolUses : [];
|
|
677
|
+
if (!text && observedToolUses.length === 0) return null;
|
|
677
678
|
// If a create-watch action was already emitted, no fallback needed
|
|
678
679
|
if (actions && actions.some(a => a.type === 'create-watch')) return null;
|
|
679
680
|
|
|
681
|
+
function _extractTargetFromValue(value, keyHint) {
|
|
682
|
+
if (value == null) return null;
|
|
683
|
+
const hint = String(keyHint || '').toLowerCase();
|
|
684
|
+
if (Array.isArray(value)) {
|
|
685
|
+
for (const item of value) {
|
|
686
|
+
const nested = _extractTargetFromValue(item, hint);
|
|
687
|
+
if (nested) return nested;
|
|
688
|
+
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
if (typeof value === 'object') {
|
|
692
|
+
for (const [k, v] of Object.entries(value)) {
|
|
693
|
+
const nested = _extractTargetFromValue(v, k);
|
|
694
|
+
if (nested) return nested;
|
|
695
|
+
}
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
const str = String(value).trim();
|
|
699
|
+
if (!str) return null;
|
|
700
|
+
const prUrlMatch = str.match(/\/pull\/(\d+)\b/i) || str.match(/\/pullrequest\/(\d+)\b/i);
|
|
701
|
+
if (prUrlMatch) return { target: prUrlMatch[1], targetType: 'pr' };
|
|
702
|
+
const prMatch = str.match(/\bPR[- #:]?(\d+)\b/i) || str.match(/\bpull[- ]request[- #:]?(\d+)\b/i);
|
|
703
|
+
if (prMatch) return { target: prMatch[1], targetType: 'pr' };
|
|
704
|
+
const wiMatch = str.match(/\bW-([a-z0-9]+)\b/i);
|
|
705
|
+
if (wiMatch) return { target: 'W-' + wiMatch[1], targetType: 'work-item' };
|
|
706
|
+
if ((hint.includes('pr') || hint.includes('pull')) && /^\d+$/.test(str)) return { target: str, targetType: 'pr' };
|
|
707
|
+
if ((hint.includes('work') || hint.includes('item') || hint === 'id') && /^W-[a-z0-9]+$/i.test(str)) return { target: str.toUpperCase().startsWith('W-') ? 'W-' + str.slice(2) : str, targetType: 'work-item' };
|
|
708
|
+
if (hint.includes('target')) {
|
|
709
|
+
if (/^\d+$/.test(str)) return { target: str, targetType: 'pr' };
|
|
710
|
+
if (/^W-[a-z0-9]+$/i.test(str)) return { target: 'W-' + str.slice(2), targetType: 'work-item' };
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function _extractIntervalFromValue(value, keyHint) {
|
|
716
|
+
if (value == null) return null;
|
|
717
|
+
const hint = String(keyHint || '').toLowerCase();
|
|
718
|
+
if (Array.isArray(value)) {
|
|
719
|
+
for (const item of value) {
|
|
720
|
+
const nested = _extractIntervalFromValue(item, hint);
|
|
721
|
+
if (nested) return nested;
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
if (typeof value === 'object') {
|
|
726
|
+
for (const [k, v] of Object.entries(value)) {
|
|
727
|
+
const nested = _extractIntervalFromValue(v, k);
|
|
728
|
+
if (nested) return nested;
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
if (!(hint.includes('interval') || hint.includes('every') || hint.includes('frequency'))) return null;
|
|
733
|
+
const str = String(value).trim().toLowerCase();
|
|
734
|
+
if (!str) return null;
|
|
735
|
+
if (/^\d+$/.test(str)) return str;
|
|
736
|
+
const match = str.match(/^(\d+(?:\.\d+)?)\s*(s|sec|seconds?|m|min|minutes?|h|hr|hours?)$/i);
|
|
737
|
+
if (!match) return null;
|
|
738
|
+
return match[1] + match[2][0].toLowerCase();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function _extractConditionFromValue(value, keyHint) {
|
|
742
|
+
if (value == null) return null;
|
|
743
|
+
const hint = String(keyHint || '').toLowerCase();
|
|
744
|
+
if (Array.isArray(value)) {
|
|
745
|
+
for (const item of value) {
|
|
746
|
+
const nested = _extractConditionFromValue(item, hint);
|
|
747
|
+
if (nested) return nested;
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
if (typeof value === 'object') {
|
|
752
|
+
for (const [k, v] of Object.entries(value)) {
|
|
753
|
+
const nested = _extractConditionFromValue(v, k);
|
|
754
|
+
if (nested) return nested;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
if (!(hint.includes('condition') || hint.includes('until') || hint.includes('goal') || hint.includes('status'))) return null;
|
|
759
|
+
const str = String(value).trim().toLowerCase();
|
|
760
|
+
if (['merged', 'build-pass', 'build-fail', 'completed', 'failed', 'status-change', 'any', 'new-comments', 'vote-change'].includes(str)) return str;
|
|
761
|
+
if (/\b(?:pass(?:es|ing|ed)?|green|succeed(?:s|ed)?|success)\b/i.test(str)) return 'build-pass';
|
|
762
|
+
if (/\b(?:fail(?:s|ing|ed)?|red|broken|broke)\b/i.test(str)) return 'build-fail';
|
|
763
|
+
if (/\bmerge(?:d)?\b/i.test(str)) return 'merged';
|
|
764
|
+
if (/\bcomplete(?:d)?\b/i.test(str)) return 'completed';
|
|
765
|
+
if (/\bfail(?:ed)?\b/i.test(str)) return 'failed';
|
|
766
|
+
if (/\bcomment/i.test(str)) return 'new-comments';
|
|
767
|
+
if (/\bvote|review/i.test(str)) return 'vote-change';
|
|
768
|
+
if (/\bstatus/i.test(str)) return 'any';
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const loopToolSeen = observedToolUses.some(t => /\bloop\b/i.test(String(t?.name || '')));
|
|
773
|
+
const toolText = observedToolUses.map(t => {
|
|
774
|
+
try { return [String(t?.name || ''), JSON.stringify(t?.input || {})].filter(Boolean).join(' '); }
|
|
775
|
+
catch { return String(t?.name || ''); }
|
|
776
|
+
}).join('\n');
|
|
777
|
+
const combinedText = [text || '', toolText].filter(Boolean).join('\n');
|
|
778
|
+
|
|
680
779
|
// Check for /loop invocation patterns in CC response
|
|
681
780
|
const loopPatterns = [
|
|
682
781
|
/\/loop\b/i,
|
|
@@ -686,14 +785,21 @@ function _detectLoopInvocation(text, actions) {
|
|
|
686
785
|
/\bmonitoring.*\bloop\b/i,
|
|
687
786
|
/\binvok(?:e|ed|ing).*\bloop\b/i,
|
|
688
787
|
];
|
|
689
|
-
if (!loopPatterns.some(p => p.test(
|
|
788
|
+
if (!loopToolSeen && !loopPatterns.some(p => p.test(combinedText))) return null;
|
|
690
789
|
|
|
691
790
|
// Extract target — PR number or work item ID
|
|
692
|
-
const
|
|
693
|
-
const
|
|
791
|
+
const directTarget = observedToolUses.map(t => _extractTargetFromValue(t && t.input, t && t.name)).find(Boolean);
|
|
792
|
+
const prMatch = combinedText.match(/\/pull\/(\d+)\b/i) ||
|
|
793
|
+
combinedText.match(/\/pullrequest\/(\d+)\b/i) ||
|
|
794
|
+
combinedText.match(/\bPR[- #:]?(\d+)\b/i) ||
|
|
795
|
+
combinedText.match(/\bpull[- ]request[- #:]?(\d+)/i);
|
|
796
|
+
const wiMatch = combinedText.match(/\bW-([a-z0-9]+)\b/i);
|
|
694
797
|
|
|
695
798
|
let target = null, targetType = 'pr';
|
|
696
|
-
if (
|
|
799
|
+
if (directTarget) {
|
|
800
|
+
target = directTarget.target;
|
|
801
|
+
targetType = directTarget.targetType;
|
|
802
|
+
} else if (prMatch) {
|
|
697
803
|
target = prMatch[1];
|
|
698
804
|
targetType = 'pr';
|
|
699
805
|
} else if (wiMatch) {
|
|
@@ -703,16 +809,20 @@ function _detectLoopInvocation(text, actions) {
|
|
|
703
809
|
if (!target) return null; // Can't synthesize without a target
|
|
704
810
|
|
|
705
811
|
// Extract interval (e.g. "every 15 minutes", "every 5m")
|
|
706
|
-
const
|
|
812
|
+
const directInterval = observedToolUses.map(t => _extractIntervalFromValue(t && t.input, t && t.name)).find(Boolean);
|
|
813
|
+
const intervalMatch = combinedText.match(/every\s+(\d+)\s*(s|sec|seconds?|m|min|minutes?|h|hr|hours?)\b/i);
|
|
707
814
|
let interval = '5m';
|
|
708
|
-
if (
|
|
815
|
+
if (directInterval) interval = directInterval;
|
|
816
|
+
else if (intervalMatch) interval = intervalMatch[1] + intervalMatch[2][0];
|
|
709
817
|
|
|
710
818
|
// Infer condition from keywords
|
|
711
|
-
let condition = 'any';
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
819
|
+
let condition = observedToolUses.map(t => _extractConditionFromValue(t && t.input, t && t.name)).find(Boolean) || 'any';
|
|
820
|
+
if (condition === 'any') {
|
|
821
|
+
if (/\bbuild\b/i.test(combinedText) && /\b(?:pass(?:es|ing|ed)?|green|succeed(?:s|ed)?|success)\b/i.test(combinedText)) condition = 'build-pass';
|
|
822
|
+
else if (/\bbuild\b/i.test(combinedText) && /\b(?:fail(?:s|ing|ed)?|red|broken|broke)\b/i.test(combinedText)) condition = 'build-fail';
|
|
823
|
+
else if (/\bmerge[d]?\b/i.test(combinedText)) condition = 'merged';
|
|
824
|
+
else if (/\bcomplete[d]?\b/i.test(combinedText)) condition = 'completed';
|
|
825
|
+
}
|
|
716
826
|
|
|
717
827
|
return {
|
|
718
828
|
type: 'create-watch',
|
|
@@ -726,6 +836,23 @@ function _detectLoopInvocation(text, actions) {
|
|
|
726
836
|
};
|
|
727
837
|
}
|
|
728
838
|
|
|
839
|
+
function _extractToolUsesFromRaw(raw) {
|
|
840
|
+
const toolUses = [];
|
|
841
|
+
if (!raw) return toolUses;
|
|
842
|
+
for (const line of String(raw).split('\n')) {
|
|
843
|
+
const trimmed = line.trim();
|
|
844
|
+
if (!trimmed || !trimmed.startsWith('{')) continue;
|
|
845
|
+
try {
|
|
846
|
+
const obj = JSON.parse(trimmed);
|
|
847
|
+
if (obj.type !== 'assistant' || !Array.isArray(obj.message?.content)) continue;
|
|
848
|
+
for (const block of obj.message.content) {
|
|
849
|
+
if (block?.type === 'tool_use' && block.name) toolUses.push({ name: block.name, input: block.input || {} });
|
|
850
|
+
}
|
|
851
|
+
} catch {}
|
|
852
|
+
}
|
|
853
|
+
return toolUses;
|
|
854
|
+
}
|
|
855
|
+
|
|
729
856
|
// ── Server-side CC action execution ──────────────────────────────────────────
|
|
730
857
|
// Actions are executed server-side so all clients (frontend, curl, Teams) get the same behavior.
|
|
731
858
|
// The frontend still shows status toasts but no longer needs to fire the API calls.
|
|
@@ -3758,8 +3885,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3758
3885
|
}
|
|
3759
3886
|
|
|
3760
3887
|
const parsed = parseCCActions(result.text);
|
|
3888
|
+
const toolUses = _extractToolUsesFromRaw(result.raw);
|
|
3761
3889
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
3762
|
-
const _loopWatch = _detectLoopInvocation(parsed.text, parsed.actions);
|
|
3890
|
+
const _loopWatch = _detectLoopInvocation(parsed.text, parsed.actions, toolUses);
|
|
3763
3891
|
if (_loopWatch) {
|
|
3764
3892
|
parsed.actions.push(_loopWatch);
|
|
3765
3893
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
@@ -3869,6 +3997,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3869
3997
|
const streamModel = CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel;
|
|
3870
3998
|
const streamEffort = CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort;
|
|
3871
3999
|
const ccMaxTurns = CONFIG.engine?.ccMaxTurns || shared.ENGINE_DEFAULTS.ccMaxTurns;
|
|
4000
|
+
let toolUses = [];
|
|
3872
4001
|
const llmPromise = callLLMStreaming(prompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
3873
4002
|
timeout: 900000, label: 'command-center', model: streamModel, maxTurns: ccMaxTurns,
|
|
3874
4003
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
@@ -3879,6 +4008,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3879
4008
|
writeCcEvent({ type: 'chunk', text: display });
|
|
3880
4009
|
},
|
|
3881
4010
|
onToolUse: (name, input) => {
|
|
4011
|
+
toolUses.push({ name, input: input || {} });
|
|
3882
4012
|
writeCcEvent({ type: 'tool', name, input: _lightToolInput(input) });
|
|
3883
4013
|
}
|
|
3884
4014
|
});
|
|
@@ -3893,6 +4023,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3893
4023
|
console.log(`[CC-stream] Resume failed (code=${result.code}) — retrying fresh`);
|
|
3894
4024
|
const freshPreamble = buildCCStatePreamble();
|
|
3895
4025
|
const freshPrompt = (freshPreamble ? freshPreamble + '\n\n---\n\n' : '') + body.message;
|
|
4026
|
+
toolUses = []; // discard stale metadata from the failed resume attempt
|
|
3896
4027
|
const retryPromise = callLLMStreaming(freshPrompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
3897
4028
|
timeout: 900000, label: 'command-center', model: streamModel, maxTurns: ccMaxTurns,
|
|
3898
4029
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
@@ -3903,6 +4034,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3903
4034
|
writeCcEvent({ type: 'chunk', text: display });
|
|
3904
4035
|
},
|
|
3905
4036
|
onToolUse: (name, input) => {
|
|
4037
|
+
toolUses.push({ name, input: input || {} });
|
|
3906
4038
|
writeCcEvent({ type: 'tool', name, input: _lightToolInput(input) });
|
|
3907
4039
|
}
|
|
3908
4040
|
});
|
|
@@ -3952,7 +4084,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3952
4084
|
// Send final result with actions — execute server-side first
|
|
3953
4085
|
const { text: displayText, actions } = parseCCActions(result.text);
|
|
3954
4086
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
3955
|
-
const _loopWatch = _detectLoopInvocation(displayText, actions);
|
|
4087
|
+
const _loopWatch = _detectLoopInvocation(displayText, actions, toolUses);
|
|
3956
4088
|
if (_loopWatch) {
|
|
3957
4089
|
actions.push(_loopWatch);
|
|
3958
4090
|
console.warn('[CC] /loop invocation detected — converted to create-watch');
|
|
@@ -4142,8 +4274,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4142
4274
|
try {
|
|
4143
4275
|
const config = queries.getConfig();
|
|
4144
4276
|
const routing = safeRead(path.join(MINIONS_DIR, 'routing.md')) || '';
|
|
4277
|
+
const engine = { ...shared.ENGINE_DEFAULTS, ...(config.engine || {}) };
|
|
4278
|
+
if (engine.prPollStatusEvery === undefined && engine.adoPollStatusEvery !== undefined) engine.prPollStatusEvery = engine.adoPollStatusEvery;
|
|
4279
|
+
if (engine.prPollCommentsEvery === undefined && engine.adoPollCommentsEvery !== undefined) engine.prPollCommentsEvery = engine.adoPollCommentsEvery;
|
|
4145
4280
|
return jsonReply(res, 200, {
|
|
4146
|
-
engine
|
|
4281
|
+
engine,
|
|
4147
4282
|
claude: { ...shared.DEFAULT_CLAUDE, ...(config.claude || {}) },
|
|
4148
4283
|
agents: config.agents || {},
|
|
4149
4284
|
teams: { ...shared.ENGINE_DEFAULTS.teams, ...(config.teams || {}) },
|
|
@@ -4172,6 +4307,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4172
4307
|
if (body.engine) {
|
|
4173
4308
|
const e = body.engine;
|
|
4174
4309
|
const D = shared.ENGINE_DEFAULTS;
|
|
4310
|
+
if (e.prPollStatusEvery === undefined && e.adoPollStatusEvery !== undefined) e.prPollStatusEvery = e.adoPollStatusEvery;
|
|
4311
|
+
if (e.prPollCommentsEvery === undefined && e.adoPollCommentsEvery !== undefined) e.prPollCommentsEvery = e.adoPollCommentsEvery;
|
|
4175
4312
|
// Numeric fields: { key: [min, max?] }
|
|
4176
4313
|
const numericFields = {
|
|
4177
4314
|
tickInterval: [10000], maxConcurrent: [1, 50], inboxConsolidateThreshold: [1],
|
|
@@ -4181,7 +4318,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4181
4318
|
meetingRoundTimeout: [60000],
|
|
4182
4319
|
versionCheckInterval: [60000],
|
|
4183
4320
|
maxBuildFixAttempts: [1, 10],
|
|
4184
|
-
|
|
4321
|
+
prPollStatusEvery: [1], prPollCommentsEvery: [1],
|
|
4185
4322
|
agentBusyReassignMs: [0],
|
|
4186
4323
|
};
|
|
4187
4324
|
for (const [key, [min, max]] of Object.entries(numericFields)) {
|
|
@@ -4194,6 +4331,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4194
4331
|
config.engine[key] = val;
|
|
4195
4332
|
}
|
|
4196
4333
|
}
|
|
4334
|
+
delete config.engine.adoPollStatusEvery;
|
|
4335
|
+
delete config.engine.adoPollCommentsEvery;
|
|
4197
4336
|
// String fields
|
|
4198
4337
|
if (e.worktreeRoot !== undefined) config.engine.worktreeRoot = String(e.worktreeRoot || D.worktreeRoot);
|
|
4199
4338
|
// CC model/effort
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1045",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|